Remove Render Blocking CSS in Next.js for Better Core Web Vitals
Remove render blocking CSS in Next.js for faster Core Web Vitals

Remove render blocking CSS in Next.js
CSS is render blocking. The browser will not paint a single pixel until it has downloaded and parsed every stylesheet in the <head>. In a Next.js app, that means two external CSS files by default: one global stylesheet and one page specific stylesheet. On a fast 3G connection, downloading those two files costs you 600ms before the browser even starts rendering. That is 600ms added straight to your First Contentful Paint and Largest Contentful Paint.
Last reviewed by Arjen Karel on March 2026
Table of Contents!
- Remove render blocking CSS in Next.js
- App Router vs Pages Router
- Option 1: Generate Critical CSS (Pages Router)
- Option 2: Inline all CSS in the head (Pages Router)
- Option 3: Styled Components (Pages Router)
- Option 4: Inline CSS with inlineCss (App Router, Next.js 15+)
- A note on CSS Modules and Tailwind CSS
- Which option should I use?
On average, across all my clients and all devices, I measure about 200ms of delay caused by render blocking CSS. According to the 2025 Web Almanac, 85% of mobile pages still fail the render blocking resources audit. The median page loads 8 CSS files totaling 79 KB. On slow mobile connections, render delay can account for 69% of total LCP time. Time to fix this.

App Router vs Pages Router
The fix depends on which Next.js router you use. The Pages Router (using _document.tsx) and the App Router (using app/layout.tsx) handle CSS delivery differently. Critical CSS inlining via critters does not work with the App Router because streaming is incompatible with how critters processes HTML. If you are on the App Router, skip to Option 4.
The App Router is the default since Next.js 13.4. But many production apps still run the Pages Router, and the first three options below remain valid for those.
Option 1: Generate Critical CSS (Pages Router)
The fastest option is to generate Critical CSS. Critical CSS is a collection of CSS rules needed to render the visible part of the page. Those rules are placed inline in the <head>. Then, in parallel, the original CSS files are downloaded while the browser continues to render. Once the original CSS files are downloaded they are injected into the page.
Critical CSS is available in Next.js as an experimental feature. Add experimental: { optimizeCss: true } to your next.config.js. Next.js bundles the critters library internally, so you do not need to install it separately. Note that the original critters package is deprecated (replaced by the beasties fork), but Next.js has not yet made the switch.
const nextConfig = {
reactStrictMode: true,
experimental: { optimizeCss: true }
} This option only works with the Pages Router. It does not support the App Router because critters needs the full rendered HTML, which is incompatible with streaming.
Option 2: Inline all CSS in the head (Pages Router)
If you do not want to enable experimental features in your Next.js app you could consider inlining your CSS manually. Create a custom head section and reference it in your _document.tsx.
The downside is that the inline CSS will be larger than with the first method because you are inlining everything, not just the critical rules. However, if you keep your stylesheets clean and lean, this will improve the Core Web Vitals for your Next.js app significantly.
import { Html, Head, Main, NextScript } from "next/document";
import { readFileSync } from "fs";
import { join } from "path";
class InlineStylesHead extends Head {
getCssLinks: Head["getCssLinks"] = ({ allFiles }) => {
const { assetPrefix } = this.context;
if (!allFiles || allFiles.length === 0) return null;
return allFiles
.filter((file: any) => /\.css$/.test(file))
.map((file: any) => (
<style
key={file}
nonce={this.props.nonce}
data-href={`${assetPrefix}/_next/${file}`}
dangerouslySetInnerHTML={{
__html: readFileSync(join(process.cwd(), ".next", file), "utf-8"),
}}
/>
));
};
}
export default function Document() {
return (
<Html>
<InlineStylesHead />
<body>
<Main />
<NextScript />
</body>
</Html>
);
} Option 3: Styled Components (Pages Router)
Styled Components is a CSS-in-JS tool that lets you write CSS in JavaScript files. You can scope class names, remove unused CSS automatically, and manage styles alongside your components. For the Core Web Vitals, it means only the styles needed on that page are injected.
Add compiler: { styledComponents: true } to your next.config.js, install styled-components (yarn add styled-components and yarn add -D @types/styled-components), then update _document.js to support server side rendering for styled components.
const nextConfig = {
reactStrictMode: true,
compiler: {
styledComponents: true,
}
}
import Document, { DocumentContext } from "next/document";
import { ServerStyleSheet } from "styled-components";
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
} If you use the App Router with styled-components, you need a different setup. Create a style registry client component using useServerInsertedHTML and wrap your root layout with it. Styled components only work in Client Components in the App Router. See the Next.js CSS-in-JS guide for the full pattern.
Option 4: Inline CSS with inlineCss (App Router, Next.js 15+)
For the App Router, Next.js 15 introduced experimental.inlineCss. This replaces all <link> stylesheet tags with inline <style> tags, eliminating the render blocking waterfall entirely.
const nextConfig = {
experimental: {
inlineCss: true,
}
} This is still experimental and the Next.js team does not yet recommend it for production. It inlines all CSS (not just critical CSS), which increases HTML size. For sites using Tailwind CSS, this trade-off works well because Tailwind generates small, atomic CSS bundles. For sites with large stylesheets, the HTML bloat may hurt Time to First Byte.
A note on CSS Modules and Tailwind CSS
CSS Modules and Tailwind CSS are the two most popular styling approaches in Next.js today. Both compile to standard CSS files at build time and are served as <link> tags. That means they are render blocking by default.
Tailwind has one advantage: because it purges unused classes, the output is typically very small (often under 10 KB gzipped). The render blocking impact of a 10 KB file is minimal. CSS Modules can grow larger if you are not careful about unused styles.
Either way, all the options above apply. If you want to eliminate render blocking CSS entirely, combine your preferred styling approach with optimizeCss (Pages Router) or inlineCss (App Router).
Which option should I use?
It depends on your router and your CSS strategy:
- App Router + Tailwind: Use
inlineCss. Small CSS bundles make inlining practical. - App Router + large CSS: Wait for
inlineCssto stabilize, or use a custom beasties setup. - Pages Router: Use
optimizeCss(Option 1). It is the simplest fix and only inlines what is critical. - Pages Router + CSS-in-JS: Use styled-components or Emotion with the SSR patterns shown above.
Across Next.js sites monitored by CoreDash, those using inlined critical CSS show a median FCP improvement of 400ms compared to default CSS delivery. That is the difference between a good and a mediocre FCP score.
Whichever option you choose, verify the results with Real User Monitoring. Lab scores tell you what might happen. Field data tells you what actually happened.
For more on how CSS and JavaScript interact with the rendering pipeline, see the complete guide to fixing render blocking resources. If you are optimizing a Next.js app, also check the guides on fixing third party scripts and measuring the Core Web Vitals in Next.js. For a broader look at loading strategy, the resource prioritization guide covers how browsers decide what to fetch first.
Find out what is actually slow.
I map your critical rendering path using real field data. You get a clear answer on what blocks LCP, what causes INP spikes, and where layout shifts originate.
Book a Deep Dive
