<?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: abhilashlr</title>
    <description>The latest articles on DEV Community by abhilashlr (@abhilashlr).</description>
    <link>https://dev.to/abhilashlr</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%2F340347%2Ff47b0aa8-aa20-4935-98e6-b8a112d66dc8.jpg</url>
      <title>DEV Community: abhilashlr</title>
      <link>https://dev.to/abhilashlr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhilashlr"/>
    <language>en</language>
    <item>
      <title>Stop Hardcoding Dashboards: Build JSON-Driven Analytics Widgets Instead</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Sat, 07 Mar 2026 06:10:41 +0000</pubDate>
      <link>https://dev.to/abhilashlr/stop-hardcoding-dashboards-build-json-driven-analytics-widgets-instead-4bm7</link>
      <guid>https://dev.to/abhilashlr/stop-hardcoding-dashboards-build-json-driven-analytics-widgets-instead-4bm7</guid>
      <description>&lt;p&gt;Analytics dashboards usually start simple.&lt;/p&gt;

&lt;p&gt;A chart here. A table there. Maybe a couple of metrics.&lt;/p&gt;

&lt;p&gt;But over time, dashboards grow into something far more complex:&lt;br&gt;
different widget types, customizable titles and descriptions, layout&lt;br&gt;
rules, conditional rendering, and feature flags.&lt;/p&gt;

&lt;p&gt;If each widget is hardcoded in the UI, the dashboard slowly turns into a&lt;br&gt;
collection of one-off components that are difficult to maintain.&lt;/p&gt;

&lt;p&gt;Recently, we solved this problem by building an analytics widget system&lt;br&gt;
that combines &lt;strong&gt;composable React components with a JSON-driven&lt;br&gt;
configuration layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The result: dashboards where widgets are &lt;strong&gt;defined by configuration&lt;br&gt;
instead of hardcoded UI&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚠️ The Problem with Hardcoded Dashboards
&lt;/h2&gt;

&lt;p&gt;Many dashboards begin with a straightforward implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&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="nc"&gt;RevenueChart&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="nc"&gt;ActiveUsersChart&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="nc"&gt;ErrorRateWidget&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="nc"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine at first, but the problems appear as dashboards evolve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Every widget requires a dedicated component\&lt;/li&gt;
&lt;li&gt;  Layout and presentation logic get duplicated\&lt;/li&gt;
&lt;li&gt;  Titles and descriptions live inside UI code\&lt;/li&gt;
&lt;li&gt;  Small content changes require deployments\&lt;/li&gt;
&lt;li&gt;  Adding variations leads to component explosion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually, the dashboard becomes tightly coupled to the frontend&lt;br&gt;
codebase.&lt;/p&gt;

&lt;p&gt;We wanted a system where widgets could be &lt;strong&gt;described declaratively&lt;br&gt;
instead of implemented manually&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  💡 The Core Idea: Configuration Over Components
&lt;/h2&gt;

&lt;p&gt;Instead of hardcoding widgets in React, we describe them using a &lt;strong&gt;JSON&lt;br&gt;
configuration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api_error_rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"API Error Rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Error percentage in the last 24 hours"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"line-chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"errors_over_time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeRange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"24h"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration contains everything required to render a widget.&lt;/p&gt;

&lt;p&gt;The UI layer simply &lt;strong&gt;interprets the configuration and renders the&lt;br&gt;
correct component&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This creates a clear separation:&lt;/p&gt;

&lt;p&gt;Configuration → &lt;strong&gt;what should be shown&lt;/strong&gt;\&lt;br&gt;
Components → &lt;strong&gt;how it should be rendered&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🧩 The Widget Renderer
&lt;/h2&gt;

&lt;p&gt;At runtime, a renderer maps the configuration to the appropriate&lt;br&gt;
component.&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;function&lt;/span&gt; &lt;span class="nf"&gt;WidgetRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;config&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;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;widgetRegistry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WidgetContainer&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;Component&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;WidgetContainer&lt;/span&gt;&lt;span class="p"&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;h2&gt;
  
  
  🗂️ The Widget Registry
&lt;/h2&gt;

&lt;p&gt;To support multiple widget types, we maintain a registry.&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="nx"&gt;widgetRegistry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-chart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LineChartWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar-chart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BarChartWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StatWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TableWidget&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a new widget type becomes straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Create the component\&lt;/li&gt;
&lt;li&gt; Register it in the widget registry&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No changes to the dashboard structure are required.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 A Composable Widget Structure
&lt;/h2&gt;

&lt;p&gt;Every widget follows a consistent layout:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Widget
 ├── Title
 ├── Description
 └── Content (chart / table / stat)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The container component handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  layout and spacing\&lt;/li&gt;
&lt;li&gt;  titles and descriptions\&lt;/li&gt;
&lt;li&gt;  loading states\&lt;/li&gt;
&lt;li&gt;  error states\&lt;/li&gt;
&lt;li&gt;  consistent styling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows widgets to focus purely on &lt;strong&gt;data visualization logic&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Before vs After: The Architecture Shift
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Before: Hardcoded Dashboard
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboard
 ├── RevenueChart
 ├── ActiveUsersChart
 ├── ErrorRateWidget
 ├── LatencyChart
 └── Many one-off components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  dashboards tightly coupled to UI code&lt;/li&gt;
&lt;li&gt;  adding widgets required new components&lt;/li&gt;
&lt;li&gt;  layout logic lived inside the page&lt;/li&gt;
&lt;li&gt;  small changes required deployments&lt;/li&gt;
&lt;li&gt;  component trees kept growing&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ✅ After: Configuration-Driven Dashboard
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboard
   │
   ▼
Dashboard Config
   │
   ▼
Grid Layout Engine
   │
   ▼
Widget Renderer
   │
   ▼
Widget Registry
   │
   ├── LineChartWidget
   ├── StatWidget
   ├── TableWidget
   └── BarChartWidget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Widgets are no longer hardcoded --- they are &lt;strong&gt;rendered dynamically from&lt;br&gt;
configuration&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  📦 Dashboard Configuration Example
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"widgets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error_rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"line-chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"API Error Rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Error percentage over time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"errors_over_time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"layout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success_rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Success Rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"valueFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"percentage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"layout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This configuration becomes the &lt;strong&gt;single source of truth&lt;/strong&gt; for the&lt;br&gt;
dashboard.&lt;/p&gt;


&lt;h2&gt;
  
  
  📐 Adding a Layout System
&lt;/h2&gt;

&lt;p&gt;Rendering widgets is only half the challenge. Dashboards also require&lt;br&gt;
flexible layout management.&lt;/p&gt;

&lt;p&gt;To solve this, we integrated &lt;strong&gt;React Grid Layout&lt;/strong&gt;, which provides a&lt;br&gt;
responsive drag‑and‑drop grid system.&lt;/p&gt;

&lt;p&gt;Instead of hardcoding layout in UI code, widget positioning is also&lt;br&gt;
defined in configuration.&lt;/p&gt;


&lt;h2&gt;
  
  
  📐 Grid Layout Configuration
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Layout properties:&lt;/p&gt;

&lt;p&gt;Property   Meaning&lt;/p&gt;



&lt;p&gt;x          horizontal grid position&lt;br&gt;
  y          vertical grid position&lt;br&gt;
  w          widget width in grid units&lt;br&gt;
  h          widget height in grid units&lt;/p&gt;

&lt;p&gt;Separating &lt;strong&gt;widget configuration&lt;/strong&gt; from &lt;strong&gt;layout configuration&lt;/strong&gt; keeps&lt;br&gt;
the system clean and extensible.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧩 Rendering the Grid
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;GridLayout&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;react-grid-layout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;widgets&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;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;widget&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GridLayout&lt;/span&gt; &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;rowHeight&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="si"&gt;}&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;widgets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;widget&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;WidgetRenderer&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="si"&gt;}&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;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GridLayout&lt;/span&gt;&lt;span class="p"&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;This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  configuration-driven layouts&lt;/li&gt;
&lt;li&gt;  flexible widget positioning&lt;/li&gt;
&lt;li&gt;  consistent dashboard structure&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🔒 Adding Type Safety with TypeScript
&lt;/h2&gt;

&lt;p&gt;When dashboards are driven by configuration, &lt;strong&gt;type safety becomes&lt;br&gt;
essential&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Widget Type Definitions
&lt;/h2&gt;

&lt;p&gt;Centralizing widget types avoids registry/config mismatches.&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WidgetTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;LINE_CHART&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-chart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;STAT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;WidgetType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;WidgetTypes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;WidgetTypes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;Every widget shares a common structure.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BaseWidgetConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WidgetType&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GridLayout&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stable IDs allow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  drag‑and‑drop persistence&lt;/li&gt;
&lt;li&gt;  layout tracking&lt;/li&gt;
&lt;li&gt;  widget analytics&lt;/li&gt;
&lt;li&gt;  configuration updates&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Widget‑Specific Configurations
&lt;/h3&gt;

&lt;p&gt;Each widget extends the base configuration.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LineChartWidgetConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BaseWidgetConfig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-chart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;timeRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1h&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;24h&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7d&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;StatWidgetConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BaseWidgetConfig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;valueFormat&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;percentage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Union Type for All Widgets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;WidgetConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;LineChartWidgetConfig&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;StatWidgetConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  invalid configurations fail at compile time&lt;/li&gt;
&lt;li&gt;  editors provide autocomplete&lt;/li&gt;
&lt;li&gt;  widgets remain type-safe as the system grows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Dashboards often start as simple collections of charts but eventually&lt;br&gt;
evolve into complex UI systems.&lt;/p&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;JSON-driven widget configuration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;composable React components&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;a typed widget registry&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;a grid layout system&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;stable widget identities&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we created a dashboard architecture that scales without turning into a&lt;br&gt;
collection of one-off components.&lt;/p&gt;

