<?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>DEV Community: Thomas T</title>
    <description>The latest articles on DEV Community by Thomas T (@thomastepi).</description>
    <link>https://dev.to/thomastepi</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%2F3290989%2F713f0297-bd8e-42fc-bb08-e137b8e065f5.png</url>
      <title>DEV Community: Thomas T</title>
      <link>https://dev.to/thomastepi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thomastepi"/>
    <language>en</language>
    <item>
      <title>Migrating My Portfolio from CRA to Next.js App Router: Lessons Learned</title>
      <dc:creator>Thomas T</dc:creator>
      <pubDate>Tue, 24 Jun 2025 18:25:23 +0000</pubDate>
      <link>https://dev.to/thomastepi/migrating-my-portfolio-from-cra-to-nextjs-app-router-lessons-learned-1o52</link>
      <guid>https://dev.to/thomastepi/migrating-my-portfolio-from-cra-to-nextjs-app-router-lessons-learned-1o52</guid>
      <description>&lt;p&gt;As part of improving my personal portfolio site, I decided to migrate it from &lt;strong&gt;Create React App (CRA)&lt;/strong&gt; to the latest &lt;strong&gt;Next.js App Router&lt;/strong&gt;. While the overall benefits of the App Router are clear (including better SEO, file-based layouts, and built-in server-side rendering), the migration process introduced several non-trivial challenges.&lt;/p&gt;

&lt;p&gt;This article documents the specific hurdles I faced and how I resolved them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Hydration Mismatch from Chakra UI
&lt;/h2&gt;

&lt;p&gt;After integrating Chakra UI with the App Router, I encountered this warning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning: Text content did not match. Server: “undefined” Client: “light”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This occurred because Chakra UI dynamically injects a &lt;code&gt;data-theme&lt;/code&gt; attribute on the client, which does not exist on the server-rendered HTML. This difference triggered a hydration mismatch warning from React.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolution
&lt;/h3&gt;

&lt;p&gt;I added the &lt;code&gt;suppressHydrationWarning&lt;/code&gt; attribute to the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag to suppress this warning in non-critical areas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;suppressHydrationWarning&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Layout Flicker Between Mobile and Desktop Header
&lt;/h2&gt;

&lt;p&gt;Upon loading, the header would briefly display the mobile layout (even on desktop) before correcting itself. This issue was caused by Chakra UI’s &lt;code&gt;useBreakpointValue&lt;/code&gt;, which runs only on the client and is undefined during server-side rendering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolution
&lt;/h3&gt;

&lt;p&gt;I delayed rendering the header until after the component mounted.&lt;br&gt;
This prevented layout shifts by ensuring the component only renders once the viewport width is available on the client. While this approach worked for my small portfolio site, it’s not ideal for larger applications. It serves as a temporary workaround until I find a more permanent and robust solution that handles responsive behavior more gracefully in a server-rendered environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hasMounted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHasMounted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="nf"&gt;useEffect&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="nf"&gt;setHasMounted&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="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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasMounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Improper Nesting of &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; Tags in Multiple Layouts
&lt;/h2&gt;

&lt;p&gt;While structuring localized routes using &lt;code&gt;app/[lng]/layout.js&lt;/code&gt;, I mistakenly returned a full HTML structure (including &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;) in both the root and localized layout files. This caused several critical errors, including:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error: You are mounting a new html component when a previous one has not first unmounted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In HTML, &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; cannot be a child of &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. This will cause a hydration error.&lt;br&gt;&lt;br&gt;
&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; cannot contain a nested &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Resolution
&lt;/h3&gt;

&lt;p&gt;Only the root &lt;code&gt;app/layout.js&lt;/code&gt; should return the full HTML document structure. Nested layouts must return just the content portion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In app/layout.js&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// In app/[lng]/layout.js&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// No html/body here&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Sticky Header Not Working Due to Global CSS
&lt;/h2&gt;

&lt;p&gt;Although my header component used &lt;code&gt;position="sticky"&lt;/code&gt; with Chakra UI, it failed to remain fixed during scroll. This behavior was inconsistent with how it worked in my previous CRA-based setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;globals.css&lt;/code&gt; generated by &lt;code&gt;create-next-app&lt;/code&gt; included the following rule:&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;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&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;While this was intended to prevent horizontal scrolling, it unintentionally interfered with &lt;code&gt;position: sticky&lt;/code&gt; by affecting the scroll context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolution
&lt;/h3&gt;

&lt;p&gt;Replacing &lt;code&gt;overflow-x: hidden&lt;/code&gt; with the safer &lt;code&gt;overflow-x: clip&lt;/code&gt; resolved the issue:&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;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clip&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Migrating to Next.js with the App Router revealed several differences in rendering behavior compared to Create React App (CRA). Issues related to hydration, responsive layout handling, global styles, and layout structuring required careful debugging and thoughtful adjustments to the application's architecture.&lt;/p&gt;

&lt;p&gt;Despite the hurdles, the migration was worthwhile for the benefits it brings in scalability, routing flexibility, and performance optimization. For anyone undertaking a similar migration, I recommend paying close attention to hydration warnings, how responsive logic behaves in server-rendered apps, and the broader impact of global CSS on layout behavior.&lt;/p&gt;

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