<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Alessandro Grosselle</title>
    <description>The latest articles on Forem by Alessandro Grosselle (@alessandro-grosselle).</description>
    <link>https://forem.com/alessandro-grosselle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3373823%2F078c4765-23d7-4edc-84a2-c3eadefeeb4a.jpeg</url>
      <title>Forem: Alessandro Grosselle</title>
      <link>https://forem.com/alessandro-grosselle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alessandro-grosselle"/>
    <language>en</language>
    <item>
      <title>How to Return HTTP 410 (Gone) Status in Next.js App Router: Two Workarounds</title>
      <dc:creator>Alessandro Grosselle</dc:creator>
      <pubDate>Sat, 24 Jan 2026 22:08:33 +0000</pubDate>
      <link>https://forem.com/alessandro-grosselle/how-to-return-http-410-gone-status-in-nextjs-app-router-two-workarounds-2f0g</link>
      <guid>https://forem.com/alessandro-grosselle/how-to-return-http-410-gone-status-in-nextjs-app-router-two-workarounds-2f0g</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;In marketplace applications, the HTTP 410 (Gone) status code is crucial for indicating that an item is no longer available. &lt;br&gt;
For second-hand marketplace platforms like Vinted or similar e-commerce sites, returning a 410 status is essential for SEO; it signals to search engines that the content has been permanently removed and should be deindexed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Let's say you need to implement a Next.js page using App Router that returns a 410 status. The typical flow would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch data from your backend service&lt;/li&gt;
&lt;li&gt;Backend returns 410 (Gone)&lt;/li&gt;
&lt;li&gt;Frontend displays a 410 page (perhaps suggesting other listings)&lt;/li&gt;
&lt;li&gt;Browser and search engines see the 410 status code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sounds simple, right? &lt;strong&gt;Wrong!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Currently, Next.js App Router &lt;strong&gt;does not support&lt;/strong&gt; forcing a 410 status code. While it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/api-reference/functions/redirect" rel="noopener noreferrer"&gt;&lt;code&gt;redirect()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/api-reference/functions/not-found" rel="noopener noreferrer"&gt;&lt;code&gt;notFound()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/api-reference/functions/unauthorized" rel="noopener noreferrer"&gt;&lt;code&gt;unauthorized()&lt;/code&gt;&lt;/a&gt; (experimental)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/api-reference/functions/forbidden" rel="noopener noreferrer"&gt;&lt;code&gt;forbidden()&lt;/code&gt;&lt;/a&gt; (experimental)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no built-in way to return a 410 status.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://github.com/vercel/next.js/pull/78706" rel="noopener noreferrer"&gt;PR proposing a &lt;code&gt;gone()&lt;/code&gt; function&lt;/a&gt; has been submitted, but there's no ETA on when it will be merged.&lt;/p&gt;
&lt;h2&gt;
  
  
  Purpose of This Article
&lt;/h2&gt;

&lt;p&gt;Intrigued by this limitation, I explored alternatives rather than waiting for the PR. I've identified &lt;strong&gt;two possible workarounds&lt;/strong&gt; (I hesitate to call them "solutions" - they're definitely workarounds).&lt;/p&gt;

&lt;p&gt;All code examples are available in this &lt;a href="https://github.com/ale-grosselle/public-demo/tree/main/apps/next-410-example" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Solution 1: Custom Express.js Server
&lt;/h2&gt;

&lt;p&gt;If your application uses a custom server like Express.js, this is the best workaround I've found.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Strategy
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In your Next.js page, when your backend returns 410, call Next.js's &lt;code&gt;notFound()&lt;/code&gt; (which generates a 404)&lt;/li&gt;
&lt;li&gt;At the Express.js level, intercept the 404 and override it to 410&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Next.js Page Implementation
&lt;/h4&gt;

&lt;p&gt;Here's a simple page that uses &lt;code&gt;notFound()&lt;/code&gt; when the backend returns 410:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/page-with-error-using-express/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;notFound&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getContentById&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/api/content/utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SearchParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentDetailsResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getContentById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contentDetailsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle 410 Gone status using Next.js notFound function&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentDetailsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;410&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Ad&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/ale-grosselle/public-demo/blob/main/apps/next-410-example/app/page-with-error-using-express/page.tsx" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Express.js Server with Status Code Interception
&lt;/h4&gt;

&lt;p&gt;Now here's the magic: we intercept the &lt;code&gt;statusCode&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.ts&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/page-with-error-using-express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Intercept statusCode property to change 404 to 410&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentStatusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;statusCode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;currentStatusCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Intercepted 404 in statusCode setter, changing to 410&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;currentStatusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;410&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;currentStatusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;configurable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Render the Next.js page&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/page-with-error-using-express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ParsedUrlQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/ale-grosselle/public-demo/blob/main/apps/next-410-example/server.ts" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;The key is &lt;strong&gt;intercepting the response before it's sent&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before calling &lt;code&gt;app.render()&lt;/code&gt;, we override the &lt;code&gt;statusCode&lt;/code&gt; property setter using &lt;code&gt;Object.defineProperty()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When Next.js tries to set &lt;code&gt;res.statusCode = 404&lt;/code&gt; (from &lt;code&gt;notFound()&lt;/code&gt;), our interceptor catches it&lt;/li&gt;
&lt;li&gt;We change it to 410 before the headers are sent to the browser&lt;/li&gt;
&lt;li&gt;The browser receives a 410 status code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Important Note:&lt;/strong&gt; You must intercept &lt;strong&gt;before&lt;/strong&gt; &lt;code&gt;app.render()&lt;/code&gt; is called. Here's why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.render() internally does:
1. Process the route
2. Call res.writeHead(404, headers)  ← Sends status to browser
3. Call res.write(htmlContent)        ← Sends body
4. Call res.end()                     ← Closes connection
5. Return to your code (too late!)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you check &lt;code&gt;res.statusCode&lt;/code&gt; &lt;strong&gt;after&lt;/strong&gt; &lt;code&gt;app.render()&lt;/code&gt;, the response has already been sent with 404 headers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 2: Using Next.js Middleware (proxy.ts)
&lt;/h2&gt;