&lt;p&gt;Widgets are no longer hardcoded UI elements --- they are &lt;strong&gt;structured&lt;br&gt;
configuration interpreted by a flexible UI layer&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;Huge credits to &lt;a href="https://www.linkedin.com/in/swatantramishra5/" rel="noopener noreferrer"&gt;@145_swatantra_mishra_b100&lt;/a&gt; for rolling out a major chunk of this idea to production.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Fri, 30 Jan 2026 06:12:02 +0000</pubDate>
      <link>https://dev.to/abhilashlr/-4398</link>
      <guid>https://dev.to/abhilashlr/-4398</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/abhilashlr" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F340347%2Ff47b0aa8-aa20-4935-98e6-b8a112d66dc8.jpg" alt="abhilashlr"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/abhilashlr/migrating-to-nextjs-16-react-19-a-platform-upgrade-that-paid-off-4j6f" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Migrating to Next.js 16 &amp;amp; React 19: A Platform Upgrade That Paid Off&lt;/h2&gt;
      &lt;h3&gt;abhilashlr ・ Jan 30&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nextjs&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>react</category>
      <category>ai</category>
    </item>
    <item>
      <title>Migrating to Next.js 16 &amp; React 19: A Platform Upgrade That Paid Off</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Fri, 30 Jan 2026 04:59:08 +0000</pubDate>
      <link>https://dev.to/abhilashlr/migrating-to-nextjs-16-react-19-a-platform-upgrade-that-paid-off-4j6f</link>
      <guid>https://dev.to/abhilashlr/migrating-to-nextjs-16-react-19-a-platform-upgrade-that-paid-off-4j6f</guid>
      <description>&lt;p&gt;Framework migrations are often framed as maintenance work — necessary, but unexciting.&lt;/p&gt;

&lt;p&gt;Our recent migration to &lt;strong&gt;Next.js 16 and React 19&lt;/strong&gt; turned out to be something more: a platform upgrade that measurably improved performance, security, stability, and developer velocity — while simplifying how we build and reason about our application.&lt;/p&gt;

&lt;p&gt;This post shares &lt;strong&gt;why we upgraded, what changed technically, and what it now enables&lt;/strong&gt; — both for engineers working on the product and for the users relying on it every day.&lt;/p&gt;

&lt;p&gt;If you are looking for how we phased the upgrade, &lt;a href="https://dev.to/abhilashlr/migrating-a-large-scale-monorepo-from-nextjs-14-to-16-a-real-world-journey-5383"&gt;give this post a read&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Why We Chose to Upgrade
&lt;/h2&gt;

&lt;p&gt;We were on a stable setup, but over time we started seeing familiar signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security advisories accumulating across the React and Next.js ecosystem
&lt;/li&gt;
&lt;li&gt;Performance and rendering improvements landing upstream that we couldn’t leverage
&lt;/li&gt;
&lt;li&gt;Increasing friction adopting modern tooling
&lt;/li&gt;
&lt;li&gt;Legacy patterns sticking around longer than they should
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At a certain point, &lt;em&gt;not upgrading&lt;/em&gt; becomes riskier than upgrading.&lt;/p&gt;

&lt;p&gt;Rather than treating this as a routine version bump, we approached it as a &lt;strong&gt;strategic platform investment&lt;/strong&gt; — one that would reduce friction for the next 12–24 months.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚛️ React 19: Fewer Workarounds, Better Defaults
&lt;/h2&gt;

&lt;p&gt;React 19 doesn’t introduce flashy new APIs for everyday development — and that’s a feature, not a limitation.&lt;/p&gt;

&lt;p&gt;What stood out during the migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More predictable concurrent rendering behavior
&lt;/li&gt;
&lt;li&gt;Cleaner async handling without excessive defensive code
&lt;/li&gt;
&lt;li&gt;Reduced reliance on workaround-heavy patterns around effects and state
&lt;/li&gt;
&lt;li&gt;Better alignment with server-centric application architectures
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, this meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fewer rendering edge cases
&lt;/li&gt;
&lt;li&gt;Cleaner component logic
&lt;/li&gt;
&lt;li&gt;Easier reasoning about data flow under load
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React felt less like something we had to &lt;em&gt;handle carefully&lt;/em&gt; and more like something we could &lt;strong&gt;trust by default&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Next.js 16: Stability, Performance, and Security by Default
&lt;/h2&gt;

&lt;p&gt;Next.js 16 reinforced several principles that matter in real-world production systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔄 Improved Rendering &amp;amp; Data Fetching Semantics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clearer boundaries between server and client components
&lt;/li&gt;
&lt;li&gt;More predictable caching and revalidation behavior
&lt;/li&gt;
&lt;li&gt;Fewer hydration mismatches and rendering inconsistencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ⚡ Faster Development Loops
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Noticeably quicker dev server startup
&lt;/li&gt;
&lt;li&gt;Faster rebuilds during local development
&lt;/li&gt;
&lt;li&gt;Less time waiting, more time iterating
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔐 Security Improvements
&lt;/h3&gt;

&lt;p&gt;A significant class of known vulnerabilities was addressed simply by upgrading — without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom middleware
&lt;/li&gt;
&lt;li&gt;Manual patching
&lt;/li&gt;
&lt;li&gt;Forked dependencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security became a &lt;strong&gt;baseline capability&lt;/strong&gt;, not an afterthought.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Turbopack: From Promising to Production-Ready
&lt;/h2&gt;

&lt;p&gt;One of the most impactful improvements during this migration was &lt;strong&gt;Turbopack&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Earlier versions felt experimental. With Next.js 16, Turbopack has clearly crossed into &lt;strong&gt;mature and production-ready territory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What we observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Significantly faster cold starts in development
&lt;/li&gt;
&lt;li&gt;Near-instant incremental rebuilds
&lt;/li&gt;
&lt;li&gt;Stable behavior across a large, real-world codebase
&lt;/li&gt;
&lt;li&gt;Smooth compatibility with modern Next.js features
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This had a direct effect on day-to-day productivity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster feedback loops
&lt;/li&gt;
&lt;li&gt;Cheaper context switching
&lt;/li&gt;
&lt;li&gt;A lighter, more responsive debugging experience
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turbopack didn’t just improve performance — it &lt;strong&gt;changed the development experience&lt;/strong&gt;, and those gains compound over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Benchmarks: What Changed in Practice
&lt;/h2&gt;

&lt;p&gt;We intentionally avoided synthetic benchmarks and focused on &lt;strong&gt;real workflows and production behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Below are the results we observed after migrating to &lt;strong&gt;Next.js 16, React 19, and Turbopack&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%2Fz7j9xpih47ct3umgqkdz.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%2Fz7j9xpih47ct3umgqkdz.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🛠️ Development Experience
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dev server cold start&lt;/td&gt;
&lt;td&gt;~45–60s&lt;/td&gt;
&lt;td&gt;~8–12s&lt;/td&gt;
&lt;td&gt;~6× faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incremental rebuild (component change)&lt;/td&gt;
&lt;td&gt;2–4s&lt;/td&gt;
&lt;td&gt;&amp;lt;500ms&lt;/td&gt;
&lt;td&gt;Near-instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route change during dev&lt;/td&gt;
&lt;td&gt;Noticeable reload&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;td&gt;Qualitative win&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU usage during dev&lt;/td&gt;
&lt;td&gt;High &amp;amp; spiky&lt;/td&gt;
&lt;td&gt;Lower &amp;amp; stable&lt;/td&gt;
&lt;td&gt;More predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Engineers spend less time waiting and more time iterating. Over the course of a day, this compounds into a meaningful productivity gain.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧪 Build &amp;amp; CI Performance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Production build time&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;~20–30% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI flakiness related to builds&lt;/td&gt;
&lt;td&gt;Occasional&lt;/td&gt;
&lt;td&gt;Near-zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle warnings &amp;amp; edge cases&lt;/td&gt;
&lt;td&gt;Frequent&lt;/td&gt;
&lt;td&gt;Significantly reduced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cleaner builds also made CI logs easier to reason about — an underrated but important improvement.&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%2Fxbq47revnk83ac4xaz0r.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%2Fxbq47revnk83ac4xaz0r.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🌍 Runtime &amp;amp; User-Facing Performance
&lt;/h3&gt;

&lt;p&gt;User-visible gains were incremental rather than dramatic, but &lt;strong&gt;consistent and measurable&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster initial page loads, especially on server-rendered routes
&lt;/li&gt;
&lt;li&gt;Smoother client-side navigation
&lt;/li&gt;
&lt;li&gt;Reduced hydration-related warnings and mismatches
&lt;/li&gt;
&lt;li&gt;Improved responsiveness under concurrent usage
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than chasing headline numbers, the real win was &lt;strong&gt;predictability and consistency&lt;/strong&gt; at scale.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧯 Stability &amp;amp; Reliability
&lt;/h3&gt;

&lt;p&gt;Post-migration, we observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A noticeable drop in rendering-related bugs
&lt;/li&gt;
&lt;li&gt;Fewer production-only edge cases
&lt;/li&gt;
&lt;li&gt;Higher confidence shipping UI-heavy changes
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React 19 and Next.js 16 handled concurrency and async behavior more predictably, reducing the need for defensive coding patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ What This Enabled for Engineers
&lt;/h2&gt;

