<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Shubham Jha | Dev Blog]]></title><description><![CDATA[Deep dives into system design, enterprise development, and modern web technologies — written from 6+ years of real engineering experience. No toy projects. Code that actually ships.]]></description><link>https://blog.shubhamjha.com</link><image><url>https://cdn.hashnode.com/uploads/logos/69d1293c6792e486f6810f5c/4178f40b-ca98-4bfe-84be-1aca4a2a56d2.webp</url><title>Shubham Jha | Dev Blog</title><link>https://blog.shubhamjha.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 12:06:46 GMT</lastBuildDate><atom:link href="https://blog.shubhamjha.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Master React and Next.js in 2026]]></title><description><![CDATA[The Shift You Cannot Ignore
The defining change of 2026 isn't a new API or a clever hook. React has moved from client-first to server-native. The React Compiler now handles what developers once manage]]></description><link>https://blog.shubhamjha.com/how-to-master-react-and-next-js-in-2026</link><guid isPermaLink="true">https://blog.shubhamjha.com/how-to-master-react-and-next-js-in-2026</guid><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[learning]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Shubham Jha]]></dc:creator><pubDate>Tue, 07 Apr 2026 05:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69d1293c6792e486f6810f5c/744af3ef-b3aa-4992-959d-9a1efa0b61e5.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Shift You Cannot Ignore</h2>
<p>The defining change of 2026 isn't a new API or a clever hook. React has moved from <strong>client-first</strong> to <strong>server-native</strong>. The React Compiler now handles what developers once managed manually. The boundary between frontend and cloud infrastructure has dissolved. AI tooling is table stakes - not a nice-to-have.</p>
<p>This changes how learning works. A course that walks you through <code>useState</code> and <code>useEffect</code> isn't a foundation - it's technical debt from day one. The resources below are selected for one reason: do they build engineers who can <em>architect</em> systems, or just developers who follow tutorials?</p>
<p>I've worked through most of what's on this list — some of it while building production systems, some while trying to close gaps I only noticed when something broke in a way I couldn't explain. The React Compiler handles your performance optimizations now. AI tooling generates your boilerplate. What neither produces is judgment - the calls around what to build, what to cut, and what to push back on entirely. That's the gap these resources are selected to close.</p>
<p>They're arranged as a sequence. Start where you are, move down when something stops being challenging.</p>
<hr />
<h2>01. <a href="https://www.freecodecamp.org/learn/front-end-development-libraries-v9/">freeCodeCamp Front End Dev Libraries</a>: The Honest Starting Point</h2>
<p>Most roadmaps skip this one because it's free and therefore assumed to be lightweight. It isn't. The curriculum is one of the most structurally complete in a way most paid courses aren't - theory is never left unapplied, each concept module is followed immediately by something you build.</p>
<p>The sequencing is what sets it apart. It moves from <strong>React Fundamentals</strong> through <strong>state management</strong>, <strong>routing</strong>, <strong>performance</strong>, <strong>testing</strong>, and <strong>CSS frameworks</strong> in an order that mirrors how real codebases are actually structured. <strong>TypeScript</strong> comes last - not as an afterthought, but because that's where it makes sense: once you have enough context to understand why it exists rather than just memorizing syntax.</p>
<p>At the end sits a <strong>certification exam</strong> that requires demonstrated competency, not just completion. And it costs nothing.</p>
<p><strong>Who this is for:</strong> Developers new to the ecosystem, or anyone whose foundation came from tutorials that skipped the unglamorous parts. If any module here surprises you, that's your gap.</p>
<hr />
<h2>02 - <a href="https://nextjs.org/learn">Next.js Learn</a>: Canonical Architecture from the Source</h2>
<p>The official Next.js docs are no longer a reference manual. By 2026 they've become a project-based curriculum built around <strong>React Server Components</strong> and <strong>Partial Pre-rendering</strong> - and it's the only resource that reflects actual Vercel architectural intent rather than a third party's reading of it.</p>
<p>The flagship tutorial has moved beyond the <strong>Dashboard starter</strong>. It now covers AI-First streaming patterns - how to pipe data directly to the UI while managing server-side logic without a traditional API layer. You get the constraints, the reasoning, and the defaults from the people who designed the framework. That's harder to find than it sounds.</p>
<p><strong>Who this is for:</strong> Anyone whose Next.js intuition formed before the <strong>App Router</strong> stabilized. The official docs have changed more in the past two years than most third-party courses have - even experienced developers should check back in.</p>
<hr />
<h2>03 - <a href="https://joyofreact.com/">The Joy of React</a>: Building Mental Models That Last</h2>
<p>Syntax changes. Mental models don't. <strong>Josh W. Comeau's</strong> <em>The Joy of React</em> is built on this premise. Where most courses show you <em>what</em> to write, Josh shows you <em>why</em> it behaves the way it does - through custom visualizers that render the React Fiber tree in motion as state changes propagate.</p>
<p>For 2026, the course covers the React Compiler directly - not just what it does, but how to write code the compiler can actually reason about. The <code>useMemo</code> and <code>useCallback</code> instincts you built up over years of client-side React aren't just unnecessary now. Applied incorrectly, they fight the compiler. Joy of React works those instincts out of you.</p>
<p>If you can only invest in one paid resource this year, I'd pick this one. Architectural knowledge compounds on a solid mental model. Without one, everything else - Epic Web, Frontend Masters, the official docs - becomes memorization.</p>
<p><strong>Who this is for:</strong> Developers who can ship features but feel like they're guessing rather than reasoning. Especially useful if RSC still feels like something you work around rather than reach for.</p>
<hr />
<h2>04 - <a href="https://www.epicweb.dev/full-stack">Epic Web</a>: The Full-Stack Professional Curriculum</h2>
<p>Most courses treat the browser as the whole world. <strong>Kent C. Dodds'</strong> Epic Web treats it as one node in a larger system - and that difference in scope is what separates developers who implement features from engineers who can own a production system when things wrong.</p>
<p>By 2026, the curriculum covers <strong>SQLite</strong> at the Edge, <strong>Passkey authentication</strong>, end-to-end type safety with modern TypeScript, <strong>race condition handling</strong>, optimistic UI, and <strong>cache invalidation</strong> at scale. The project-based structure is unforgiving in the right way: production-grade decisions at every step, no deferring the hard parts.</p>
<p>The gap Epic Web closes isn't syntactic - it's dispositional. Knowing how to handle errors, write tests, and reason about deployment is what makes the difference between a developer who needs supervision and one who can be trusted with a codebase that generates revenue. That transition doesn't come from building more side projects.</p>
<p><strong>Who this is for:</strong> Mid-level developers ready to move into senior or lead roles. If you've never owned a production incident, debugged a race condition under load, or made a caching decision that affected real users - this curriculum that closes that gap.</p>
<hr />
<h2>05 - <a href="https://frontendmasters.com/learn/react/">Frontend Masters</a>: Architecture at Scale</h2>
<p>Frontend Masters answers a different question than the other resources on this list. Not <strong>how do I build this feature?</strong> but <strong>how do I keep a large system from becoming unmaintainable in two years?</strong></p>
<p>The 2026 curriculum from instructors like <strong>Scott Moss</strong> and <strong>Lydia Hallie</strong> covers problems that only surface at scale: splitting large Next.js applications into <strong>micro-frontends</strong> without tanking performance, integrating <strong>LLM agents</strong> into production React components via modern SDKs, tracking down <strong>millisecond-level bottlenecks</strong> with the 2026 iteration of Chrome DevTools. These aren't side project problems. They come up when you're responsible for a system with real traffic, a real team, and consequences when you get the architecture wrong.</p>
<p>At the senior and lead level, your biggest leverage isn't the code you write - it's the decisions that prevent bad code from being written six months from now. Frontend Masters is the only resource here built specifically to develop that judgment.</p>
<p><strong>Who this is for:</strong> Senior developers and technical leads working on existing production systems. If your day-to-day involves inheriting and maintaining a large codebase rather than building greenfield projects, this fits your reality better than anything else on the list. For a concrete starting point on production performance — a real Next.js App Router codebase, a 3.2s LCP, and the specific fixes that moved it — <a href="https://shubhamjha.com/blog/core-web-vitals-nextjs-optimization">this Core Web Vitals case study</a> pairs well with the Frontend Masters content.</p>
<hr />
<h2>06 - AI-Native Learning: The Methodology That Replaced Courses</h2>
<p>The most important resource of 2026 isn't a platform - it's a practice. With tools like <strong>Cursor</strong>, <strong>v0.dev</strong>, and <strong>Claude Code</strong> standard in most workflows, the fastest path to mastery isn't passive consumption anymore. It's deliberate synthesis: use AI to generate complexity, then deconstruct what it produced and rebuild it by hand.</p>
<p>Not letting the AI write your code - using it as a pair programmer who is always available and never impatient, and treating its output as raw material for understanding rather than a final answer. The developers advancing fastest right now already work this way.</p>
<h3>The Three-Step Synthesis Loop</h3>
<p><strong>1. Generate and audit.</strong> Prompt an AI to build a complex feature - a streaming search component with PPR, a Server Action with optimistic UI, a Passkey authentication flow. Study the output critically before running it.</p>
<p><strong>2. Force the explanation.</strong> Ask why each pattern was chosen. <em>Why a Server Action here instead of a Route Handler? Why is this a Client Component when it seems like it could be a Server Component?</em> Vague answers mean the generation was shallow. Push until the reasoning is specific.</p>
<p><strong>3. Rebuild from scratch.</strong> Close the output and rewrite it manually. If you can't, you've found exactly what you don't yet understand - which is more useful than getting the feature shipped.</p>
<p>No course updates fast enough for this. React, Next.js, and TypeScript ship on a weekly cadence now - by the time someone re-records a video, it's already stale. AI-native learning keeps you current by default, because you're always working against the live state of the ecosystem, not a snapshot of it.</p>
<p><strong>Who this is for:</strong> Every level. The features you generate and audit should match where you are in the sequence above.</p>
<hr />
<h2>The Verdict: A Sequence, Not a Menu</h2>
<table>
<thead>
<tr>
<th>Resource</th>
<th>Best for</th>
<th>The bottleneck it fixes</th>
</tr>
</thead>
<tbody><tr>
<td>freeCodeCamp v9</td>
<td>New → Junior</td>
<td>Shaky fundamentals and no verifiable proof of skill</td>
</tr>
<tr>
<td>Next.js Learn</td>
<td>All levels</td>
<td>Outdated architectural intuitions</td>
</tr>
<tr>
<td>Joy of React</td>
<td>Junior → Mid</td>
<td>Guessing rather than reasoning</td>
</tr>
<tr>
<td>Epic Web</td>
<td>Mid → Senior</td>
<td>Front-end skills without production discipline</td>
</tr>
<tr>
<td>Frontend Masters</td>
<td>Senior → Lead</td>
<td>Feature-building without systems thinking</td>
</tr>
<tr>
<td>AI-Native Learning</td>
<td>All levels</td>
<td>Keeping pace with a weekly release cycle</td>
</tr>
</tbody></table>
<p>One question worth sitting with: <em>at what point in this list does the material stop feeling challenging and start feeling familiar?</em> That's your actual level. It's probably one step lower than you assumed.</p>
<p>If you're new to the ecosystem and want to start from the actual foundations — JavaScript, HTML, CSS through to your first React component — <a href="https://shubhamjha.com/blog/learn-javascript-html-css-react-beginners-2026">learning web development in 2026</a> maps out the right sequence before diving into any of the resources above.</p>
<p>If you're building a React + Next.js product and want the engineering and architecture to match what these resources teach, you can explore my <a href="https://shubhamjha.com/projects">projects</a> or <a href="https://shubhamjha.com/contact">contact me</a> to discuss your roadmap.</p>
]]></content:encoded></item><item><title><![CDATA[Learn JavaScript, HTML, CSS and React in 2026]]></title><description><![CDATA[Why most beginners never finish learning web development
They start with the wrong question. What framework should I learn? The real question is: do I actually understand what's happening on the scree]]></description><link>https://blog.shubhamjha.com/learn-javascript-html-css-and-react-in-2026</link><guid isPermaLink="true">https://blog.shubhamjha.com/learn-javascript-html-css-and-react-in-2026</guid><category><![CDATA[HTML5]]></category><category><![CDATA[CSS]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Shubham Jha]]></dc:creator><pubDate>Mon, 06 Apr 2026 02:19:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69d1293c6792e486f6810f5c/89434107-0cb9-464b-8d30-d455f6b4a7e4.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Why most beginners never finish learning web development</h2>
<p>They start with the wrong question. <em>What framework should I learn?</em> The real question is: <em>do I actually understand what's happening on the screen?</em> Most tutorials drop you into React on day one. You copy code, it runs, and you move on without knowing why it worked.</p>
<p>I've seen this pattern repeatedly — developers who can build features but freeze when something breaks at the DOM level, because they skipped the part that explains what the DOM actually is. The sequence below is the order I wish I'd learned in.</p>
<p>There's a fix for that. It starts with a product card, a plain JavaScript function, and ends with that same card as a React component. Six concepts, in one specific order, each making the next click into place. By the time you write the React version, you won't just know <em>how</em> to write it. You'll know why every single line is there.</p>
<h2>1. JS Basics</h2>
<p>JavaScript is what makes web pages do things: maps that move, content that updates without a reload, buttons that respond. Before you touch the DOM or write a component, you need to think like a programmer, which is different from knowing syntax.</p>
<p>The focus here isn't memorisation. It's learning to ask <em>why</em> code behaves the way it does.</p>
<p>A good early example — why does this function return nothing?</p>
<pre><code class="language-javascript">// Why does this print "undefined"?
function getProduct() {
  const name = "Laptop"
}
console.log(getProduct()) // undefined — no return statement

// Fixed:
function getProduct() {
  const name = "Laptop"
  return name
}
console.log(getProduct()) // "Laptop"
</code></pre>
<p>Seems obvious once you see it. Most beginners spend weeks hitting this wall in different forms because they <em>never stopped to ask the question</em>. Getting into the habit of asking it is most of what this section is about.</p>
<p>The section works through variables, types, functions, objects, arrays, and conditionals, then goes deeper into scope, the call stack, and debugging. Those last three aren't advanced topics. They're what separates developers who can read error messages from developers who can't.</p>
<h2>2. HTML Basics</h2>
<p>HTML is the structure of a web page, the part browsers parse before anything else runs. Get it wrong and nothing else works right, no matter how good your JavaScript is.</p>
<p>The key concept is the Document Object Model: browsers read HTML into a tree of nodes, and that tree is what JavaScript actually operates on. Knowing what the <code>document</code> object is, how <code>querySelector</code> works, and how to attach event listeners turns DOM errors from mysterious into something you can actually fix.</p>
<pre><code class="language-javascript">// Select the product list container from the HTML
const list = document.querySelector("#product-list")

// Listen for a click on any card inside it
list.addEventListener("click", (event) =&gt; {
  if (event.target.tagName === "BUTTON") {
    console.log("Add to cart clicked")
  }
})
</code></pre>
<h2>3. CSS Basics</h2>
<p>An app that looks broken feels broken, even if it works perfectly. CSS is what turns a functional prototype into something people trust enough to use.</p>
<p>The two layout systems worth learning first are flexbox and grid. Not because they're trendy, but because they replaced a decade of float hacks and table abuse, and they're what every modern UI is actually built with.</p>
<p>Here's flexbox laying out the product grid:</p>
<pre><code class="language-css">/* Product grid layout */
.product-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.5rem;
  padding: 2rem;
}

.product-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 280px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 1.25rem;
}
</code></pre>
<p>Two properties and your grid wraps and centers itself at any screen size. Before flexbox, that required a painful combination of floats, clearfixes, and prayers. Now that the card looks right, the next question is how to make it respond to a click, which is where JavaScript and HTML have to work together.</p>
<h2>4. HTML + JS</h2>
<p>React, Vue, Angular — they're all solving the same problem: wiring JavaScript to HTML without making a mess of it. But before you use a tool that does this for you, it's worth seeing what it actually does.</p>
<p>Most developers who say they're comfortable with React have never written a <code>createElement</code> call, which is exactly why their debugging stops at the component boundary. Here's the manual version, no framework, no build step:</p>
<pre><code class="language-javascript">// No framework, no build step — just JS and the DOM
function renderProduct(product) {
  const card = document.createElement("div")
  card.className = "product-card"
  card.innerHTML = `
    &lt;h2&gt;${product.name}&lt;/h2&gt;
    &lt;p&gt;$${product.price}&lt;/p&gt;
    &lt;button&gt;Add to cart&lt;/button&gt;
  `
  document.getElementById("product-list").appendChild(card)
}

