H Heygents Docs Open App

Updated July 31, 2026 · Performance · Medium severity

Anonymous visitors download megabytes of JavaScript they never use

Why is my site's JavaScript bundle so large for a page that has almost no interactivity? Your client bundle is oversized because code that only logged-in users need, admin panels, auth flows, chat widgets, is imported from a shared root layout and shipped to every anonymous visitor too. Run a bundle analyzer, move that code behind dynamic imports or server components, and set a size budget in CI so it can't silently regrow.

Seen in 32 of 450 scanned projects (7%). Counts projects where a bundle analysis showed admin, auth, or chat code, or large serialized props, present in a chunk loaded by anonymous public routes.

Common stacks: Next.js React Webpack Vite

How to tell you have it

  • The Network tab shows a multi-megabyte main JS chunk on a marketing page with no interactive elements
  • Admin, auth, and chat code all show up in the same chunk analysis as the public homepage
  • A blog or article page ships its full body text as serialized props inside a client component
  • Ten self-hosted font families load site-wide even though most pages use two
  • A single file in the client bundle is several thousand lines long and imports half the app

Why it matters

A global layout is a convenience for developers and a tax on every visitor. If admin, auth, and chat infrastructure are imported at the root of the app instead of the specific routes that need them, an anonymous user landing on the homepage pays for code they will never execute. This is invisible in local dev, where everything feels instant, and very visible on a slow mobile connection.

Serializing large amounts of data into client component props is a related but distinct mistake: it's not just JS weight, it's data that didn't need to leave the server at all. Article bodies, long lists, and analytics payloads passed as props get embedded directly into the page's script tags and re-parsed by the browser instead of rendered once on the server.

Font loading is an easy one to overlook because each individual @font-face declaration feels small. Ten self-hosted families, each with multiple weights and formats, adds up to hundreds of kilobytes of render-blocking or layout-shifting downloads on a page that visually only uses two or three of them.

Loading vendor scripts from a public CDN at runtime without defer or async blocks the main thread while the browser fetches and executes a resource it doesn't control the timing or availability of. Combined with a large first-party bundle, this stacks two independent slowdowns on top of each other for every first-time visitor.

How Heygents detects it

Deep Scan reads the build output and package.json for a bundle analyzer, and inspects import graphs from the root layout or app shell for admin, auth, and chat modules that a public route pulls in unconditionally. It also reads component files for large inline data props and counts self-hosted font files and their formats.

How to fix it

  1. Generate a bundle analysis Run the analyzer for your framework and look at what's in the chunk that loads on the homepage or landing page specifically, not the whole app's total size.
  2. Move gated code out of the root layout Anything only a logged-in or admin user needs should be imported inside the route that requires it, not the shared layout every page renders through.
  3. Dynamic-import heavy, rarely-used widgets A chat widget or rich editor that only a fraction of visitors ever open should not block the initial bundle. Load it lazily so its cost is paid only when it's actually used.
  4. Render article bodies on the server If long text content is being passed as a prop into a client component just to display it, render it in a server component instead so the HTML ships once and the text never has to be re-serialized into a script tag.
  5. Set a size budget in CI Fail the build if the main bundle exceeds a threshold, so a regression is caught in a pull request instead of discovered months later when the bundle has quietly doubled.

Generate a bundle analysis

ANALYZE=true npm run build

Move gated code out of the root layout

// before: apps/root layout.tsx
import AdminPanel from '@/components/AdminPanel';

// after: app/admin/layout.tsx only
import AdminPanel from '@/components/AdminPanel';

Dynamic-import heavy, rarely-used widgets

const ChatWidget = dynamic(() => import('@/components/ChatWidget'), { ssr: false });

Render article bodies on the server

// server component, no 'use client'
export default async function Article({ slug }) {
  const post = await getPost(slug);
  return <article dangerouslySetInnerHTML={{ __html: post.html }} />;
}

Set a size budget in CI

npx bundlesize --max 250kB dist/assets/index-*.js

Find this in your own projects, automatically

Heygents runs a read-only Deep Scan across every project you own, finds issues like this one, and hands you a ready-to-run fix an AI agent can execute and verify. A solo developer gets the audit, the backlog and the fix loop in one place.

Open Heygents →

Frequently asked questions

How do I know if code-splitting actually helped?

Compare the bundle analyzer output before and after on the specific route you changed, and check the Network tab's transferred size for that route in an incognito window. A win in total build output doesn't matter if the route a real visitor lands on didn't shrink.

Is ten font families really a performance problem?

Yes, both in download weight and in layout shift as each face swaps in. Audit which weights and families are actually used in the rendered CSS, drop the rest, and prefer variable fonts where the framework supports them to collapse multiple weight files into one.

Should I self-host or use a CDN for third-party scripts?

Self-hosting gives you control over caching and lets you add defer or async explicitly, while a public CDN risks an uncontrolled render-block if the script tag doesn't specify loading behavior. Either can work; the defect is loading a script synchronously in the head with no defer attribute.

What's a reasonable JS budget for a marketing page?

Most marketing and content pages should ship well under 200-300KB of first-party JS after compression. Interactive dashboards or editors can justify more, but that code should live behind the route that needs it, not the shared shell every page pays for.