&lt;p&gt;If you don't have a custom server or prefer not to add that logic to your server, here's an alternative approach using Next.js middleware.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Strategy
&lt;/h3&gt;

&lt;p&gt;The proxy/middleware is called &lt;strong&gt;before&lt;/strong&gt; the React Server Component logic executes. The idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the content status in the middleware&lt;/li&gt;
&lt;li&gt;If it returns 410, rewrite to an error page with 410 status&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Middleware with 410 Handling
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// proxy.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/page-with-error-using-proxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentDetailsResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getContentById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentDetailsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;410&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/page-with-error-using-proxy/410`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;410&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/ale-grosselle/public-demo/blob/main/apps/next-410-example/proxy.ts" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Caveat
&lt;/h3&gt;

&lt;p&gt;Everything works well, &lt;strong&gt;BUT&lt;/strong&gt; there's a significant drawback: you need to make the same API call &lt;strong&gt;twice&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First in the middleware/proxy to check the status&lt;/li&gt;
&lt;li&gt;Again in the page component to fetch the actual data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might think "let's use React's &lt;code&gt;cache&lt;/code&gt; function to optimize!" Great idea, but unfortunately &lt;strong&gt;it won't work&lt;/strong&gt; because the middleware and page run in different contexts, so the cache isn't shared.&lt;/p&gt;

&lt;p&gt;This solution is viable if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't mind the duplicate API call&lt;/li&gt;
&lt;li&gt;OR you don't need to fetch data at all (status check only)&lt;/li&gt;
&lt;li&gt;OR your API is fast enough that the duplicate call isn't a concern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The goal of this article was to share my two workaround approaches and, more importantly, to understand if others have faced the same problem and how they've solved it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions for the community:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have you encountered this limitation in Next.js App Router?&lt;/li&gt;
&lt;li&gt;Do you have alternative solutions?&lt;/li&gt;
&lt;li&gt;What's your preferred workaround?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;To the Vercel/Next.js team:&lt;/strong&gt; Are there better approaches we should consider? Any updates on the &lt;a href="https://github.com/vercel/next.js/pull/78706" rel="noopener noreferrer"&gt;&lt;code&gt;gone()&lt;/code&gt; function PR&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Until Next.js provides native support for custom status codes, these workarounds can help you properly handle 410 responses in your marketplace or e-commerce applications.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>seo</category>
    </item>
    <item>
      <title>Comparing Page Transition Strategies in Next.js: A Performance Study</title>
      <dc:creator>Alessandro Grosselle</dc:creator>
      <pubDate>Sun, 26 Oct 2025 14:31:08 +0000</pubDate>
      <link>https://forem.com/alessandro-grosselle/comparing-page-transition-strategies-in-nextjs-a-performance-study-19i0</link>
      <guid>https://forem.com/alessandro-grosselle/comparing-page-transition-strategies-in-nextjs-a-performance-study-19i0</guid>
      <description>&lt;p&gt;In the era of modern web development, we often ask ourselves: what's the best way to navigate between pages?&lt;br&gt;
By "best," I mean providing a user experience that's as smooth and continuous as native apps have trained us to expect.&lt;/p&gt;

&lt;p&gt;If you're a developer working with Next.js, you've probably noticed several features that support navigation.&lt;br&gt;
You're likely using the &lt;a href="https://nextjs.org/docs/app/api-reference/components/link" rel="noopener noreferrer"&gt;next/link&lt;/a&gt; component for client-side navigation, similar to a single-page application.&lt;br&gt;
And if you're staying current, you might have heard about exciting new web platform features like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API" rel="noopener noreferrer"&gt;Speculation Rules&lt;/a&gt; and &lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions" rel="noopener noreferrer"&gt;CSS View Transitions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With all these options available, I wanted to answer a simple question:&lt;br&gt;
&lt;strong&gt;What's the optimal way to navigate between pages in a Next.js application?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;To answer this question, I built a comparison project testing &lt;strong&gt;different navigation strategies&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Standard HTML navigation: Traditional anchor tags. &lt;a href="https://view-transition-comparison.vercel.app/standard" rel="noopener noreferrer"&gt;Experiment link&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js SPA navigation: Client-side routing. &lt;a href="https://view-transition-comparison.vercel.app/spa" rel="noopener noreferrer"&gt;Experiment link&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Speculation Rules + CSS View Transitions: &lt;a href="https://view-transition-comparison.vercel.app/speculation-view-transitions" rel="noopener noreferrer"&gt;Experiment link&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Speculation Rules + Next.js Loading UI: &lt;a href="https://view-transition-comparison.vercel.app/speculation-loading" rel="noopener noreferrer"&gt;Experiment link&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Quicklink + CSS View Transitions: &lt;a href="https://view-transition-comparison.vercel.app/quicklink-view-transitions" rel="noopener noreferrer"&gt;Experiment link&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Quicklink + Next.js Loading UI: &lt;a href="https://view-transition-comparison.vercel.app/speculation-loading" rel="noopener noreferrer"&gt;Experiment link&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/ale-grosselle/view-transition-comparison" rel="noopener noreferrer"&gt;https://github.com/ale-grosselle/view-transition-comparison&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Demo homepage&lt;/strong&gt;: &lt;a href="https://view-transition-comparison.vercel.app/" rel="noopener noreferrer"&gt;https://view-transition-comparison.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Test Case Setup
&lt;/h2&gt;

