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 introStep 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"orstrategy="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:
- Open Chrome DevTools, go to Performance, and record while clicking through your site.
- Use the Web Vitals extension to see real-time INP scores as you interact.
- 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.