&lt;p&gt;This upgrade unlocked leverage across the engineering workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ Faster Feedback Loops
&lt;/h3&gt;

&lt;p&gt;Faster builds and rebuilds changed how often engineers experiment — and how quickly ideas turn into shipped features.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Simpler Mental Models
&lt;/h3&gt;

&lt;p&gt;The migration allowed us to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove legacy abstractions
&lt;/li&gt;
&lt;li&gt;Delete workaround code
&lt;/li&gt;
&lt;li&gt;Standardize rendering and data-access patterns
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Less code, fewer special cases, and more confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔒 Stronger Security Posture
&lt;/h3&gt;

&lt;p&gt;By aligning with the current ecosystem, we reduced long-term security maintenance and avoided custom defensive layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Lessons We’d Share with Other Teams
&lt;/h2&gt;

&lt;p&gt;If you’re considering a similar migration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Treat it as a &lt;strong&gt;platform investment&lt;/strong&gt;, not routine maintenance
&lt;/li&gt;
&lt;li&gt;Break the work into phases — but commit to finishing
&lt;/li&gt;
&lt;li&gt;Use the opportunity to simplify, not just upgrade
&lt;/li&gt;
&lt;li&gt;Don’t carry forward legacy patterns unless they still earn their keep
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal isn’t to be “on the latest version.”&lt;br&gt;&lt;br&gt;
The goal is to &lt;strong&gt;reduce friction for the next 12–24 months&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏁 Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This migration reset our baseline.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;Next.js 16, React 19, and Turbopack&lt;/strong&gt;, we’re now building on a platform that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is faster in everyday workflows
&lt;/li&gt;
&lt;li&gt;Is more stable under real-world conditions
&lt;/li&gt;
&lt;li&gt;Is more secure by default
&lt;/li&gt;
&lt;li&gt;Reduces long-term maintenance overhead
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benchmarks confirmed what we felt intuitively:&lt;br&gt;&lt;br&gt;
this wasn’t just a successful upgrade — it was a &lt;strong&gt;meaningful platform improvement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you’ve been delaying a migration like this, we’d strongly recommend taking another look.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>react</category>
      <category>ai</category>
    </item>
    <item>
      <title>🚀 Migrating a Large-Scale Monorepo from Next.js 14 to 16: A Real-World Journey</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Fri, 09 Jan 2026 08:55:23 +0000</pubDate>
      <link>https://dev.to/abhilashlr/migrating-a-large-scale-monorepo-from-nextjs-14-to-16-a-real-world-journey-5383</link>
      <guid>https://dev.to/abhilashlr/migrating-a-large-scale-monorepo-from-nextjs-14-to-16-a-real-world-journey-5383</guid>
      <description>&lt;h2&gt;
  
  
  🧭 TL;DR
&lt;/h2&gt;

&lt;p&gt;We migrated a large production &lt;strong&gt;Next.js monorepo&lt;/strong&gt; (240+ route files, 3k+ i18n strings) from &lt;strong&gt;Next.js 14 → 16&lt;/strong&gt; by preparing the codebase first, upgrading dependencies incrementally, and relying heavily on &lt;strong&gt;Next.js codemods + AI-assisted refactoring&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The biggest breaking change was &lt;strong&gt;async route params&lt;/strong&gt;, but with a phased approach and automation, we completed the migration in &lt;strong&gt;~2 weeks&lt;/strong&gt; with minimal production risk.&lt;/p&gt;




&lt;p&gt;Upgrading a large enterprise application with a monorepo structure from Next.js 14 to Next.js 16 isn't just about bumping version numbers. It's a carefully orchestrated process that requires understanding breaking changes, preparing your codebase, and migrating incrementally. This post shares our experience migrating a production enterprise application with 240+ files changed and lessons learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Project Context
&lt;/h2&gt;

&lt;p&gt;Our setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Main Application&lt;/strong&gt;: Next.js 14.2.35 → 16.1.1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo&lt;/strong&gt;: Shared component libraries and utilities (design system, data layer, icons)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale&lt;/strong&gt;: 3,196+ internationalized strings, 240+ route files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Stack&lt;/strong&gt;: React 18 → React 19, TypeScript 5.8, Styled Components, TanStack Query&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🗺️ The Migration Strategy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Preparation (the foundation)
&lt;/h3&gt;

&lt;p&gt;Before touching Next.js itself, we prepared our codebase to be compatible with the new conventions. Think of this as cleaning your house before moving furniture - it makes everything else easier.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Update dependencies first
&lt;/h4&gt;

&lt;p&gt;We updated related packages incrementally to avoid dependency conflicts. Why? Because trying to update everything at once is like trying to solve multiple puzzles simultaneously - it's much harder to identify what broke when something goes wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Bump Next.js 14 to latest patch version&lt;/span&gt;
yarn add next@14.2.35

&lt;span class="c"&gt;# Update React Query v4 → v5&lt;/span&gt;
yarn add @tanstack/react-query@^5.90.12

&lt;span class="c"&gt;# Update react-intl for React 19 compatibility&lt;/span&gt;
yarn add react-intl@8.0.6

&lt;span class="c"&gt;# Update react-select with proper types&lt;/span&gt;
yarn add react-select@^5.8.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Updating dependencies incrementally helps isolate issues. We found that React Query v5 had breaking changes that needed fixes before tackling Next.js. Imagine debugging a broken build when you've changed 10 things at once versus just 1 - that's the difference.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Migrate to named React imports
&lt;/h4&gt;

&lt;p&gt;Next.js 16 with React 19 recommends named imports over default imports. What does this mean? Instead of importing React as a whole, we import only the specific pieces we need. We migrated 240+ files to this new pattern:&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;// Before&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&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;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After&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;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also updated all test utilities and wrapper components:&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;// Test utilities - Before&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After&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;ReactElement&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&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;Pro tip&lt;/strong&gt;: Don't do this manually! We used AI coding assistants to automate this transformation across 240+ files. Think of AI as your tireless intern who never gets bored of repetitive tasks and makes fewer mistakes than humans doing copy-paste work. This saved us hours of tedious work and reduced the chance of human error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Update monorepo packages
&lt;/h3&gt;

&lt;p&gt;If you have a monorepo (a single repository containing multiple related packages), you need to update shared packages first.&lt;/p&gt;

&lt;p&gt;Before migrating the main app, we updated our shared component library packages to support both React 18 and React 19.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Shared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;packages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;peer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"peerDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18.2.0 || ^19.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react-dom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18.2.0 || ^19.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This flexible version range (&lt;code&gt;^18.2.0 || ^19.0.0&lt;/code&gt;) means "accept React 18.2.0 or higher, OR React 19.0.0 or higher". This allowed us to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test packages independently with React 19&lt;/li&gt;
&lt;li&gt;Keep the main app on React 18 until ready&lt;/li&gt;
&lt;li&gt;Avoid breaking other projects using the monorepo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: It's like having a power adapter that works with both 110V and 220V - you can plug it in anywhere without breaking things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: TypeScript configuration updates
&lt;/h3&gt;