&lt;p&gt;The experiment simulates a real-world e-commerce scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;listing page&lt;/strong&gt; displaying multiple products&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product detail pages&lt;/strong&gt; that load when clicking an item&lt;/li&gt;
&lt;li&gt;Products stored locally with a simulated &lt;strong&gt;500ms loading delay&lt;/strong&gt; to make differences visible. You can find the data layer implementation here: &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/blob/main/src/lib/api.ts" rel="noopener noreferrer"&gt;&lt;code&gt;src/lib/api.ts&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How I Measured Performance
&lt;/h2&gt;

&lt;p&gt;Beyond "my personal" visual observation, I used three key metrics to evaluate user experience:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. &lt;strong&gt;TTFB (Time To First Byte)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The standard web metric measuring how long it takes to receive the first byte of data after requesting a page.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;strong&gt;Transition Time (Custom Metric)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This measures the actual perceived loading time from the user's perspective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On &lt;code&gt;beforeunload&lt;/code&gt;, we store the current timestamp in a cookie (&lt;a href="https://github.com/ale-grosselle/view-transition-comparison/blob/main/src/app/layout.tsx#L18" rel="noopener noreferrer"&gt;&lt;code&gt;src/app/layout.tsx&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;On the new page load, we calculate: &lt;code&gt;Date.now() - unloadTime&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implementation: &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/blob/main/src/components/TransitionTracker.tsx#L30" rel="noopener noreferrer"&gt;&lt;code&gt;src/components/TransitionTracker.tsx&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. &lt;strong&gt;Skeleton Load Time (Custom Metric)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For tests using Next.js loading states, this measures navigation continuity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We record when the skeleton/loading component appears (&lt;a href="https://github.com/ale-grosselle/view-transition-comparison/blob/main/src/app/layout.tsx#L18" rel="noopener noreferrer"&gt;&lt;code&gt;app/layout.tsx&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;We calculate: &lt;code&gt;skeletonRenderTime - unloadTime&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This shows how quickly users get visual feedback that navigation is happening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All metrics are displayed&lt;/strong&gt; in the top-right corner of detail pages during testing&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwar2lgo0fnkgjdch2q8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwar2lgo0fnkgjdch2q8.png" alt=" " width="587" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Conditions&lt;/strong&gt;&lt;br&gt;
All measurements were performed with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network: Fast 4G throttling in Chrome DevTools&lt;/li&gt;
&lt;li&gt;CPU: 4x slowdown&lt;/li&gt;
&lt;li&gt;Browser Cache: Enabled&lt;/li&gt;
&lt;li&gt;5 runs per strategy for statistical validity&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Results &amp;amp; Analysis
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Strategy 1: Standard HTML Navigation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://view-transition-comparison.vercel.app/standard" rel="noopener noreferrer"&gt;&lt;strong&gt;📺 Demo&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/tree/main/src/app/standard" rel="noopener noreferrer"&gt;&lt;strong&gt;💻 Code&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average Transition Time: 1,555ms
Average TTFB: 37.6ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is our baseline: traditional full-page reloads. &lt;br&gt;
Nothing fancy, but it's the most reliable approach when dealing with complex external scripts, analytics, and third-party widgets.&lt;/p&gt;


&lt;h3&gt;
  
  
  Strategy 2: Next.js SPA Navigation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://view-transition-comparison.vercel.app/spa" rel="noopener noreferrer"&gt;&lt;strong&gt;📺 Demo&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/tree/main/src/app/spa" rel="noopener noreferrer"&gt;&lt;strong&gt;💻 Code&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No page reload - instant content updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The smoothest experience by far. Navigation feels instant because we never reload the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important Considerations:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory Leaks:&lt;/strong&gt; In a true SPA, memory leaks accumulate over time. In multi-page apps, each navigation "resets" memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Third-Party Scripts:&lt;/strong&gt; Many external scripts (analytics, widgets, GTM) rely on page load events. These won't fire in a SPA, potentially breaking functionality.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SEO Metrics:&lt;/strong&gt; Tools like Google PageSpeed Insights measure full page loads. SPA navigation bypasses these measurements after the initial load.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Strategy 3: Speculation Rules + CSS View Transitions
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://view-transition-comparison.vercel.app/speculation-view-transitions" rel="noopener noreferrer"&gt;&lt;strong&gt;📺 Demo&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/tree/main/src/app/speculation-view-transitions" rel="noopener noreferrer"&gt;&lt;strong&gt;💻 Code&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speculation Rules: &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/blob/main/src/components/SpeculationRulesScript.tsx" rel="noopener noreferrer"&gt;&lt;code&gt;components/SpeculationRulesScript.tsx&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;View Transitions: &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/blob/main/src/app/view-transition.css" rel="noopener noreferrer"&gt;&lt;code&gt;view-transition.css&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Results (When Prefetched):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average Transition Time: 970ms
Average TTFB: 3.2ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where it gets exciting! &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API" rel="noopener noreferrer"&gt;Speculation Rules&lt;/a&gt; allow the browser to prefetch or prerender pages before users click. &lt;br&gt;
Combined with &lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions" rel="noopener noreferrer"&gt;CSS View Transitions&lt;/a&gt;, you get smooth, animated page changes.&lt;/p&gt;