renderProduct({ name: "Wireless Headphones", price: 49.99 })
</code></pre>
<p>Every React component you write later is a cleaner version of this. Writing it manually first is what makes the abstraction feel earned rather than arbitrary.</p>
<p>This section builds a minimal version of what React does: creating nodes, updating them dynamically, and splitting logic into modules using both ES and CommonJS syntax. Writing it by hand once is worth more than reading about it ten times.</p>
<h2>5. Web Server + Vite</h2>
<p>Most beginners skip straight to deploying without knowing what deployment actually means, which is why they spend hours debugging production issues that a five-minute mental model would have prevented. When you type a URL into a browser, your computer sends a request to a remote server, which sends back files. That's it.</p>
<p>Node.js lets you run JavaScript on that server. Vite takes your JavaScript, potentially dozens of files, and bundles it into something a browser can load efficiently. Unbundled JavaScript has a ceiling most beginners don't notice until they hit it hard.</p>
<p>Scaffolding a new project with Vite takes one command:</p>
<pre><code class="language-bash">npm create vite@latest my-app -- --template react
cd my-app &amp;&amp; npm install &amp;&amp; npm run dev
</code></pre>
<p>Two seconds later you have a local dev server with hot module replacement, meaning the browser updates the exact component you edited without a full page reload. That's what makes front-end development fast to iterate on. Understanding that Vite is assembling and serving those files is what makes debugging it possible.</p>
<h2>6. React JS</h2>
<p>React is a JavaScript library for building UIs. What makes it useful is that it handles DOM updates for you: describe what the UI should look like given some state, and React figures out the minimum changes needed to get there.</p>
<p>The same product card from Section 4, now as a React component:</p>
<pre><code class="language-jsx">// ProductCard.jsx
import { useState } from "react"