&lt;p&gt;Next.js 16 requires a specific TypeScript setting. We updated &lt;code&gt;tsconfig.json&lt;/code&gt; (the TypeScript configuration file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"react-jsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Changed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"preserve"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dom.iterable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esnext"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;jsx: "react-jsx"&lt;/code&gt; setting enables the new JSX transform. Translation: you no longer need to write &lt;code&gt;import React from 'react'&lt;/code&gt; at the top of every file. The build system handles it automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Route parameters type migration
&lt;/h3&gt;

&lt;p&gt;This was the biggest change. Next.js 16 made routes type-aware of their parameters - meaning TypeScript now knows exactly which parameters exist for each route, making your code safer and catching errors at compile time instead of runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are route parameters?&lt;/strong&gt; They're the dynamic parts in URLs like &lt;code&gt;/products/[id]&lt;/code&gt; where &lt;code&gt;[id]&lt;/code&gt; could be any product ID.&lt;/p&gt;

&lt;h4&gt;
  
  
  The challenge: Type-safe route parameters
&lt;/h4&gt;

&lt;p&gt;Previously in Next.js 14, route parameters weren't type-safe at the framework level. We could manually define types, but Next.js didn't enforce them:&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;// Before - Next.js 14&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViewType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// enum&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;Page&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="p"&gt;}&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="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The solution: String parameters with runtime casting
&lt;/h4&gt;

&lt;p&gt;Next.js 16 says "everything from the URL is a string" (because URLs are just text!). So now we convert them to the types we need inside our code:&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;// After - Next.js 16&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&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="nl"&gt;categoryId&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="nl"&gt;view&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="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;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&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="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;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;props&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&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;id&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;categoryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&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;categoryId&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;view&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;view&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ViewType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&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;Key changes explained&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;All params are now strings (text) - because that's what URLs actually are&lt;/li&gt;
&lt;li&gt;Page props contain &lt;code&gt;Promise&amp;lt;Params&amp;gt;&lt;/code&gt; - you must &lt;code&gt;await&lt;/code&gt; them (like waiting for a package to arrive before opening it)&lt;/li&gt;
&lt;li&gt;Convert numeric IDs with &lt;code&gt;Number()&lt;/code&gt; - turning the text "123" into the actual number 123&lt;/li&gt;
&lt;li&gt;Cast enum values with &lt;code&gt;as ViewType&lt;/code&gt; - telling TypeScript "trust me, this string is one of the valid options"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This affected 120+ route files (yes, that many!). The Next.js codemod handled most of this transformation automatically, creating this consistent pattern across all our routes:&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;// Layout pattern (created by codemod)&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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="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;Layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&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="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&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;props&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numericId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;LayoutComponent&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;numericId&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;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;/LayoutComponent&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Page pattern (created by codemod)&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;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&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="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;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;props&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="c1"&gt;// Use params...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the codemod ran, we used AI assistants to handle the parts it couldn't automate - like converting string IDs to numbers and casting enum types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 5: The main migration
&lt;/h3&gt;

&lt;p&gt;With all the preparation work done (think of it as laying the groundwork), we finally performed the actual Next.js upgrade. This is like changing the engine of a car - you want to make sure everything else is ready before you do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update Next.js and React&lt;/span&gt;
yarn add next@16.1.1 react@18.3.1 react-dom@18.3.1

&lt;span class="c"&gt;# Update Next.js tooling&lt;/span&gt;
yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; @next/bundle-analyzer@16.1.1 eslint-config-next@16.1.1

&lt;span class="c"&gt;# Update React types&lt;/span&gt;
yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; @types/react@19.2.7 @types/react-dom@19.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Handling breaking changes
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Breaking changes&lt;/strong&gt; means features that no longer work the way they used to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Removed Next.js config options&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some configuration options no longer exist in Next.js 16:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.js - Removed&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// These were removed in Next.js 16&lt;/span&gt;
  &lt;span class="c1"&gt;// distDir: '.next',&lt;/span&gt;
  &lt;span class="c1"&gt;// target: 'serverless',&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;2. Image configuration changes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;:&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="c1"&gt;// Still works but remotePatterns is the new preferred way&lt;/span&gt;
    &lt;span class="na"&gt;unoptimized&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Middleware updates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Middleware is code that runs before your pages load. NextJS Codemod should automatically migrate your existing middleware.ts to proxy.ts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 6: Testing and validation
&lt;/h3&gt;

&lt;p&gt;After the migration, we systematically tested everything.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Type checking&lt;/strong&gt;: &lt;code&gt;yarn lint&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt;: &lt;code&gt;yarn build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt;: &lt;code&gt;yarn test&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime testing&lt;/strong&gt;: Manual testing of critical paths&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We caught and fixed issues like:&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;// Issue: Async server components not awaited&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;props&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="nx"&gt;props&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="c1"&gt;// ❌ Missing await&lt;/span&gt;

  &lt;span class="c1"&gt;// Fixed&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;props&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="c1"&gt;// ✅&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🤖 What about codemods?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What are codemods?&lt;/strong&gt; They're automated tools that rewrite your code following specific patterns. Think of them as find-and-replace on steroids - they understand code structure, not just text.&lt;/p&gt;

&lt;p&gt;Next.js provides official codemods for many migrations, and we &lt;strong&gt;absolutely used them first&lt;/strong&gt;! Codemods automated a significant portion of our migration work - like having a robot do the boring repetitive parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codemods we used successfully
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Migrate metadata and generateMetadata exports&lt;/span&gt;
npx @next/codemod@latest metadata-to-viewport-export &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Update Link components&lt;/span&gt;
npx @next/codemod@latest new-link &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Convert async request APIs&lt;/span&gt;
npx @next/codemod@latest async-request-api &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These codemods handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Converting metadata exports to the new format&lt;/li&gt;
&lt;li&gt;Transforming basic async patterns on page/layout files&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI assistants for everything else
&lt;/h3&gt;

&lt;p&gt;After running codemods (which handled the straightforward, pattern-based changes), we used AI coding assistants to handle all the complex stuff that requires understanding context:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Route parameter type conversions&lt;/strong&gt; - Converting numeric IDs and enums to strings across 120+ route files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo coordination&lt;/strong&gt; - Updating shared types across packages with proper context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Named React imports&lt;/strong&gt; - Transforming 240+ files to use named imports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-aware refactoring&lt;/strong&gt; - Understanding component relationships and dependencies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI assistant understood our codebase context and applied patterns consistently, catching edge cases that simple find-and-replace would have missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our recommendation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use codemods + AI assistants for maximum efficiency!&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run all applicable Next.js codemods first - they'll handle standard Next.js patterns (80-85% of changes)&lt;/li&gt;
&lt;li&gt;Review and commit the codemod changes&lt;/li&gt;
&lt;li&gt;Use AI coding assistants for everything else - they understand context and can handle complex transformations&lt;/li&gt;
&lt;li&gt;Final manual review of critical business logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach completed our migration in 2 weeks instead of what would have taken months manually. The combination of automated codemods and intelligent AI assistance meant we could focus our time on testing and validation rather than mechanical code changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI-human-CI/CD loop
&lt;/h3&gt;

&lt;p&gt;Our migration followed a continuous improvement cycle that combined AI automation with human oversight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. AI makes changes (codemods + Claude Code + AntiGravity)
   ↓
2. Run tests locally (`yarn test`, `yarn lint`)
   ↓
3. Commit and push to GitHub
   ↓
4. GitHub Actions runs CI/CD pipeline
   - Type checking
   - Linting
   - Unit tests
   - Build verification
   ↓
5. Automated code review (CodeRabbit AI)
   - Identifies potential issues
   - Suggests improvements
   - Flags edge cases
   ↓
6. Human code review
   - Verify business logic
   - Check critical paths
   - Approve or request changes
   ↓
7. Fix issues → Repeat from step 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;: This loop ran dozens of times throughout our phased migration. AI handled the mechanical transformations (80-90% of changes), while humans focused on validating correctness, catching edge cases, and ensuring business logic integrity. Automated tools (CI/CD + CodeRabbit) acted as safety nets, catching issues before they reached production.&lt;/p&gt;

&lt;p&gt;The cycle typically took 30-60 minutes per iteration, allowing us to make rapid progress while maintaining code quality.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Migration checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist for your own migration. Print it out and check off items as you go!&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-migration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Update to latest Next.js 14.x patch&lt;/li&gt;
&lt;li&gt;Update related dependencies (React Query, react-intl, etc.)&lt;/li&gt;
&lt;li&gt;Move non-route files out of &lt;code&gt;/pages/api&lt;/code&gt; and &lt;code&gt;/app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Migrate to named React imports&lt;/li&gt;
&lt;li&gt;Update monorepo packages for React 19 compatibility&lt;/li&gt;
&lt;li&gt;Update TypeScript to 5.x&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  During migration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Update all route parameters to string types (remember: everything from URLs is text!)&lt;/li&gt;
&lt;li&gt;Update Next.js to 16.x&lt;/li&gt;
&lt;li&gt;Convert page components to async and await params (add the &lt;code&gt;await&lt;/code&gt; keyword)&lt;/li&gt;
&lt;li&gt;Update React to 18.3.x (or 19.x if you're feeling brave)&lt;/li&gt;
&lt;li&gt;Update @types/react and @types/react-dom (the TypeScript definitions)&lt;/li&gt;
&lt;li&gt;Remove deprecated Next.js config options&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Post-migration (the victory lap!)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run type checking (&lt;code&gt;tsc --noEmit&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;li&gt;Test build locally&lt;/li&gt;
&lt;li&gt;Test development server&lt;/li&gt;
&lt;li&gt;Manual testing of critical user paths&lt;/li&gt;
&lt;li&gt;Deploy to staging environment&lt;/li&gt;
&lt;li&gt;Monitor for runtime errors&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚡ Performance impact
&lt;/h2&gt;

&lt;p&gt;After migration, we measured the actual impact on our application. Here's what we found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build time&lt;/strong&gt;: Extraordinary results! (~50-60% faster with Next.js 16 turbopack improvements). &lt;em&gt;Build time&lt;/em&gt; is how long it takes to compile your code for production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Before
&lt;/h5&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%2Fymkneaxi22sm8odkkdvu.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%2Fymkneaxi22sm8odkkdvu.png" alt=" " width="685" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  After
&lt;/h5&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%2Fuphe6bnke8w1i7irmw4k.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%2Fuphe6bnke8w1i7irmw4k.png" alt=" " width="676" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bundle size&lt;/strong&gt;: Slightly smaller (-3% due to better tree-shaking). &lt;em&gt;Bundle size&lt;/em&gt; is the total size of JavaScript your users download - smaller is better!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Development&lt;/strong&gt;: Faster with Turbopack (we were already using this in Next.js 14).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ Common pitfalls and solutions
&lt;/h2&gt;

&lt;p&gt;These are the mistakes we made so you don't have to! Learn from our pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Forgetting to await params
&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;// ❌ Wrong&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;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&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="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&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="c1"&gt;// Missing await!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Correct&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;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&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="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&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;props&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Type mismatches in nested components
&lt;/h3&gt;

&lt;p&gt;Remember: route params come in as strings, but your components might expect numbers. Always convert!&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;// Route param is string, but component expects number&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numericId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&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;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Convert first!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Build errors with dynamic routes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;generateMetadata&lt;/code&gt; is a special function that generates page titles and meta tags. It also needs to await params:&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;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;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&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="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;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;props&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Item &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;id&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💡 Key takeaways
&lt;/h2&gt;

&lt;p&gt;The most important lessons we learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepare incrementally&lt;/strong&gt;: Don't upgrade everything at once - you do it one thing at a time. Update dependencies (and test), fix code patterns (and test), then upgrade Next.js.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test continuously&lt;/strong&gt;: Run type checking and tests after each major change. Catching bugs early is way easier than debugging a mountain of changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Understand the changes&lt;/strong&gt;: Read the &lt;a href="https://nextjs.org/blog/next-15" rel="noopener noreferrer"&gt;Next.js 15&lt;/a&gt; and &lt;a href="https://nextjs.org/blog/next-16" rel="noopener noreferrer"&gt;Next.js 16&lt;/a&gt; release notes thoroughly. I know it's tempting to skip documentation, but trust us - 30 minutes of reading saves hours of debugging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Async params are non-negotiable&lt;/strong&gt;: This is the biggest breaking change but thanks to Codemods from Next.js, we were able to automate most of the work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monorepo coordination&lt;/strong&gt;: If you have shared packages (a monorepo), update them first with flexible peer dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider React 19&lt;/strong&gt;: Next.js 16 works with React 18.3+ but is optimized for React 19. Decide if you want to upgrade React at the same time or separately.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📚 Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/building-your-application/upgrading/version-15" rel="noopener noreferrer"&gt;Next.js 15 Upgrade Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/blog/next-16" rel="noopener noreferrer"&gt;Next.js 16 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.dev/blog/2024/12/05/react-19" rel="noopener noreferrer"&gt;React 19 Upgrade Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/building-your-application/upgrading/codemods" rel="noopener noreferrer"&gt;Next.js Codemods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Migrating a large-scale application from Next.js 14 to 16 is a significant undertaking, but with proper preparation and incremental changes, it's manageable. Our migration touched 240+ files and took about 2 weeks of focused work, including testing and validation.&lt;/p&gt;

&lt;p&gt;The key is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Break the migration into phases&lt;/li&gt;
&lt;li&gt;Prepare your codebase first&lt;/li&gt;
&lt;li&gt;Update dependencies incrementally&lt;/li&gt;
&lt;li&gt;Test thoroughly at each step&lt;/li&gt;
&lt;li&gt;Document patterns for consistency&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;We're now running Next.js 16 in production with improved performance and access to the latest React features. The investment in careful migration paid off with a stable, future-proof codebase.&lt;/p&gt;

&lt;p&gt;Have you migrated to Next.js 16? What challenges did you face? Share your experience in the comments!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Special thanks to the Next.js team for excellent documentation and the community for sharing their migration experiences.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Sun, 30 Nov 2025 05:29:07 +0000</pubDate>
      <link>https://dev.to/abhilashlr/-2jhf</link>
      <guid>https://dev.to/abhilashlr/-2jhf</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/abhilashlr/a-complete-in-depth-guide-to-trusted-types-in-react-and-modern-web-apps-30id" class="crayons-story__hidden-navigation-link"&gt;🔥 A Complete, In-Depth Guide to Trusted Types in React and Modern Web Apps&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/abhilashlr" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F340347%2Ff47b0aa8-aa20-4935-98e6-b8a112d66dc8.jpg" alt="abhilashlr profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/abhilashlr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              abhilashlr
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                abhilashlr
                
              
              &lt;div id="story-author-preview-content-3071023" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/abhilashlr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F340347%2Ff47b0aa8-aa20-4935-98e6-b8a112d66dc8.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;abhilashlr&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/abhilashlr/a-complete-in-depth-guide-to-trusted-types-in-react-and-modern-web-apps-30id" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 29 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/abhilashlr/a-complete-in-depth-guide-to-trusted-types-in-react-and-modern-web-apps-30id" id="article-link-3071023"&gt;
          🔥 A Complete, In-Depth Guide to Trusted Types in React and Modern Web Apps
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/abhilashlr/a-complete-in-depth-guide-to-trusted-types-in-react-and-modern-web-apps-30id" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/abhilashlr/a-complete-in-depth-guide-to-trusted-types-in-react-and-modern-web-apps-30id#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>🔥 A Complete, In-Depth Guide to Trusted Types in React and Modern Web Apps</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Sat, 29 Nov 2025 13:46:45 +0000</pubDate>
      <link>https://dev.to/abhilashlr/a-complete-in-depth-guide-to-trusted-types-in-react-and-modern-web-apps-30id</link>
      <guid>https://dev.to/abhilashlr/a-complete-in-depth-guide-to-trusted-types-in-react-and-modern-web-apps-30id</guid>
      <description>&lt;h2&gt;
  
  
  Trusted Types in React: A Complete Engineering Guide to Preventing XSS Without Breaking Your App
&lt;/h2&gt;

&lt;p&gt;Trusted Types is one of the most powerful — yet misunderstood — additions to modern browser security. It addresses a problem every frontend engineer encounters eventually: how do we safely render untrusted HTML without opening the door to XSS?&lt;/p&gt;

&lt;p&gt;If you’ve ever used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTML sanitizers&lt;/li&gt;
&lt;li&gt;Script injection cleanup&lt;/li&gt;
&lt;li&gt;Rich text editors&lt;/li&gt;
&lt;li&gt;User-generated content&lt;/li&gt;
&lt;li&gt;Markdown → HTML pipelines&lt;/li&gt;
&lt;li&gt;Server-rendered CMS content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you’ve already danced around this problem.&lt;/p&gt;

&lt;p&gt;This article is a deep-dive into how &lt;strong&gt;Trusted Types&lt;/strong&gt; work, why they matter, and how to integrate them with React, Shadow DOM, and real-world production codebases — without rewriting your entire app.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Why Trusted Types Exist
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The real problem: "string-to-DOM" APIs are unsafe&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;APIs like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;srcdoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;allow arbitrary strings to be interpreted as DOM or script. If any of that string is user-controlled → you have XSS.&lt;/p&gt;

&lt;p&gt;For years, sites relied on sanitizers like &lt;strong&gt;DOMPurify&lt;/strong&gt;. These help, but they can be bypassed if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The sanitizer is outdated&lt;/li&gt;
&lt;li&gt;Someone forgets to sanitize&lt;/li&gt;
&lt;li&gt;A dev uses a different code path&lt;/li&gt;
&lt;li&gt;A new dangerous API is introduced&lt;/li&gt;
&lt;li&gt;Sanitization only happens sometimes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern apps use dozens of 3rd-party libraries, each of which might use string → DOM APIs internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusted Types fixes this structurally.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What Trusted Types Actually Do
&lt;/h2&gt;

&lt;p&gt;Trusted Types enforce that &lt;strong&gt;any DOM API that expects HTML accepts only a special object&lt;/strong&gt;, not a string.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;img src=x onerror=alert(1)&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ CSP violation → browser blocks it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;b&amp;gt;Hello&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ✅ allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser now enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only approved code can turn strings into HTML&lt;/li&gt;
&lt;li&gt;That conversion happens through one policy&lt;/li&gt;
&lt;li&gt;Every unsafe sink is protected by the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is way more robust than “remembering to sanitize everything”.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How React Renders HTML (important!)
&lt;/h2&gt;

&lt;p&gt;React normally sets the HTML for you, &lt;strong&gt;not with innerHTML&lt;/strong&gt;, but through:&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="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, React intentionally validates HTML and works through its DOM diffing layers.&lt;/p&gt;

&lt;p&gt;But this API still hits an XSS sink.&lt;/p&gt;

&lt;p&gt;When Trusted Types are enabled, the browser prevents React from passing plain strings unless they come from a TrustedHTML object.&lt;/p&gt;

&lt;p&gt;Thus:&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="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;p&amp;gt;hello&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ blocked by Trusted Types&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But:&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="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;p&amp;gt;hello&amp;lt;/p&amp;gt;&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="c1"&gt;// ✅ safe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Setting Up Trusted Types in React
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Define a policy
&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;// global script before React mounts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ttPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trustedTypes&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;createPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&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="na"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;DOMPurify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Important:
&lt;/h4&gt;

&lt;p&gt;Trusted Types do not sanitize.&lt;br&gt;
They only ensure you don’t forget to sanitize.&lt;/p&gt;

&lt;p&gt;That’s why you plug in a sanitizer (DOMPurify is the industry standard).&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 2 — Use the policy where HTML is passed to React
&lt;/h3&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="nt"&gt;div&lt;/span&gt;
  &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ttPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userHTML&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note:&lt;br&gt;
&lt;code&gt;ttPolicy.createHTML()&lt;/code&gt; returns a &lt;strong&gt;TrustedHTML&lt;/strong&gt; object, not a string.&lt;/p&gt;

&lt;p&gt;React doesn’t care — it’ll pass it straight to the DOM.&lt;/p&gt;


&lt;h2&gt;
  
  
  5. Why createHTML() Is Not a String
&lt;/h2&gt;

&lt;p&gt;This is the most confusing part for developers.&lt;/p&gt;

&lt;p&gt;Trusted Types are &lt;strong&gt;opaque, branded objects&lt;/strong&gt; with no &lt;code&gt;.toString()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is by design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You should not be able to convert them casually&lt;/li&gt;
&lt;li&gt;They should not flow back into string contexts&lt;/li&gt;
&lt;li&gt;They must be clearly treated as final sanitized safe DOM fragments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;safe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ttPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;p&amp;gt;Hi&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;someAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;safe&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would convert it back to string — which is unsafe.&lt;/p&gt;

&lt;p&gt;Trusted Types eliminate this entire class of bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Enforcing Trusted Types with CSP
&lt;/h2&gt;

&lt;p&gt;To actually enforce Trusted Types, you set a CSP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy:
  require-trusted-types-for 'script';
  trusted-types default;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also add multiple policies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trusted-types default dompurify sanitize bleed;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every call like:&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="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...is blocked by the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Full Example: Rendering User HTML Safely With Trusted Types
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup the policy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sanitizePolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trustedTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sanitize&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="na"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;DOMPurify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;h3&gt;
  
  
  React component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SafeHtml&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&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;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
        &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sanitizePolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;}}&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;strong&gt;Usage&lt;/strong&gt;&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;SafeHtml&lt;/span&gt; &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userComment&lt;/span&gt;&lt;span class="si"&gt;}&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;
  
  
  8. Trusted Types in Shadow DOM (Web Components)