&lt;p&gt;I used &lt;strong&gt;prefetch&lt;/strong&gt; with "moderate" eagerness rather than prerender. While prerender is more aggressive and faster, it loads entire pages in the background, which can be resource-intensive.&lt;/p&gt;

&lt;p&gt;You can see the full implementation in &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/blob/main/src/components/SpeculationRulesScript.tsx" rel="noopener noreferrer"&gt;&lt;code&gt;components/SpeculationRulesScript.tsx&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Experience:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When prefetched&lt;/strong&gt;: Near-instant with beautiful transitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When not prefetched&lt;/strong&gt;: Still smooth with nice animations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Browser Support:&lt;/strong&gt; Chrome 121+ and Edge (limited)&lt;/p&gt;

&lt;p&gt;You can verify prefetching in Chrome DevTools: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnw4qpne6g1mj7h60yrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnw4qpne6g1mj7h60yrj.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Strategy 4: Speculation Rules + Next.js Loading UI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://view-transition-comparison.vercel.app/speculation-loading" rel="noopener noreferrer"&gt;&lt;strong&gt;📺 Demo&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/tree/main/src/app/speculation-loading" rel="noopener noreferrer"&gt;&lt;strong&gt;💻 Code&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results (When Prefetched):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average Transition Time: 989ms
Average Skeleton Load: 127ms
Average TTFB: 3.4ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This combines Speculation Rules with &lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/loading" rel="noopener noreferrer"&gt;Next.js Loading UI&lt;/a&gt;, showing skeleton screens while content loads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Skeleton Load metric is crucial here&lt;/strong&gt;, it shows navigation happens within ~127ms, giving users immediate feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Experience:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When prefetched&lt;/strong&gt;: Extremely smooth with instant skeleton feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When not prefetched&lt;/strong&gt;: Still responsive, skeleton appears quickly (&amp;lt;300ms)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My personal favorite for real-world applications.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Strategy 5 &amp;amp; 6: Quicklink Library
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://view-transition-comparison.vercel.app/quicklink-view-transitions" rel="noopener noreferrer"&gt;&lt;strong&gt;📺 View Transitions Demo&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://view-transition-comparison.vercel.app/quicklink-loading" rel="noopener noreferrer"&gt;&lt;strong&gt;📺 Loading UI Demo&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/ale-grosselle/view-transition-comparison/tree/main/src/components/Quicklink.tsx" rel="noopener noreferrer"&gt;&lt;strong&gt;💻 Code&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleChromeLabs/quicklink" rel="noopener noreferrer"&gt;Quicklink&lt;/a&gt; is a library that prefetches links when they enter the viewport. I tested it as a cross-browser alternative to Speculation Rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results (Quicklink + View Transitions):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average Transition Time: 1,627ms
Average TTFB: 33.4ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Results (Quicklink + Loading UI):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average Transition Time: 811ms
Average Skeleton Load: 320ms
Average TTFB: 37.4ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; While &lt;a href="https://github.com/GoogleChromeLabs/quicklink" rel="noopener noreferrer"&gt;Quicklink&lt;/a&gt; has better browser support, &lt;strong&gt;it didn't match Speculation Rules' performance&lt;/strong&gt; in my tests. &lt;br&gt;
The loading UI variant performed better than view transitions, but still showed higher skeleton load times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible reasons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration may need optimization&lt;/li&gt;
&lt;li&gt;Viewport-based prefetching vs. browser-native speculation&lt;/li&gt;
&lt;li&gt;Additional JavaScript bundle overhead&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎬 Conclusion &amp;amp; Recommendations
&lt;/h2&gt;

&lt;p&gt;After extensive testing with real-world metrics, here's what I learned about page navigation in Next.js:&lt;/p&gt;

