Large HTML files slow down your site. When your HTML is too big, your Time to First Byte (TTFB) increases. Since nothing can happen on the frontend until the browser receives that first byte, a slow TTFB delays every other loading metric, including your Largest Contentful Paint (LCP).
But there's another problem: browsers can't start loading your CSS, JavaScript, images, or fonts until they've downloaded and parsed the HTML. The larger your HTML, the longer everything else has to wait. A 500kb HTML file doesn't just delay your initial render. It delays everything.
This checklist helps you find and fix the most common causes of bloated HTML in Next.js.
This is a critical step for reducing your HTML size. When you pass data from a Server Component to a Client Component, Next.js includes that data in the initial HTML as part of the RSC payload. This payload is sent twice: once embedded in the HTML for the initial load, and again as a separate fetch during client-side navigation.
- Pass only what you need: If you're only using a user's name, pass
user.nameinstead of the whole user object. Database records include metadata, timestamps, and internal IDs that your UI doesn't need. Extract just the fields your component uses.
- Use React.cache(): This helps deduplicate data fetching across your component tree. It ensures you don't fetch and serialize the same data multiple times if different components need the same information.
Developers have seen their RSC payload drop from 250kb to just 5-15kb by being selective with their data. This directly translates to faster page loads and less data over the wire.
Reference: How to optimize RSC payload size
Inline SVGs are convenient because you can style them with CSS easily. However, they live directly in your HTML. Since SVGs are XML-based, they can be surprisingly large, especially for complex illustrations or detailed icons.
- Use the Image component: Move your SVGs to the
publicfolder and use the Next.js<Image>component. Set theunoptimizedprop since SVGs are vector-based and don't need resizing. This allows the browser to cache each SVG file separately from your HTML. When you use the same logo across multiple pages, it loads once and gets reused everywhere. On Vercel, these files are automatically cached on the global CDN. - Watch out for complex paths: A single complex SVG can add several kilobytes to every page it appears on. Footer icons and other below-the-fold SVGs don't need to load immediately. With the Image component, you get automatic lazy loading. The browser only fetches these files when the user scrolls near them. With inline SVGs, the XML is always in your HTML, blocking the initial render even for content the user might never see.
Reference: Next.js Image component
The way you handle styles affects how much extra weight is added to your HTML. Some modern CSS frameworks are much better for HTML size than others.
- Prefer CSS Modules or Tailwind: These approaches generate static CSS files that are cached by the browser. They don't add extra weight to your HTML for every component instance. Tailwind keeps your CSS bundle small regardless of how many components you have, unlike runtime CSS-in-JS approaches.
- Be careful with runtime CSS-in-JS: Libraries like styled-components or Emotion often inject
<style>tags into your HTML at runtime. This can lead to significant bloat, especially on pages with many styled elements. Every time a component renders, it might be adding more style information to the document. - Look into zero-runtime alternatives: If you love the CSS-in-JS developer experience, consider libraries that extract styles into static CSS files during the build process. This gives you the best of both worlds: a great developer experience and optimized HTML.
Server Components don't add their code to your client-side bundle, and they help keep your HTML cleaner by doing the heavy lifting on the server.
- Only use 'use client' when necessary: Reserve client components for parts of your UI that need interactivity, like forms, buttons, or state. If a component just displays data, keep it as a Server Component.
- Push client components down the tree: Keep the bulk of your page as Server Components and only make the small, interactive leaves Client Components. This minimizes the amount of data that needs to be serialized across the "client boundary."
- Pass Server Components as children: You can wrap Server Components inside Client Components by passing them as
children. This is a powerful pattern that lets you keep the inner components on the server while the outer component handles client-side logic.
Server Components run on the server and have no impact on your client-side JavaScript bundle. This means you can keep your client bundle smaller by only using Client Components where you need interactivity.
Reference: Next.js Server Components
Streaming doesn't technically reduce the total HTML size, but it changes how that HTML is delivered to the user. This has a huge impact on perceived performance.
- Wrap slow components in Suspense: This allows Next.js to send the "shell" of your page immediately while the slower parts load in the background. The user sees the layout and navigation right away instead of waiting for the whole page to be ready.
- Stream content instead of blocking: By using streaming, you improve the perceived performance because the user sees content sooner. Next.js sends the HTML in chunks as it's generated, which is much better than waiting for a single massive response.
- Prioritize critical content: Use Suspense boundaries to ensure that the most important parts of your page (like the hero section) are delivered first, while less critical parts (like a related products list) load later.
Reference: Streaming and Suspense
You can't fix what you don't measure. Use these tools to see exactly what's taking up space in your HTML and RSC payload.
- Check the RSC payload: After a build, you can find RSC files in your
.nextfolder for statically rendered pages. Run this command to see the largest ones:find .next -type f -name "*.rsc" -exec du -h {} + | sort -nr | head -n 5. Note that dynamic pages won't appear here. For those, use browser DevTools. - Use browser DevTools: Open the Network tab and look for requests ending in
?_rsc=. These show you the data being sent to your client components during navigation. You can inspect the response body to see exactly what's being serialized. - Try the RSC Parser: Tools like the RSC Parser give you a visual breakdown of what's inside your payload. It's much easier to spot a giant JSON object when it's visualized.
- Watch for the 128kB warning: Next.js will show a warning in your console if your page data exceeds 128kB. This is a clear signal that you need to optimize your data fetching or component structure.
Reference: Large page data warning
- Filter your data before passing it to client components.
- Move SVGs out of your components and into the
publicfolder. - Use Tailwind or CSS Modules instead of runtime CSS-in-JS.
- Keep as much of your page on the server as possible.
- Use the RSC Parser to find hidden bloat in your data.