&lt;/h2&gt;

&lt;p&gt;Shadow DOM has separate DOM trees, but &lt;strong&gt;Trusted Types&lt;/strong&gt; work the same.&lt;/p&gt;

&lt;p&gt;Example inside a custom element:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecureWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;sanitizePolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&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;If you forget:&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ CSP violation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trusted Types enforce safety inside shadow roots too.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Where Developers Usually Get It Wrong (Important)
&lt;/h2&gt;

&lt;p&gt;❌ Mistake 1 — Using Trusted Types without sanitizers&lt;/p&gt;

&lt;p&gt;Trusted Types stop accidental sinks, but they do not magically clean HTML.&lt;/p&gt;

&lt;p&gt;❌ Mistake 2 — Creating multiple policies&lt;/p&gt;

&lt;p&gt;Use one policy per pipeline.&lt;/p&gt;

&lt;p&gt;Multiple policies = inconsistent security.&lt;/p&gt;

&lt;p&gt;❌ Mistake 3 — Using it only in React components&lt;/p&gt;

&lt;p&gt;Any of these are sinks too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;element.insertAdjacentHTML&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;script.srcdoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iframe.srcdoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;range.createContextualFragment()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your policy should cover every path HTML enters the DOM.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Advanced Architecture: A Full HTML Safety Pipeline
&lt;/h2&gt;