&lt;h3&gt;
  
  
  My Recommendation Hierarchy
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🥇 Speculation Rules + Loading UI&lt;/strong&gt; - For Chrome/Edge users with Next.js, this is the winner. You get dramatic performance improvements with native browser support and clear user feedback through skeleton screens. This is my personal favorite for production applications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🥈 Next.js SPA Navigation&lt;/strong&gt; - Perfect for simple, controlled flows like thank you pages, wizards, or internal tools. Provides instant transitions and the smoothest experience, but watch out for memory leaks in complex apps and avoid if you're using external scripts or tracking pixels.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🥉 Speculation Rules + View Transitions&lt;/strong&gt; - Deliver a smooth, polished, app-like experience with elegant animations. &lt;strong&gt;If you’re not using Next.js, this is probably the best option&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standard HTML Navigation&lt;/strong&gt; - Your fallback for maximum compatibility and when dealing with complex third-party integrations. Sometimes the traditional approach is the most reliable.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The web platform is evolving rapidly, and if your analytics show significant Chrome/Edge usage, these modern navigation strategies can transform your user experience with surprisingly little code.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Try It Yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://view-transition-comparison.vercel.app/" rel="noopener noreferrer"&gt;https://view-transition-comparison.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/ale-grosselle/view-transition-comparison" rel="noopener noreferrer"&gt;https://github.com/ale-grosselle/view-transition-comparison&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I encourage you to test each strategy with your own use cases. Use Chrome DevTools' network throttling (Fast 4G) and CPU slowdown (4x) to see the differences clearly.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your preferred navigation strategy? Have you experimented with Speculation Rules or View Transitions?&lt;/strong&gt; Share your experiences in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/getting-started/linking-and-navigating" rel="noopener noreferrer"&gt;Next.js: Linking and Navigating&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API" rel="noopener noreferrer"&gt;Speculation Rules API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions" rel="noopener noreferrer"&gt;CSS View Transitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/loading" rel="noopener noreferrer"&gt;Next.js Loading UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleChromeLabs/quicklink" rel="noopener noreferrer"&gt;Quicklink Library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Testing environment: Fast 4G network, 4x CPU throttling, Chrome DevTools, cache enabled. All measurements represent averages of 5 runs per strategy.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>ux</category>
    </item>
    <item>
      <title>Step-by-Step Guide to Capture Heap Snapshots in Node.js on Kubernetes</title>
      <dc:creator>Alessandro Grosselle</dc:creator>
      <pubDate>Tue, 21 Oct 2025 19:59:16 +0000</pubDate>
      <link>https://forem.com/alessandro-grosselle/step-by-step-guide-to-capture-heap-snapshots-in-nodejs-on-kubernetes-1i2h</link>
      <guid>https://forem.com/alessandro-grosselle/step-by-step-guide-to-capture-heap-snapshots-in-nodejs-on-kubernetes-1i2h</guid>
      <description>&lt;p&gt;Memory leaks are a common issue in &lt;strong&gt;Node.js&lt;/strong&gt; applications, especially when running inside &lt;strong&gt;containerized environments like Kubernetes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A memory leak occurs when an application keeps allocating memory without releasing it, leading to increased memory usage and eventually to container restarts or crashes.&lt;/p&gt;

&lt;p&gt;Here’s a typical graph of what a memory leak looks like: memory usage constantly increasing over time until the pod is restarted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F920pztmhz5eknnmvb0l8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F920pztmhz5eknnmvb0l8.png" alt=" " width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Common causes of memory leaks in Node.js
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Unclosed Redis or database connections&lt;/li&gt;
&lt;li&gt;Global variables accumulating data&lt;/li&gt;
&lt;li&gt;Event listeners that are never removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: memory leaks happen more often than you think, especially in complex services.&lt;/p&gt;

&lt;p&gt;And the tricky part? You won’t always see them in your local environment, since production traffic and timeouts behave differently.&lt;/p&gt;

&lt;p&gt;In our case, for example, the leak was caused by too many requests to an external API with &lt;strong&gt;a very long default timeout&lt;/strong&gt;, resulting in pending requests piling up in memory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Always define your own timeout! Never rely on external providers’ default timeouts.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step-by-Step Guide to Access Heap Snapshots in Node.js on Kubernetes
&lt;/h2&gt;

&lt;p&gt;Let’s go through a practical process to capture and inspect memory data from a Node.js application running inside a Kubernetes pod.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect to the Kubernetes Pod
&lt;/h3&gt;