function ProductCard({ name, price }) {
  const [added, setAdded] = useState(false)

  return (
    &lt;div className="product-card"&gt;
      &lt;h2&gt;{name}&lt;/h2&gt;
      &lt;p&gt;${price}&lt;/p&gt;
      &lt;button onClick={() =&gt; setAdded(true)}&gt;{added ? "✓ Added" : "Add to cart"}&lt;/button&gt;
    &lt;/div&gt;
  )
}

export default ProductCard
</code></pre>
<p>Same output. Less code. The button updates without touching the DOM directly because React tracks the state change and handles the re-render.</p>
<p>React components, JSX, hooks, state, reactivity, the component lifecycle — each one makes more sense because you've already seen what it replaces.</p>
<p>Web development has a lot of layers. Most guides hide that by dropping you into the top one. The order here is deliberate: each section exists because the next one needs it. Once the React fundamentals click, the next question is performance — <a href="https://shubhamjha.com/blog/core-web-vitals-nextjs-optimization">Next.js Core Web Vitals 2026</a> shows what slows real production apps down, and it's usually not what beginners expect.</p>
<p>Once this foundation is solid, the next step is learning which React and Next.js patterns matter at a production level — <a href="https://shubhamjha.com/blog/how-to-master-react-nextjs-2026">mastering React and Next.js in 2026</a> maps the resources worth investing in at each stage.</p>
<p>Want to see how these concepts come together in a real product? Browse my <a href="https://shubhamjha.com/projects">projects</a> or <a href="https://shubhamjha.com/contact">reach out</a> — happy to talk through what you're building.</p>
]]></content:encoded></item><item><title><![CDATA[Next.js Core Web Vitals 2026: Why LCP Isn't Just Your Images]]></title><description><![CDATA[The logistics portal I inherited had a 3.2 second LCP. That number had been sitting in a Notion doc labelled known issues for two quarters. Everyone knew it was bad. Nobody could tell you exactly why.]]></description><link>https://blog.shubhamjha.com/next-js-core-web-vitals-2026-why-lcp-isn-t-just-your-images</link><guid isPermaLink="true">https://blog.shubhamjha.com/next-js-core-web-vitals-2026-why-lcp-isn-t-just-your-images</guid><category><![CDATA[Next.js]]></category><category><![CDATA[performance]]></category><category><![CDATA[webdev]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Shubham Jha]]></dc:creator><pubDate>Sun, 05 Apr 2026 04:47:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69d1293c6792e486f6810f5c/eab2eaa0-2736-4d22-8ee4-c1fa18a18d67.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>The <a href="https://shubhamjha.com/projects/shipglobal/index.html">logistics portal</a> I inherited had a 3.2 second LCP. That number had been sitting in a Notion doc labelled <em>known issues</em> for two quarters. Everyone knew it was bad. Nobody could tell you exactly why. If you've already added <code>next/image</code>, turned on compression, and you're still stuck in the high 70s on Lighthouse, the problem is almost certainly not your images.</p>
<p>When we finally fixed it — really fixed it, not just ran Lighthouse and called it a day — page load dropped 40%. Repeat orders went up 74%. Average order value climbed 12%. I'm not claiming performance caused all of that. But 23% of vendors were dropping off in their first session. When you stop losing a quarter of your users before they've done anything, everything else you're working on gets a fairer shot.</p>
<p>Most Core Web Vitals content is written for marketing sites. It tells you to compress images and defer scripts. That's fine for a WordPress blog. For a data-heavy Next.js App Router application, the standard checklist doesn't get you past 75. Google's published thresholds (LCP under 2.5s, CLS under 0.1) are the floor, not the target. If your users are power users who touch your product dozens of times a day, they notice every stutter.</p>
<hr />
<h2>LCP in a Next.js app is probably not your images</h2>
<p>Open Chrome DevTools, run a performance trace, and look at what the LCP element actually is before you touch a single image. If it's text or a data-driven component, image optimisation is the wrong fix.</p>
<p>The LCP element was a dashboard stats card: a <code>&lt;div&gt;</code> with numbers pulled from three separate API calls. The browser was waiting for all three before it could paint anything meaningful in the viewport. The hero image was fine. The data was the bottleneck.</p>
<p>Some of the things that killed our LCP had nothing to do with assets:</p>
<ul>
<li><p>A waterfall of three sequential API calls on first render, each waiting for the previous</p>
</li>
<li><p>A <code>useEffect</code> that fetched critical above-the-fold data client-side instead of server-side</p>
</li>
<li><p>A large <code>"use client"</code> boundary at the page level that forced the entire page to hydrate before any data could render</p>
</li>
</ul>
<p>The fix wasn't clever, but it wasn't just <em>move to</em> <code>Promise.all</code> either. First, <code>fetchRevenue</code> had to be decoupled from the orders response: the original call passed <code>orders.period</code> as a dependency, making true parallelisation impossible until that contract changed. Once decoupled, we moved all three fetches to the server with <code>Promise.all</code> and streamed secondary content below the fold with <code>Suspense</code>. The dashboard stats rendered in the first paint instead of the third.</p>
<pre><code class="language-tsx">// Before: client-side waterfall
const [stats, setStats] = useState(null)
useEffect(() =&gt; {
  fetchOrderCount().then(async (orders) =&gt; {
    const revenue = await fetchRevenue(orders.period)
    const shipments = await fetchShipments()
    setStats({ orders, revenue, shipments })
  })
}, [])

// After: server-side parallel fetch
// Note: refactored fetchRevenue to accept date range from URL params instead of chaining off orders
async function DashboardStats() {
  const [orders, revenue, shipments] = await Promise.all([fetchOrderCount(), fetchRevenue(), fetchShipments()])
  return &lt;StatsCard orders={orders} revenue={revenue} shipments={shipments} /&gt;
}
</code></pre>
<p>The <code>useEffect</code> version waited sequentially for three round trips. The server version waited for the slowest of three parallel requests, and the result arrived in the initial HTML payload, not after hydration.</p>
<h3>The <code>"use client"</code> boundary that's silently inflating your bundle</h3>
<p>The most common hidden LCP regression in Next.js App Router apps: a page-level <code>"use client"</code> that got added for one interactive element and never revisited. Teams added it for a dropdown, a toast, a modal. The entire component tree beneath that boundary ships as client JavaScript and hydrates before rendering.</p>
<p>Push the boundary down to the smallest component that actually needs interactivity. A search input is a client component. The page layout, the data table, the navigation: those don't need to be. If Server Components and the App Router are still coming together for you, <a href="https://shubhamjha.com/blog/how-to-master-react-nextjs-2026">mastering React and Next.js in 2026</a> has the right sequence to build that intuition before performance work makes sense.</p>
<pre><code class="language-tsx">// Before: entire page is a client component
"use client"
export default function OrdersPage() {
  // 800 lines of component, all shipped to client
}

// After: only the search is a client component
export default async function OrdersPage() {
  const orders = await fetchOrders()
  return (
    &lt;main&gt;
      &lt;OrderSearch /&gt; {/* "use client" */}
      &lt;OrderTable orders={orders} /&gt; {/* server component, no JS shipped */}
    &lt;/main&gt;
  )
}
</code></pre>
<p>Moving to proper island architecture cut the client JavaScript bundle by roughly 35%. That reduced Time to Interactive directly, which improved INP scores as well. If you're still working out which components belong on the server vs. the client, <a href="https://shubhamjha.com/blog/building-production-ready-react-apps">building production-ready React apps</a> covers the hook and component architecture patterns that make these boundaries easier to enforce consistently.</p>
<p>The API waterfall and boundary fixes got us most of the way. The last LCP gains came from images — and not the fixes you'd expect.</p>
<h3>Images: the <code>priority</code> attribute is probably doing more harm than good</h3>
<p>If you're already using <code>next/image</code>, the remaining gains are in details most teams skip.</p>
<p>The biggest remaining issue was <code>priority</code> abuse. Some teams (including ours) mark multiple images as <code>priority</code> to ensure they preload. The problem: <code>priority</code> adds a <code>&lt;link rel="preload"&gt;</code> tag for each image. With three or four of them, the browser competes for bandwidth on resources it doesn't all need immediately.</p>
<p>One <code>priority={true}</code>, on the LCP candidate. Everything else loads lazily.</p>
<p>The second issue was missing <code>sizes</code> on responsive images. Without <code>sizes</code>, Next.js generates a srcset but the browser defaults to <code>100vw</code> as the assumed display width. On a 375px Retina screen at 2x DPR, that targets a 750px image, which for a narrow content column can be 2–3× more than necessary.</p>
<pre><code class="language-tsx">&lt;Image
  src="/hero.webp"
  alt="Dashboard preview"
  width={1200}
  height={630}
  priority
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
/&gt;
</code></pre>
<p>The <code>sizes</code> attribute tells the browser exactly which image to download at each viewport width. On mobile, this alone can save hundreds of kilobytes per page load.</p>
<hr />
<h2>CLS at 0.18: why vendors couldn't explain why the product felt broken</h2>
<p>CLS is deceptively hard to debug because it often doesn't appear in Lighthouse. Lighthouse measures CLS on a simulated load with a clean cache. Real CLS happens when:</p>
<ul>
<li><p>A user has slow network and fonts load late</p>
</li>
<li><p>A banner or cookie consent appears after the initial paint</p>
</li>
<li><p>A data-driven component changes height after content loads</p>
</li>
</ul>
<p>Our CLS was 0.18. In practice, vendors saw the order table jump down when the pagination bar loaded. A small thing. It happened on every page visit. Multiply that by 30 visits a day per vendor across hundreds of vendors and it's a constant source of friction that nobody files a bug report about. Nobody files a bug that says "the page jumped." They just quietly stop using the product.</p>
<h3>Fonts cause layout shift even when they load "correctly"</h3>
<p>Fallback fonts have different metrics than your custom font. When Inter loads, text that was rendering in Arial reflows to match Inter's line height, letter spacing, and word spacing. Paragraphs shift. Buttons resize. That's your CLS.</p>
<p><code>next/font</code> handles the loading. The real fix is font metric override: CSS descriptors that make your fallback font match your custom font's dimensions closely enough that the reflow is imperceptible.</p>
<pre><code class="language-ts">import { Inter } from "next/font/google"

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
  fallback: ["system-ui", "Arial"],
  adjustFontFallback: true, // Next.js calculates override metrics automatically
})
</code></pre>
<p><code>adjustFontFallback: true</code> generates <code>size-adjust</code>, <code>ascent-override</code>, <code>descent-override</code>, and <code>line-gap-override</code> for the fallback. The visual difference between fallback and loaded font becomes small enough that layout doesn't shift meaningfully.</p>
<h3>Dynamic content: reserving space before you know the size</h3>
<p>The hardest CLS to fix is from content whose size you don't know yet: banners, notification bars, data-driven cards, ad slots. The naive solution is to avoid adding things dynamically. The real solution is to reserve space.</p>
<p>For fixed-height elements like banners, use a min-height wrapper even when the content is empty:</p>
<pre><code class="language-tsx">&lt;div style={{ minHeight: "48px" }}&gt;{banner &amp;&amp; &lt;Banner message={banner.message} /&gt;}&lt;/div&gt;
</code></pre>
<p>For data-driven content where you don't know the final height, skeleton loaders with accurate proportions are better than no loaders. A skeleton that's 80px tall and content that's 120px tall still causes a shift.</p>
<p>The largest CLS contributor was the order stats row: four cards that loaded with real data after the page rendered. Each card had a different final height depending on the number inside. We fixed it by setting a fixed card height and truncating overflowing numbers, then exposing a tooltip for the full value. CLS went from 0.18 to 0.04. The page stopped moving.</p>
<hr />
<h2>Why performance degrades after you fix it — and how to break the cycle</h2>
<p>Performance regresses because improvements and code changes happen in different places. Lighthouse scores feel good locally and break in production. You need to measure in both places, for different reasons.</p>
<p>Locally, Lighthouse tells you what's theoretically possible. Production RUM (real user monitoring) tells you what's actually happening.</p>
<p>For production monitoring, the Web Vitals JS library piped into your analytics is the minimum viable setup:</p>
<pre><code class="language-ts">import { onLCP, onINP, onCLS } from "web-vitals"
import type { Metric } from "web-vitals"