&lt;p&gt;A robust real-world pipeline looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Input comes from server/client
               ↓
2. Clean with a sanitizer (DOMPurify)
               ↓
3. Convert via Trusted Types policy
               ↓
4. Render via React/shadow DOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no bypass paths&lt;/li&gt;
&lt;li&gt;no forgotten sanitization&lt;/li&gt;
&lt;li&gt;browser-level enforcement&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Rich text editors
&lt;/h3&gt;

&lt;p&gt;Quill, TinyMCE, TipTap — all output HTML.&lt;/p&gt;

&lt;p&gt;Trusted Types ensure no editor misconfiguration breaks your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. CMS-rendered content
&lt;/h3&gt;

&lt;p&gt;Blog platforms, Notion embeds, marketing pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. User-generated comments
&lt;/h3&gt;

&lt;p&gt;Forums, reviews, chat apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Markdown rendering
&lt;/h3&gt;

&lt;p&gt;Markdown → HTML is extremely risky if not sanitized.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Final Check: Is Your App Safe Without Trusted Types?
&lt;/h2&gt;

&lt;p&gt;If you use any of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;iframe srcdoc="..."&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTML sanitization&lt;/li&gt;
&lt;li&gt;Web Components with string HTML&lt;/li&gt;
&lt;li&gt;3rd-party libraries injecting HTML&lt;/li&gt;
&lt;li&gt;UI frameworks like Angular/Old React&lt;/li&gt;
&lt;li&gt;Markdown-to-HTML conversion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;→ Your app should use Trusted Types.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern security requirements (e.g. enterprise customers, finance, healthcare, gov) increasingly require it.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Rolling Out Trusted Types Safely: Report Violations Before Enforcing
&lt;/h2&gt;

&lt;p&gt;A common mistake teams make is enabling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: require-trusted-types-for 'script';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...&lt;em&gt;immediately&lt;/em&gt; and breaking half the app.&lt;/p&gt;

&lt;p&gt;In reality, the best practice is a two-phase rollout:&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1 — Report‐Only Mode (No Breaking, Just Visibility)
&lt;/h3&gt;

&lt;p&gt;Before you enforce Trusted Types, configure your CSP with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy-Report-Only:
  require-trusted-types-for 'script';
  report-uri https://YOUR-ERROR-MONITORING-ENDPOINT;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR for Sentry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy-Report-Only:
  require-trusted-types-for 'script';
  report-uri https://sentry.io/api/&amp;lt;project&amp;gt;/security/?sentry_key=&amp;lt;key&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows browsers to send violation events without blocking the app.&lt;/p&gt;

&lt;p&gt;You'll start receiving reports any time a library or component incorrectly uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;innerHTML&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;insertAdjacentHTML&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;script.srcdoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iframe.srcdoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Range.createContextualFragment()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;React components with raw strings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you a map of all unsafe sinks in your app — something nearly impossible to discover manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2 — Send Violations to Your Error Monitoring Tool
&lt;/h3&gt;

&lt;p&gt;You can also catch violations &lt;strong&gt;directly in JavaScript&lt;/strong&gt; and forward them to Sentry, LogRocket, Datadog, or any internal error system.&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;securitypolicyviolation&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;e&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;// Example: report to Sentry&lt;/span&gt;
  &lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Trusted Types Violation&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="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;directive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;violatedDirective&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;blockedURI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blockedURI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lineNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;columnNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columnNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sourceFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceFile&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;Now every Trusted Types violation becomes visible in error dashboards long before users notice breakage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3 — Enforce Trusted Types Once Violations Are Near Zero
&lt;/h3&gt;

&lt;p&gt;Only after fixing the violations should you switch from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy-Report-Only: ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to the enforcing version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy:
  require-trusted-types-for 'script';
  trusted-types default sanitize;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This two-phase strategy is widely used in Enterprise apps, consumer apps with large legacy codebases&lt;/p&gt;

&lt;p&gt;It ensures that rolling out Trusted Types is safe, predictable, and does not break production.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Trusted Types&lt;/strong&gt; are the single most important browser security feature most frontend devs still underestimate. They provide structural protection from XSS regardless of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who writes the code&lt;/li&gt;
&lt;li&gt;which library you use&lt;/li&gt;
&lt;li&gt;how complex your UI is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding them to a React or Shadow DOM app is not just simple — it’s the kind of engineering discipline that pays off for years.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>⚡ Supercharging GitHub Actions CI: From Slow to Lightning Fast with Turbo Caching</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Mon, 03 Nov 2025 12:14:34 +0000</pubDate>
      <link>https://dev.to/abhilashlr/supercharging-github-actions-ci-from-slow-to-lightning-fast-with-turbo-caching-1bed</link>
      <guid>https://dev.to/abhilashlr/supercharging-github-actions-ci-from-slow-to-lightning-fast-with-turbo-caching-1bed</guid>
      <description>&lt;p&gt;&lt;em&gt;How we optimized our monorepo CI pipeline and reduced build times by 70% using smart caching strategies&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🐌 The Problem: Slow CI is a Developer Productivity Killer
&lt;/h2&gt;

&lt;p&gt;Picture this: You're working on a critical feature for your React monorepo. You push your changes, create a pull request, and then... you wait. And wait. Your GitHub Actions CI takes 8-10 minutes to run lint and build checks, grinding your development flow to a halt.&lt;/p&gt;

&lt;p&gt;This was exactly our situation with our &lt;code&gt;@atomicworkhq/atomic-ui&lt;/code&gt; monorepo - a TypeScript project built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;6 packages&lt;/strong&gt;: icons, obsidian (design system), data models, forms, assist, and public apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turbo&lt;/strong&gt;: For coordinated builds and caching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yarn workspaces&lt;/strong&gt;: For dependency management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt;: For CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our original CI was taking way too long, and developers were getting frustrated. Time for an optimization sprint! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 Analyzing the Original Setup
&lt;/h2&gt;

&lt;p&gt;Here's what our original &lt;code&gt;sanity.yml&lt;/code&gt; workflow looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ BEFORE: Inefficient caching and resource usage&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sanity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Lint&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="c1"&gt;# Basic turbo cache - not optimized&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache turbo build setup&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.turbo&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ hashFiles('yarn.lock') }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Basic yarn cache&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fetch yarn cache if available&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;~/.cache/yarn&lt;/span&gt;
            &lt;span class="s"&gt;node_modules&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Always install dependencies (even with cache hits)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install --frozen-lockfile&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run task&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn ${{ matrix.task }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issues with the Original Approach
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inefficient dependency installation&lt;/strong&gt;: Always ran &lt;code&gt;yarn install&lt;/code&gt;, even with cache hits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor cache keys&lt;/strong&gt;: Generic cache keys didn't differentiate between tasks or branches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory constraints&lt;/strong&gt;: No memory optimization for Node.js processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited Turbo cache&lt;/strong&gt;: Only cached &lt;code&gt;.turbo&lt;/code&gt; directory, missing &lt;code&gt;~/.turbo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No cache persistence&lt;/strong&gt;: Didn't save updated cache for future runs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🛠️ The Optimization Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Smart Dependency Caching
&lt;/h3&gt;

&lt;p&gt;First, we implemented conditional dependency installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ✅ AFTER: Smart dependency caching&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore Yarn cache&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cache&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;~/.cache/yarn&lt;/span&gt;
      &lt;span class="s"&gt;**/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}&lt;/span&gt;
    &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;${{ runner.os }}-yarn-main-&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.cache.outputs.cache-hit != 'true'&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NODE_OPTIONS="--max_old_space_size=8192" yarn install --frozen-lockfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key improvements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Skip installation when cache hits (saves 2-3 minutes!)&lt;/li&gt;
&lt;li&gt;✅ Increased Node.js memory limit to prevent OOM errors&lt;/li&gt;
&lt;li&gt;✅ Better cache paths including &lt;code&gt;~/.cache/yarn&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Advanced Turbo Caching Strategy
&lt;/h3&gt;