&lt;p&gt;First, find the pod you want to debug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods | &lt;span class="nb"&gt;grep&lt;/span&gt; &amp;lt;your-app-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check which process ID is running Node.js (our case, it’s &lt;code&gt;1&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;your-app-name-64dcf6b84d-nx5cl&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; ps aux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, send the &lt;code&gt;SIGUSR1&lt;/code&gt; signal to that process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;your-app-name-64dcf6b84d-nx5cl&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-SIGUSR1&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Node.js, the &lt;code&gt;SIGUSR1&lt;/code&gt; signal has an &lt;em&gt;official purpose&lt;/em&gt;: it enables the &lt;strong&gt;Node.js inspector&lt;/strong&gt; for debugging a running process.&lt;br&gt;
Next, forward the debugging port from the pod to your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward &amp;lt;your-app-name-64dcf6b84d-nx5cl&amp;gt; 9229:9229
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3u6p3fo3hv0aenh3m5ef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3u6p3fo3hv0aenh3m5ef.png" alt=" " width="452" height="53"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect the Chrome DevTools Debugger
&lt;/h3&gt;

&lt;p&gt;Open Chrome and navigate to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome://inspect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then click &lt;strong&gt;“Open dedicated DevTools for Node”&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6zhh0bejv78bozhgysq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6zhh0bejv78bozhgysq.png" alt=" " width="417" height="176"&gt;&lt;/a&gt;&lt;br&gt;
In the &lt;strong&gt;Connections&lt;/strong&gt; panel, click &lt;em&gt;Add connection&lt;/em&gt; and enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost:9229
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk55lv59523g5nblzobws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk55lv59523g5nblzobws.png" alt=" " width="653" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you’re connected to your Node.js process &lt;strong&gt;running inside the Kubernetes pod&lt;/strong&gt;. You should even see your application logs appearing in the DevTools console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Take and Compare Heap Snapshots
&lt;/h3&gt;

&lt;p&gt;Go to the &lt;strong&gt;“Memory”&lt;/strong&gt; tab in DevTools and click &lt;strong&gt;“Take Heap Snapshot”&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Wait for the snapshot to generate; this shows all memory allocations in your app. If a leak exists, you’ll notice that memory usage keeps growing between snapshots.&lt;br&gt;
To confirm, take a &lt;strong&gt;second snapshot&lt;/strong&gt; after some time (say 10 minutes) and switch to the &lt;strong&gt;“Comparison”&lt;/strong&gt; view. Select the first snapshot as the baseline.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw78m7au5phg208f0law8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw78m7au5phg208f0law8.png" alt=" " width="677" height="231"&gt;&lt;/a&gt;&lt;br&gt;
This will show which objects were allocated between the two snapshots, and whether any aren’t being released.&lt;/p&gt;

&lt;p&gt;Sometimes, your pod might restart before the debugger finishes sending data over the WebSocket, preventing you from downloading the snapshot.&lt;br&gt;
That’s exactly what happened to me when I clicked "Take snapshot":&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu72j6iqhb30o5e4c2ng6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu72j6iqhb30o5e4c2ng6.png" alt=" " width="503" height="26"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s a simple trick: expose a &lt;strong&gt;custom endpoint&lt;/strong&gt; in your app to create and save a heap snapshot programmatically.&lt;br&gt;
This example shows an Express.js endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Heap snapshot endpoint for memory profiling&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/heap-snapshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;:.&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/tmp/heap-snapshot-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.heapsnapshot`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;w&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HeapProfiler.addHeapSnapshotChunk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HeapProfiler.takeHeapSnapshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Heap snapshot created: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;LABEL_REQUESTID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Heap snapshot created successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getErrorMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown error occurred&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Failed to create heap snapshot: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;LABEL_REQUESTID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HTTPStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InternalServerError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to create heap snapshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate the snapshot, calling the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;your-app-name-64dcf6b84d-nx5cl&amp;gt; wget http://localhost:&amp;lt;your-app-port&amp;gt;/heap-snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, copy the snapshot from the pod to your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;cp&lt;/span&gt; &amp;lt;your-app-name-64dcf6b84d-nx5cl&amp;gt;:/tmp/heap-snapshot-2025-10-19T15-19-34-391Z.heapsnapshot ./heap-snapshot.heapsnapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can open this &lt;code&gt;.heapsnapshot&lt;/code&gt; file in Chrome DevTools later and analyze it offline, clicking "Loading profile":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8uvu5prfwwe4b2u4h53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8uvu5prfwwe4b2u4h53.png" alt=" " width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And that’s it! This workflow gives you a &lt;strong&gt;practical and reproducible method&lt;/strong&gt; to detect memory leaks in Node.js applications running on Kubernetes.&lt;/p&gt;

&lt;p&gt;It’s saved me a lot of time, hopefully, it’ll help you too.&lt;/p&gt;

&lt;p&gt;Thanks for reading!  &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>kubernetes</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>The CSS Ordering Quiz That Will Break Your Next.js Assumptions</title>
      <dc:creator>Alessandro Grosselle</dc:creator>
      <pubDate>Tue, 30 Sep 2025 13:30:12 +0000</pubDate>
      <link>https://forem.com/alessandro-grosselle/the-css-ordering-quiz-that-will-break-your-nextjs-assumptions-a0m</link>
      <guid>https://forem.com/alessandro-grosselle/the-css-ordering-quiz-that-will-break-your-nextjs-assumptions-a0m</guid>
      <description>&lt;p&gt;&lt;em&gt;Can you predict how Next.js handles CSS import order? This interactive quiz reveals a hidden behavior that might surprise you.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 For the Impatient
&lt;/h2&gt;

&lt;p&gt;Want to see the magic immediately? Here are all the links:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 Repository&lt;/strong&gt;: &lt;a href="https://github.com/ale-grosselle/css-order-next.js" rel="noopener noreferrer"&gt;https://github.com/ale-grosselle/css-order-next.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧪 Live Test Cases&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://css-order-next-js.vercel.app/case-a" rel="noopener noreferrer"&gt;Case A&lt;/a&gt; - Basic CSS ordering&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://css-order-next-js.vercel.app/case-b" rel="noopener noreferrer"&gt;Case B&lt;/a&gt; - Server component CSS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://css-order-next-js.vercel.app/case-c" rel="noopener noreferrer"&gt;Case C&lt;/a&gt; - Client component follows rules&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://css-order-next-js.vercel.app/case-d" rel="noopener noreferrer"&gt;Case D&lt;/a&gt; - &lt;strong&gt;The mind-bender!&lt;/strong&gt; 🤯&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🕵️‍♂️ Pro Tip&lt;/strong&gt;: Open your browser's DevTools (F12 or right-click → Inspect) and check the &lt;strong&gt;Elements&lt;/strong&gt; panel to see the actual CSS order loaded for each case. This is crucial for understanding how Next.js handles CSS imports!&lt;/p&gt;




&lt;p&gt;We all know the golden rule from the Next.js documentation: &lt;strong&gt;&lt;a href="https://nextjs.org/docs/app/getting-started/css#ordering-and-merging" rel="noopener noreferrer"&gt;"CSS import order matters"&lt;/a&gt;&lt;/strong&gt;. It's a fundamental principle we've relied on for years. But how well do you really understand CSS ordering in Next.js?&lt;/p&gt;

&lt;p&gt;Let's put your knowledge to the test with a hands-on quiz that reveals some surprising behavior you probably didn't know about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup: Our CSS Ordering Laboratory
&lt;/h2&gt;

&lt;p&gt;I've created a simple Next.js project to test CSS import behavior. Here's what we're working with:&lt;/p&gt;

&lt;h3&gt;
  
  
  Base Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Layout Component (&lt;code&gt;layout.tsx&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Global Styles (&lt;code&gt;globals.css&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.foo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--layout-file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--layout-file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This sets all &lt;code&gt;.foo&lt;/code&gt; divs to red by default.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Test Stylesheets
&lt;/h3&gt;

&lt;p&gt;We have several CSS files that all target the same element with different colors:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;1.global.css&lt;/code&gt;&lt;/strong&gt; - Makes divs yellow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.foo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--1-global-page-css&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--1-global-page-css&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;0.module.css&lt;/code&gt;&lt;/strong&gt; - CSS Module for brown divs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.body-module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--0--page--css--module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;brown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--0--page--css--module&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Server Component CSS (&lt;code&gt;components/2.css&lt;/code&gt;)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.foo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--2-rsc-component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;orange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--2-rsc-component&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Client Component CSS (&lt;code&gt;components/3.css&lt;/code&gt;)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.foo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--3-client-component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--3-client-component&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's see how well you can predict CSS ordering behavior!&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Quiz Time: Can You Guess the Color?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Case A: The Baseline Test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🌐 &lt;a href="https://css-order-next-js.vercel.app/case-a" rel="noopener noreferrer"&gt;Try Case A Live&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// case-a/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../1.global.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../0.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body-module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt; foo`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;HELLO&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🤔 Your Prediction&lt;/strong&gt;: What background color will the div have?&lt;/p&gt;

&lt;p&gt;A) Red (from &lt;code&gt;globals.css&lt;/code&gt;)&lt;br&gt;
B) Yellow (from &lt;code&gt;1.global.css&lt;/code&gt;)&lt;br&gt;
C) Brown (from &lt;code&gt;0.module.css&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Correct Answer: C) Brown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt;: Layout CSS (&lt;code&gt;globals.css&lt;/code&gt;) is loaded first in a separate bundle. Within the page bundle, &lt;code&gt;0.module.css&lt;/code&gt; is imported LAST, so at equal specificity it wins over both &lt;code&gt;globals.css&lt;/code&gt; and &lt;code&gt;1.global.css&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fksx35u15c7hrzbhew8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fksx35u15c7hrzbhew8.png" alt=" " width="679" height="282"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Case B: Adding a Server Component
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🌐 &lt;a href="https://css-order-next-js.vercel.app/case-b" rel="noopener noreferrer"&gt;Try Case B Live&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// case-b/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../1.global.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../0.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/Example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Server component with CSS&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body-module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt; foo`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;HELLO&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Example&lt;/code&gt; component imports &lt;code&gt;./2.css&lt;/code&gt; which makes &lt;code&gt;.foo&lt;/code&gt; divs orange.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🤔 Your Prediction&lt;/strong&gt;: What color will the div be now?&lt;/p&gt;