function sendToAnalytics(metric: Metric) {
  const payload = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // "good", "needs-improvement", "poor"
    page: window.location.pathname,
  })
  // Quick start: navigator.sendBeacon("/api/vitals", payload) — sends as text/plain
  navigator.sendBeacon("/api/vitals", new Blob([payload], { type: "application/json" }))
}

onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
</code></pre>
<p>One caveat: fire this on a sample of sessions (10–20%) rather than every user, or you'll flood your analytics endpoint on high-traffic pages. Add a <code>Math.random() &lt; 0.1</code> guard around the beacon call in production.</p>
<p>This gives you per-page breakdown. You want to know that your homepage LCP is 1.8s but your order history page is 3.4s, because those are different problems with different fixes.</p>
<p>The second piece is a performance budget in CI. Not a hard block (that creates friction), but a warning that goes to Slack or fails a check:</p>
<pre><code class="language-js">// lighthouserc.js
module.exports = {
  ci: {
    assert: {
      assertions: {
        "largest-contentful-paint": ["warn", { maxNumericValue: 2500 }],
        "cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
        "total-blocking-time": ["warn", { maxNumericValue: 300 }],
      },
    },
  },
}
</code></pre>
<p>Make CLS a hard error. Make LCP a warning. Layout stability is non-negotiable. Load time is something to improve over time.</p>
<p>The budget and the monitoring tell you where you are. The process is what actually moves the number.</p>
<h2>A workflow that compounds instead of one that spikes</h2>
<p>Single performance fixes don't compound. A workflow does. It doesn't need to be elaborate.</p>
<p>The loop we settled on:</p>
<ul>
<li><p>One baseline measurement per sprint on three key pages (home, a data-heavy listing page, a form flow)</p>
</li>
<li><p>One performance task per sprint, focused on the current worst-performing metric on the worst-performing page</p>
</li>
<li><p>One regression check in PR review: if a PR adds a new <code>"use client"</code> boundary at a high level, it gets flagged</p>
</li>
</ul>
<p>That's it. No performance sprints. No big-bang optimization projects. Small, consistent, measured.</p>
<p>In six months of running this loop, we went from a team that did occasional performance "fixes" to a team where performance kept improving passively because the worst regressions never made it to production. For the broader Next.js and React patterns that make this kind of iteration sustainable at scale, <a href="https://shubhamjha.com/blog/building-scalable-web-apps-2026">building scalable web apps in 2026</a> is a useful companion read.</p>
<hr />
<h2>What the numbers actually mean — and the metric that didn't show up in Lighthouse</h2>
<p>The 40% load time improvement, 74% repeat order increase, 12% AOV growth: I want to be honest about attribution. We also redesigned the portal, improved mobile layouts, and fixed navigation architecture in the same period. Performance wasn't the only variable.</p>
<p>The metric I'm most proud of didn't show up in Lighthouse at all: support tickets from vendors dropped to near zero. That wasn't purely a performance win. The React migration cleaned up brittle UI, and rethinking the information architecture meant vendors could find what they needed without calling support. But a fast, stable UI that doesn't jump around removes an entire category of frustration before it becomes a ticket. CLS at 0.18 means vendors watch content shift on every page load. That's not a bug they can articulate. It just makes the product feel broken in a way they can't explain.</p>
<p>The 23% first-session drop-off is the number I'll stake a claim on. When your LCP is 3.2 seconds, a quarter of your users have decided to close the tab before they've seen a single piece of your UI. When it drops to 1.9 seconds, those people stay. What they do once they stay is a product problem, not a performance problem.</p>
<p>Find your equivalent of the 23% number. It's in your analytics: session duration by page load time bucket, conversion rate by connection speed, bounce rate on your heaviest pages. The data is there. Use it to make the argument, because performance is a product problem disguised as a technical one, and nobody funds a Lighthouse score.</p>
<p>That Notion doc still exists. It's mostly empty now.</p>
<p>If your team has a performance number that's been sitting in a backlog for too long, I work with engineering teams on Next.js performance, architecture, and the delivery practices that make improvements stick. Browse my <a href="https://shubhamjha.com/projects">projects</a> or <a href="https://shubhamjha.com/contact">reach out</a> to talk through your situation.</p>
]]></content:encoded></item></channel></rss>