&lt;p&gt;Next, we revolutionized our Turbo caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ✅ AFTER: Advanced Turbo caching with task-specific keys&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore Turbo cache&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;~/.turbo&lt;/span&gt;
      &lt;span class="s"&gt;.turbo&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref || github.ref_name }}&lt;/span&gt;
    &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref || github.ref_name }}&lt;/span&gt;
      &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-main&lt;/span&gt;
      &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run task&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn ${{ matrix.task }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Task-specific caching&lt;/strong&gt;: &lt;code&gt;matrix.task&lt;/code&gt; in cache keys separates lint vs build artifacts&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Branch-aware caching&lt;/strong&gt;: Uses actual branch names for better cache hits&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Comprehensive cache paths&lt;/strong&gt;: Both &lt;code&gt;~/.turbo&lt;/code&gt; and &lt;code&gt;.turbo&lt;/code&gt; directories&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Smart fallback hierarchy&lt;/strong&gt;: Falls back through task → branch → main → generic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Memory Optimization in package.json
&lt;/h3&gt;

&lt;p&gt;We also optimized our npm scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_OPTIONS=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;--max_old_space_size=8192&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; turbo run build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_OPTIONS=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;--max_old_space_size=8192&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; turbo run check-types"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents memory-related build failures in large monorepos.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 The Complete Optimized Workflow
&lt;/h2&gt;

&lt;p&gt;Here's our final, lightning-fast workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sanity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.task }}&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node.js environment&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yarn'&lt;/span&gt;

      &lt;span class="c1"&gt;# Smart Yarn caching&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore Yarn cache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cache&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;~/.cache/yarn&lt;/span&gt;
            &lt;span class="s"&gt;**/node_modules&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-yarn-main-&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.cache.outputs.cache-hit != 'true'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NODE_OPTIONS="--max_old_space_size=8192" yarn install --frozen-lockfile&lt;/span&gt;

      &lt;span class="c1"&gt;# Advanced Turbo caching&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore Turbo cache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;~/.turbo&lt;/span&gt;
            &lt;span class="s"&gt;.turbo&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref || github.ref_name }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref || github.ref_name }}&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-main&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run task&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn ${{ matrix.task }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📊 Performance Results: The Numbers Don't Lie
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cold run time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8-10 minutes&lt;/td&gt;
&lt;td&gt;6-7 minutes&lt;/td&gt;
&lt;td&gt;25% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Warm run time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6-8 minutes&lt;/td&gt;
&lt;td&gt;2-3 minutes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;70% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache hit rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~30%&lt;/td&gt;
&lt;td&gt;~85%&lt;/td&gt;
&lt;td&gt;183% improvement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dependency install time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2-3 minutes&lt;/td&gt;
&lt;td&gt;10-20 seconds&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Developer satisfaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;😤&lt;/td&gt;
&lt;td&gt;😍&lt;/td&gt;
&lt;td&gt;Priceless&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  🧠 Key Learnings and Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Task-Specific Cache Keys Are Game Changers&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Generic key - poor cache utilization&lt;/span&gt;
&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ hashFiles('yarn.lock') }}&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ Task-specific key - much better cache hits&lt;/span&gt;
&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref || github.ref_name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Conditional Dependency Installation Saves Massive Time&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Always check if cache was hit before installing&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.cache.outputs.cache-hit != 'true'&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install --frozen-lockfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;strong&gt;Memory Optimization Prevents Random Failures&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_OPTIONS=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;--max_old_space_size=8192&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; turbo run build"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;strong&gt;Comprehensive Cache Paths Matter&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;~/.turbo      # User-level cache&lt;/span&gt;
  &lt;span class="s"&gt;.turbo        # Project-level cache&lt;/span&gt;
  &lt;span class="s"&gt;~/.cache/yarn # Yarn's cache&lt;/span&gt;
  &lt;span class="s"&gt;**/node_modules # All node_modules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;strong&gt;Smart Cache Hierarchy Provides Best Fallbacks&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref }}  # Exact match&lt;/span&gt;
  &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-main                    # Same task, main branch&lt;/span&gt;
  &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-                        # Same task, any branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 Beyond Basic Optimization: Advanced Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Parallel vs Sequential Jobs
&lt;/h3&gt;

&lt;p&gt;We experimented with both approaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Option A: Parallel execution (current)&lt;/span&gt;
&lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Option B: Sequential execution&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... lint job&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lint&lt;/span&gt; &lt;span class="c1"&gt;# Wait for lint to pass&lt;/span&gt;
    &lt;span class="c1"&gt;# ... build job&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verdict&lt;/strong&gt;: Parallel wins for speed, but sequential is better for cost optimization and fail-fast scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branch-Aware Caching Strategy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Use actual branch name for PR caching&lt;/span&gt;
&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref || github.ref_name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures each feature branch maintains its own cache while falling back to main branch cache when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏆 Impact on Developer Experience
&lt;/h2&gt;

&lt;p&gt;The results speak for themselves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚡ 70% faster warm builds&lt;/strong&gt; - Developers get feedback in 2-3 minutes instead of 8-10&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;💰 Reduced CI costs&lt;/strong&gt; - Fewer compute minutes = lower GitHub Actions bill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔄 Faster iteration cycles&lt;/strong&gt; - Quick feedback loop encourages more frequent commits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;😊 Happier developers&lt;/strong&gt; - No more coffee breaks waiting for CI&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔧 Implementation Guide for Your Project
&lt;/h2&gt;

&lt;p&gt;Want to implement these optimizations in your monorepo? Here's a step-by-step guide:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Audit Your Current Workflow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check your current CI run times&lt;/li&gt;
&lt;li&gt;Identify which steps take the longest&lt;/li&gt;
&lt;li&gt;Look for redundant operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Implement Smart Caching
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add these patterns to your workflow&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; optimize-ci
&lt;span class="c"&gt;# Update your .github/workflows/*.yml files&lt;/span&gt;
&lt;span class="c"&gt;# Test with a small change&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Monitor and Iterate
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Watch your GitHub Actions dashboard&lt;/li&gt;
&lt;li&gt;Track cache hit rates&lt;/li&gt;
&lt;li&gt;Measure before/after performance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Consider Your Monorepo Structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Single repo with multiple packages? ✅ This approach works great&lt;/li&gt;
&lt;li&gt;Independent repos? Consider different cache strategies&lt;/li&gt;
&lt;li&gt;Hybrid setup? Mix and match techniques&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤔 Common Pitfalls and How to Avoid Them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Over-Caching&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Don't cache everything&lt;/span&gt;
&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;~/.cache&lt;/span&gt;
  &lt;span class="s"&gt;node_modules&lt;/span&gt;
  &lt;span class="s"&gt;dist&lt;/span&gt;
  &lt;span class="s"&gt;.next&lt;/span&gt;
  &lt;span class="s"&gt;.turbo&lt;/span&gt;
  &lt;span class="s"&gt;# ... this gets messy&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ Be selective and specific&lt;/span&gt;
&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;~/.turbo&lt;/span&gt;
  &lt;span class="s"&gt;.turbo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Cache Key Collisions&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Too generic - causes conflicts&lt;/span&gt;
&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-cache&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ Include all relevant context&lt;/span&gt;
&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.head_ref || github.ref_name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;strong&gt;Forgetting Memory Limits&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Large TypeScript monorepos can easily hit Node.js memory limits. Always set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--max_old_space_size=8192"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔮 Future Optimizations
&lt;/h2&gt;

&lt;p&gt;We're not stopping here! Next up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker layer caching&lt;/strong&gt; for even faster container builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed task execution&lt;/strong&gt; using GitHub's matrix strategy more creatively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent test splitting&lt;/strong&gt; to parallelize test suites&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build artifact sharing&lt;/strong&gt; between workflows&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://turbo.build/repo/docs/core-concepts/caching" rel="noopener noreferrer"&gt;Turbo Caching Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows" rel="noopener noreferrer"&gt;GitHub Actions Caching Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yarnpkg.com/features/zero-installs" rel="noopener noreferrer"&gt;Yarn Caching Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💬 What's Your Experience?
&lt;/h2&gt;

&lt;p&gt;Have you optimized your CI pipeline recently? What techniques worked best for your team? Drop a comment below and share your optimization wins!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building fast, reliable CI/CD pipelines is an art and a science. The key is measuring, experimenting, and iterating. Happy coding! 🚀&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🏷️ Tags
&lt;/h2&gt;

&lt;h1&gt;
  
  
  GitHubActions #CI #Monorepo #Turbo #Performance #DevOps #TypeScript #React #Caching
&lt;/h1&gt;

</description>
      <category>performance</category>
      <category>cicd</category>
      <category>productivity</category>
      <category>github</category>
    </item>
    <item>
      <title>Global state management in React apps</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Wed, 17 Sep 2025 05:18:29 +0000</pubDate>
      <link>https://dev.to/abhilashlr/global-state-management-in-react-apps-48lg</link>
      <guid>https://dev.to/abhilashlr/global-state-management-in-react-apps-48lg</guid>
      <description>&lt;h1&gt;
  
  
  Choosing the Right Way to Manage Global State in React Apps
&lt;/h1&gt;

&lt;p&gt;When building modern React applications, one of the most common questions teams face is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we manage state that needs to be accessed by deeply nested components?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's tempting to reach for React's Context API or start prop-drilling, but as apps grow, these approaches often lead to performance bottlenecks and complex code paths.&lt;/p&gt;

