Site speed

How to make a heavy framework site fast (Next.js, etc.)

TS Talha Shahzad··5 min read
The short version
  • Heavy JavaScript frameworks without lazy-loading are the top cause of poor INP scores.
  • Code-splitting and lazy-loading components reduce the JavaScript the browser must process on initial load.
  • Hydration is the hidden cost: the browser re-runs your entire component tree just to make it interactive.
  • Defer non-critical scripts and trim unused JS before considering a platform migration.

Next.js, React, Vue, Angular. These frameworks build great products. They also ship a lot of JavaScript. And JavaScript is the single most expensive resource type on the web because it has to be downloaded, parsed, compiled, and executed before a page becomes interactive.

Framework-heavy sites consistently top the list of pages with poor INP (Interaction to Next Paint) scores. The page loads, it looks ready, the user clicks a button, and nothing happens for 300-500 milliseconds because the browser is still processing JavaScript. That is what INP measures: the gap between a user's action and the browser's response.

The fix is not rebuilding on a different platform. The fix is JavaScript discipline.

Why framework sites get slow

Frameworks are not inherently slow. A well-built Next.js site can be extremely fast. The problem is what accumulates over months of feature development:

Shipping the entire bundle on every page. Without code-splitting, every page loads every component, even components that only appear on other pages. A checkout page loads the blog layout. A blog post loads the product carousel. The browser downloads and processes all of it.

Hydration overhead. Server-side rendering (SSR) in frameworks like Next.js sends HTML to the browser, which is fast. But then the browser has to "hydrate" the page: it re-runs all the component JavaScript to attach event handlers and make the page interactive. On a complex page, hydration can take 1-3 seconds on a mobile device. During hydration, the page looks ready but does not respond to clicks.

Unused dependencies. Over time, npm packages accumulate. A date picker library imported for one page gets bundled sitewide. A charting library used on the dashboard ships on the marketing pages. Each dependency adds to the JavaScript payload.

Client-side rendering of content that could be static. Fetching data client-side (via useEffect or similar) means the browser downloads the JavaScript, executes it, makes an API call, waits for the response, and then renders the content. For content that does not change per user, this is unnecessary work that could be handled at build time.

Step 1: analyze your JavaScript bundle

Before fixing anything, understand what you are shipping. Use the Bundle Analyzer to visualize your bundle:

ANALYZE=true next build

This generates a visual map of every module in your bundle, sized by byte weight. Look for:

  • Large libraries you did not expect (moment.js, lodash, full charting libraries)
  • Duplicate packages (different versions of the same dependency)
  • Modules that appear in chunks for pages they should not be on

This analysis usually reveals 30-50% of the bundle that should not be there.

Want a website that turns visitors into customers, not just compliments?

Book a 15-min intro

Step 2: code-split and lazy-load

Code-splitting is the most impactful single optimization for framework sites. Instead of loading everything upfront, you split your code into chunks that load only when needed.

In Next.js, dynamic imports handle this:

import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
  loading: () => <div>Loading...</div>,
  ssr: false
});

Apply this to:

  • Any component that appears below the fold
  • Modals and dialogs that only open on user action
  • Charts, maps, and complex visualizations
  • Features behind tabs or accordions that are not immediately visible

A well-split Next.js site loads 100-200 KB of JavaScript on initial page load instead of 500 KB to 1 MB. The difference in INP is dramatic.

Step 3: reduce hydration cost

Hydration is the hidden performance tax of SSR frameworks. You can reduce it several ways:

Use React Server Components (available in Next.js App Router). Server Components render on the server and send zero JavaScript to the client. Only components that need interactivity (forms, buttons, toggles) need to be Client Components.

Defer non-critical client components. Wrap interactive elements that are not immediately needed in dynamic imports with ssr: false. This prevents them from being hydrated during initial page load.

Consider partial hydration or islands. Frameworks like Astro and Fresh hydrate only the interactive "islands" on a page, leaving the rest as static HTML. If you are building a new project, these architectures dramatically reduce JavaScript cost.

Step 4: trim unused JavaScript

Audit npm dependencies. Run npx depcheck to find packages that are imported in your code but never actually used, or packages listed in package.json but not imported anywhere.

Replace heavy libraries with lighter alternatives. Common swaps:

  • moment.js (300 KB) with date-fns (tree-shakeable) or the native Intl API
  • lodash (70 KB full) with lodash-es (tree-shakeable) or native JS methods
  • Full icon libraries with only the icons you use

Tree-shake aggressively. Modern bundlers (webpack 5, Vite, Turbopack) can eliminate unused exports from libraries. But tree-shaking only works with ES module imports. If you are importing with require(), switch to import to enable tree-shaking.

Step 5: defer third-party scripts

Third-party scripts (analytics, chat widgets, marketing pixels) compete with your framework JavaScript for the main thread. I covered this in detail in my post on marketing tags killing page speed.

For framework sites specifically:

  • Load analytics via the Next.js Script component with strategy="afterInteractive" or strategy="lazyOnload".
  • Move chat widgets and non-critical tools to lazy loading.
  • Consider server-side tagging to move tracking off the client entirely.

Step 6: measure interactions, not just load

The core metric for framework sites is INP, not LCP. LCP might be fine because SSR sends HTML quickly. But INP reveals the real problem: the page looks ready but cannot respond to input.

Test INP by:

  1. Open Chrome DevTools, go to Performance, and record while clicking through your site.
  2. Use the Web Vitals extension to see real-time INP scores as you interact.
  3. Check field data in Search Console for production INP numbers.

Target INP under 200 milliseconds. If your INP is over 500ms, the user experience is noticeably broken, and it is almost always a JavaScript problem.

The realistic outcome

A framework site that ships 800 KB of JavaScript and has an INP of 600ms can typically be optimized to 200-300 KB and an INP under 200ms without removing any features. The work is not a rebuild. It is a refactor: code-splitting, dependency trimming, deferred loading, and server component adoption.

This kind of optimization takes focused, methodical work. It is not glamorous. But the result is a site that responds instantly to every click instead of making users wait, and that directly impacts conversion rate and Core Web Vitals.

If your framework site feels sluggish and your INP scores reflect it, a performance-focused technical audit identifies exactly where the JavaScript weight is concentrated and how to reduce it without breaking features.

Prefer to hire through Upwork?
Top Rated Plus, 100% Job Success, 450+ projects shipped. See the reviews and start a contract.
Hire me on Upwork

FAQ

Should I rebuild my site on a lighter framework?

Usually not. The performance issues are almost always in how the framework is used, not which framework it is. Code-splitting, lazy-loading, and trimming unused JS can dramatically improve speed without a rebuild.

What is hydration and why does it slow down my site?

Hydration is the process where the browser re-executes your component JavaScript to attach event handlers and make the page interactive. On a heavy React or Next.js site, this can take several seconds on mobile devices, blocking all user interactions until it completes.

How do I measure INP on my site?

Use the Web Vitals Chrome extension for real-time measurement while browsing. For production data, check the Core Web Vitals report in Google Search Console or the field data section of PageSpeed Insights.

All posts
the next step is small

Want a site that does this for you?

15 minutes, no deck, no pressure. Worst case, you leave with a free plan.

keep reading

More notes