&lt;p&gt;A) Red (from &lt;code&gt;globals.css&lt;/code&gt;)&lt;br&gt;
B) Brown (from &lt;code&gt;0.module.css&lt;/code&gt;)&lt;br&gt;
C) Orange (from server component &lt;code&gt;2.css&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Correct Answer: C) Orange&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaaj7b7obc31r9qjwwvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaaj7b7obc31r9qjwwvw.png" alt=" " width="675" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt;: Server component CSS follows import order. The &lt;code&gt;Example&lt;/code&gt; component is imported LAST, so its CSS comes after everything else and wins.&lt;/p&gt;


&lt;h3&gt;
  
  
  Case C: Still Following the Rules
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🌐 &lt;a href="https://css-order-next-js.vercel.app/case-c" rel="noopener noreferrer"&gt;Try Case C Live&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// case-c/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/Example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../1.global.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../0.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExampleClientComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/Example-client-component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Client component!&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body-module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt; foo`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;HELLO&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ExampleClientComponent&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Example&lt;/code&gt; (server component with orange CSS) imported FIRST&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1.global.css&lt;/code&gt; (yellow) imported SECOND&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0.module.css&lt;/code&gt; (brown) imported THIRD&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ExampleClientComponent&lt;/code&gt; (client component with black CSS) imported LAST&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🤔 Your Prediction&lt;/strong&gt;: Based on import order, what color should win?&lt;/p&gt;