&lt;p&gt;In one of our recent projects, we had this exact situation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;React Query&lt;/code&gt; was already in place for server state (API-driven data).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Zustand&lt;/code&gt; was used for certain client-side needs (local storage, undo/redo).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;External libraries brought in their own Context providers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we needed a way to add a global, app-wide local state — accessible from any level of the component tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Principles: Different Types of State
&lt;/h2&gt;

&lt;p&gt;Before deciding how to manage state, it's important to separate server state from client state:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server state:&lt;/strong&gt; Data that comes from APIs (users, posts, settings). Best handled by React Query, which gives us caching, background updates, and invalidation out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client state:&lt;/strong&gt; Data that lives only in the app (UI toggles, local preferences, temporary form state). This is where tools like Zustand shine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Context for Everything?
&lt;/h2&gt;

&lt;p&gt;React Context has its place — things like theming, localization, and authentication are great fits. But for frequently changing state, Context introduces two problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Performance – any update can re-render the entire tree of consumers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability – as state grows, multiple contexts quickly become hard to maintain.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Zustand Works Better Here
&lt;/h2&gt;

&lt;p&gt;Zustand is lightweight, scalable, and avoids the pitfalls of Context. Each component can subscribe only to the part of the store it cares about, so re-renders are isolated.&lt;/p&gt;

&lt;p&gt;Here's a simple example:&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;// store/globalStore.ts&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;create&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;zustand&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;GlobalState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&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="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;isSidebarOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;toggleSidebar&lt;/span&gt;&lt;span class="p"&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="k"&gt;void&lt;/span&gt;&lt;span class="p"&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;useGlobalStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GlobalState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&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="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;isSidebarOpen&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;toggleSidebar&lt;/span&gt;&lt;span class="p"&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="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&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="na"&gt;isSidebarOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSidebarOpen&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;Usage anywhere in the app:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useGlobalStore&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;@/store/globalStore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SidebarToggle&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;isOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useGlobalStore&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSidebarOpen&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;toggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useGlobalStore&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toggleSidebar&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggle&lt;/span&gt;&lt;span class="si"&gt;}&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;isOpen&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Close Sidebar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Open Sidebar&lt;/span&gt;&lt;span class="dl"&gt;"&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;button&lt;/span&gt;&lt;span class="p"&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;No prop drilling. No unnecessary re-renders. Just clean, predictable state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices and Closing thoughts
&lt;/h2&gt;

&lt;p&gt;As React apps scale, state management decisions compound. The best approach is not to introduce new tools every time a new need arises, but to leverage existing patterns wisely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use React Query for server state – don't reinvent async state management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Zustand for client/global state – scalable, performant, and easy to adopt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep Context minimal – only for true cross-cutting concerns like theme or i18n.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Split Zustand stores into slices – avoid a monolithic &lt;strong&gt;"god store"&lt;/strong&gt; by modularizing concerns (UI slice, preferences slice, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination keeps the app consistent, performant, and easy for engineers to maintain.&lt;/p&gt;




&lt;p&gt;✨ Curious to hear from other engineers — how do you manage global state in your React apps?&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>react</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Ember performance tweaks</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Mon, 27 Apr 2020 07:26:25 +0000</pubDate>
      <link>https://dev.to/abhilashlr/ember-performance-tweaks-5131</link>
      <guid>https://dev.to/abhilashlr/ember-performance-tweaks-5131</guid>
      <description>&lt;p&gt;I wrote down my experience on performance tweaks that went well for the Ember apps I worked with. Some of them are simple notes that I wrote down while I was tweaking the build pipelines. I share those tips and tricks that could make your Ember apps faster, accessible and awesome!&lt;/p&gt;

&lt;p&gt;Before I talk about how to solve for Ember app's performance tips, I presume you have a good understanding of what Ember is, and you have a basic knowledge of its ecosystem, build pipelines, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key areas to optimise Ember apps:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://abhilashlr.in/ember-performance-tweaks-part-1" rel="noopener noreferrer"&gt;Improving build timelines &amp;amp; optimising build size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://abhilashlr.in/ember-performance-tweaks-part-2" rel="noopener noreferrer"&gt;Optimising asset cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://abhilashlr.in/ember-performance-tweaks-part-3" rel="noopener noreferrer"&gt;Building and optimising Ember apps for SEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Improving the accessibility of Ember apps.&lt;/li&gt;
&lt;li&gt;Making Ember apps installable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As I continue to write my blog posts, I will update this post with all the relevant links.&lt;/p&gt;

</description>
      <category>ember</category>
      <category>performance</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Refactoring Ember templates - Quick tip!</title>
      <dc:creator>abhilashlr</dc:creator>
      <pubDate>Mon, 20 Apr 2020 14:01:11 +0000</pubDate>
      <link>https://dev.to/abhilashlr/refactoring-ember-templates-quick-tip-1pkc</link>
      <guid>https://dev.to/abhilashlr/refactoring-ember-templates-quick-tip-1pkc</guid>
      <description>&lt;p&gt;Originally posted at: &lt;a href="https://abhilashlr.in/ember-refactor-templates" rel="noopener noreferrer"&gt;https://abhilashlr.in/ember-refactor-templates&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of us might have written complex if-else logic in our Ember templates (a.k.a HTMLBars templates). There could be scenarios where certain lines of code look almost similar, but we keep them as-is for readability. Perhaps, writing a shorter version of it makes the entire file readable with concise LoC. Let's assume a simple bootstrap-like HTML + text content (using translations):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{#if isInfo}}
  &amp;lt;i class="icon-info"&amp;gt;&amp;lt;/i&amp;gt;
  {{t "info.title"}}
{{else if isWarn}}
  &amp;lt;i class="icon-warn"&amp;gt;&amp;lt;/i&amp;gt;
  {{t "warn.title"}}
...
{{/if}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the problem with this LoC is readability in itself if there were similar HTML all around this page's template file. In such cases, you can opt for a computed property that finds the right key and use it on the template. Something like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;i class="icon-{{type}}"&amp;gt;&amp;lt;/i&amp;gt;{{t (concat type ".title")}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazing, now we have 13 lines shortened to 1 line.&lt;br&gt;
Bonus Tip 1:&lt;/p&gt;

&lt;p&gt;Let's say you have no control over the t helper's key and that it would be a specific use case based on your API or 3rd party services.&lt;/p&gt;

&lt;p&gt;In such scenarios, one way is to optimise this in your JS code, based on switch conditions and decide which key you would want to use. That would be unit-testable code base. Let's say you would want to do this if-else in the template like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;i class="icon-alert margin-right-20"&amp;gt;&amp;lt;/i&amp;gt;
{{#if condition1}}
  {{t "something_from_server1"}}
{{else if condition2}}
  {{t "something_from_3rdparty_service"}}
{{else}}
  {{t "generic_message"}}
{{/if}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you notice a bit closer, there is a class called margin-right-20 on the icon. Generally, icon + text lines on the UI tend to have a minor space between them. Since our templates use a sequence of if-else conditions, this internally introduces whitespace around the moustache ({}) code that is present. And if you see closer on the UI, the icon + text would end up having a 20px margin on the right and a white space introduced just before the TextNode (generated by the translation helper).&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%2Fabhilashlr-img-optimizer.herokuapp.com%2F7ztcRX7ynvTaxJXHniA0lvgL7Y8%3D%2F1024x0%2Fsmart%2Fhttps%3A%2F%2Fabhilashlr.in%2Fassets%2Fimages%2Fblogs%2Fember%2Fwhitespace-3b.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%2Fabhilashlr-img-optimizer.herokuapp.com%2F7ztcRX7ynvTaxJXHniA0lvgL7Y8%3D%2F1024x0%2Fsmart%2Fhttps%3A%2F%2Fabhilashlr.in%2Fassets%2Fimages%2Fblogs%2Fember%2Fwhitespace-3b.png" alt="white-space-image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like I said before, one way to fix this is by generating the key on the javascript side and rendering the same in a single line. Or if you still like to do this on your template, you can use the ~ character by the braces, something like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;i class="icon-alert margin-right-20"&amp;gt;&amp;lt;/i&amp;gt;
{{~#if condition1}}
  {{~t "something_from_server1"}}
{{~else if condition2}}
  {{~t "something_from_3rdparty_service"}}
{{~else}}
  {{~t "generic_message"}}
{{/if}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And inspecting the rendered HTML in the Browser inspector shows&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%2Fabhilashlr-img-optimizer.herokuapp.com%2Flk7aR4eEj3z8UKVBiEGjbEn9JLE%3D%2F1024x0%2Fsmart%2Fhttps%3A%2F%2Fabhilashlr.in%2Fassets%2Fimages%2Fblogs%2Fember%2Fwhitespace-4b.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%2Fabhilashlr-img-optimizer.herokuapp.com%2Flk7aR4eEj3z8UKVBiEGjbEn9JLE%3D%2F1024x0%2Fsmart%2Fhttps%3A%2F%2Fabhilashlr.in%2Fassets%2Fimages%2Fblogs%2Fember%2Fwhitespace-4b.png" alt="image-without-whitespace" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a good read on the same: Whitespace Control.&lt;br&gt;
Bonus Tip 2:&lt;/p&gt;

&lt;p&gt;If the same HTML of icon and text's parent holds a display: flex or display: inline-flex, despite whitespace being present, it would not be visible on the UI output.&lt;/p&gt;

&lt;p&gt;Here's a twiddle for you to try.&lt;/p&gt;

</description>
      <category>ember</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>html</category>
    </item>
  </channel>
</rss>