&lt;p&gt;A) Orange (server component imported first)&lt;br&gt;
B) Brown (from &lt;code&gt;0.module.css&lt;/code&gt; imported third)&lt;br&gt;
C) Black (client component imported last)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Expected Answer: C) Black&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45b2kpku94n8jumwqc51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45b2kpku94n8jumwqc51.png" alt=" " width="679" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt;: Following the "last import wins" rule, since &lt;code&gt;ExampleClientComponent&lt;/code&gt; is imported last, its CSS should come last and win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📊 Expected CSS Loading Order:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Layout Bundle&lt;/strong&gt;: &lt;code&gt;globals.css&lt;/code&gt; (red)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Bundle&lt;/strong&gt;: &lt;code&gt;components/2.css&lt;/code&gt; (orange) → &lt;code&gt;1.global.css&lt;/code&gt; (yellow) → &lt;code&gt;0.module.css&lt;/code&gt; (brown) → &lt;code&gt;components/3.css&lt;/code&gt; (black) ← Should win&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;And indeed, the div IS black!&lt;/strong&gt; The rule still holds...&lt;/p&gt;


&lt;h3&gt;
  
  
  Case D: The Mind-Bender That Breaks Everything
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🌐 &lt;a href="https://css-order-next-js.vercel.app/case-d" rel="noopener noreferrer"&gt;Try Case D Live&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's try one more test to confirm our suspicions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// case-d/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExampleClientComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/Example-client-component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// FIRST&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/Example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../1.global.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// THIRD&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../0.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// LAST CSS import&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body-module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt; foo`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;HELLO&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ExampleClientComponent&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the client component is imported FIRST, and &lt;code&gt;0.module.css&lt;/code&gt; is imported LAST.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🤔 Final Prediction&lt;/strong&gt;: Based on the "last import wins" rule, what color should the div be?&lt;/p&gt;

&lt;p&gt;A) Black (client component imported first)&lt;br&gt;
B) Brown (from &lt;code&gt;0.module.css&lt;/code&gt; imported last)&lt;br&gt;
C) Orange (from server component)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🤯 Answer: Still Black!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13l60895rdyv2n6mqgtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13l60895rdyv2n6mqgtf.png" alt=" " width="677" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Hidden Truth Revealed&lt;/strong&gt;: Even though &lt;code&gt;0.module.css&lt;/code&gt; is imported LAST and should win according to the fundamental CSS ordering rule, the client component's CSS still wins!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📊 Expected CSS Loading Order:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Layout Bundle&lt;/strong&gt;: &lt;code&gt;globals.css&lt;/code&gt; (red)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Bundle&lt;/strong&gt;: &lt;code&gt;components/3.css&lt;/code&gt; (black - imported first) → &lt;code&gt;components/2.css&lt;/code&gt; (orange) → &lt;code&gt;1.global.css&lt;/code&gt; (yellow) → &lt;code&gt;0.module.css&lt;/code&gt; (brown) ← Should win!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;📊 Actual CSS Loading Order:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Layout Bundle&lt;/strong&gt;: &lt;code&gt;globals.css&lt;/code&gt; (red)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Bundle&lt;/strong&gt;: &lt;code&gt;components/2.css&lt;/code&gt; (orange) → &lt;code&gt;1.global.css&lt;/code&gt; (yellow) → &lt;code&gt;0.module.css&lt;/code&gt; (brown)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Bundle&lt;/strong&gt;: &lt;code&gt;components/3.css&lt;/code&gt; (black) ← Always wins!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;This completely breaks the "last import wins" rule!&lt;/strong&gt; Client component CSS gets moved to the end regardless of where you import it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 The Discovery: Next.js Has a unclear CSS Rule
&lt;/h2&gt;

&lt;p&gt;What I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Layout CSS is always loaded first (separate bundle)&lt;/li&gt;
&lt;li&gt;✅ Server component CSS follows import order within the page&lt;/li&gt;
&lt;li&gt;❌ Client component CSS &lt;strong&gt;always loads after server component CSS&lt;/strong&gt;, regardless of import order. &lt;/li&gt;
&lt;li&gt;✅ Among client components, import order between multiple client components is still respected&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚨 MUST-Follow Rules to Avoid CSS Chaos
&lt;/h2&gt;

&lt;p&gt;After discovering this hidden behavior, the &lt;a href="https://nextjs.org/docs/app/getting-started/css#ordering-and-merging" rel="noopener noreferrer"&gt;Next.js CSS recommendations&lt;/a&gt; become &lt;strong&gt;absolute requirements&lt;/strong&gt;, not just suggestions:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Use CSS Modules for ALL Component-Specific Styles&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ DO: Always use CSS Modules for components&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./MyComponent.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ DON'T: Never use global CSS in components&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./MyComponent.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This creates ordering conflicts!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this is critical&lt;/strong&gt;: CSS Modules provide scoping that prevents the CSS ordering conflicts we discovered. Your styles won't accidentally override or be overridden by other components.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Import CSS Only Once Per Component&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ DO: One CSS import per component&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Button.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Click&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ DON'T: Multiple CSS imports in one component&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./theme.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./button.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./overrides.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unpredictable ordering!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Multiple CSS imports create dependency chains that behave differently for server vs client components.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Define Global CSS ONLY in Layout&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ DO: Only in layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tailwind.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// If using Tailwind&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ DON'T: Global CSS anywhere else&lt;/span&gt;
&lt;span class="c1"&gt;// pages/home.tsx - NEVER do this!&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This breaks predictability&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this is essential&lt;/strong&gt;: Global CSS should be loaded once and consistently. Importing it multiple places creates the exact ordering conflicts we discovered.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Did this quiz surprise you? Have you encountered similar CSS ordering mysteries in your Next.js projects? Share your experiences in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>css</category>
    </item>
  </channel>
</rss>
