All postsBuilds

Render-Blocking CSS in Next.js: One Experimental Flag Removed Both Requests.

You have seen the warning. You run Lighthouse on your Next.js site, the score looks fine, and there it sits in the diagnostics: "Eliminate render-blocking...

Garvit Sharma

Garvit Sharma

15 June 2026 · 6 MIN READ

Builds

Render-Blocking CSS in Next.js: One Experimental Flag Removed Both Requests.

webight.com
On this page

You have seen the warning. You run Lighthouse on your Next.js site, the score looks fine, and there it sits in the diagnostics: "Eliminate render-blocking resources." Two CSS files. A few KiB each. You ignore it, because how much damage can two small stylesheets really do?

I ignored it too. On my own site.

In June I audited webight.com the same way I audit client sites, and Lighthouse mobile gave me a 90 on performance. One of the things dragging it down was 22.5 KiB of CSS split across 2 render-blocking requests. Both were standard Next.js chunks, the files Next generates from Tailwind and my globals. Nothing exotic. The kind of output every Next.js project on the planet ships by default.

The fix was one flag in next.config.ts and a rebuild. Both requests gone. Here is the whole thing, including when you should copy this and when you absolutely should not.

What render-blocking actually means

See, the browser refuses to paint anything until it has your CSS. That is by design. If it painted first and styled later, every page load would flash ugly unstyled HTML for a moment, so the browser waits.

Which means every <link rel="stylesheet"> in your head is a promise: show the user nothing until this file arrives. Each one of those links is a network request sitting directly on the critical path between "user tapped your link" and "user sees pixels." HTML downloads, parser finds the link tag, browser fires a request, waits, parses the CSS, and only then paints. On a 4G connection with real-world latency, that wait is measurable.

Here is the part that bugged me when I dug into my own report. My two CSS files were 22.5 KiB combined. Compressed, that is a fraction of one hero image. The entire cost was the round trips themselves. The cargo was basically free and I was paying for the trips.

That is the one idea this whole post hangs on. When your total CSS is small, the requests are the cost. Remove the requests and the cost goes with them.

The fix: experimental.inlineCss

Next.js 15.1 shipped an experimental flag that takes your built CSS and inlines it straight into the HTML instead of linking to it. This is the diff on webight.com:

// next.config.ts
 import type { NextConfig } from "next";

 const nextConfig: NextConfig = {
+  experimental: {
+    inlineCss: true,
+  },
 };

 export default nextConfig;

And the complete file if you are starting clean:

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    inlineCss: true,
  },
};

export default nextConfig;

What it does at build time: instead of emitting

<link rel="stylesheet" href="/_next/static/css/abc123.css" />

Next puts the full CSS content inside a <style> tag in the head of every page. The styles arrive in the same response as the HTML. Zero extra requests on the critical path. The browser has everything it needs to paint the moment the document lands.

Two conditions before you get excited. It only works with the App Router, and it is experimental, which means the Next team can rename it or remove it in a future release. I accepted that risk because the worst case is deleting 3 lines from my config.

Verify it, do not assume it

A flag in a config file means nothing until you check the output it produces. After the rebuild, grep the built HTML:

npm run build
grep -rl 'rel="stylesheet"' .next/server/app

Before the flag, that grep printed every prerendered HTML file in the project. After, it printed nothing. That silence is the confirmation.

Then check production, because local builds and deployed builds can disagree:

curl -s https://www.webight.com | grep -c 'rel="stylesheet"'

A 0 means the deployed HTML carries no stylesheet links. If you want to see where the CSS went:

curl -s https://www.webight.com | grep -o '<style' | wc -l

You will find your styles sitting inline in the head. That grep step takes 20 seconds and it has caught me out before on other changes, where a config edit silently did nothing because I put it under the wrong key. Check the output, every time.

After this, Lighthouse stopped flagging render-blocking resources entirely, and its estimate had put roughly 150 ms of render delay on those two requests for a mobile connection.

Honest accounting: my performance score went from 90 to 96 in that audit, but the inlineCss flag was one of several fixes. The biggest single win was a 4,190 ms LCP render delay caused by a framer-motion opacity animation on the hero headline, which waited for hydration before showing anything. I replaced it with a plain CSS animation and the delay dropped to 382 ms. I also removed two leftover third-party scripts and added security headers. The flag killed the render-blocking warning specifically. The 6 points came from the pile.

When inlining wins

The condition is simple: your total CSS has to be small.

Mine was 22.5 KiB before compression, and Tailwind is the reason. Tailwind only emits the classes you actually used, so a purged production build for a marketing site usually lands in this range. With CSS that small, inlining adds a few KiB to each compressed HTML response, and in exchange you delete every CSS round trip from the critical path.

It also matters who visits you. A studio site like mine gets mostly first-time visitors: someone reads a post, checks the work page, maybe the pricing page, leaves. First visits get zero benefit from CSS caching, because there is nothing cached yet. For that traffic pattern, inlining is close to free speed.

When inlining loses

Caching. That is the trade you are making, and you need to actually run the numbers for your site instead of copying my config because it worked for me.

An external stylesheet is fetched once, stored with immutable cache headers, and served from disk on every page after that, for months. The repeat visitor pays for it one time. Inlined CSS has no cache entry. It rides inside every single HTML response, every full page load, every visit.

Now scale that up. Say you run a dashboard product with 140 KiB of CSS and users who log in daily. With link tags, that 140 KiB downloads once and then costs nothing for weeks. Inlined, it gets shipped again on every full document request, forever. You took a one-time cost and turned it into a subscription. That is the exact opposite of what you wanted.

My rule after doing this: total CSS under about 30 KiB with mostly first-visit traffic, inline it. CSS past 50 KiB, or an app where the same people return every day, keep the link tags and let the cache do its job. Between those, measure both builds and look at your actual analytics before deciding.

One more thing people miss. Client-side navigations in the App Router do not refetch full HTML documents, so the repeat-download penalty applies to full page loads, hard refreshes, and new sessions. If your users live inside one long session with soft navigations, the penalty shrinks. If they arrive cold from Google every time, it shrinks too, because cold visitors were never cached anyway. The penalty bites hardest in the middle: returning users who start fresh sessions often.

The takeaway you can run today

The whole check fits in a 10-minute window.

npm run build
grep -rl 'rel="stylesheet"' .next/server/app
ls -la .next/static/css/

Look at the total size of those CSS files. Small and mostly cold traffic? Add the flag, rebuild, grep again, confirm the links are gone, deploy, re-run Lighthouse. Big CSS or daily returning users? Leave it alone, you already have the better setup.

So open your own Lighthouse report tonight and look for that render-blocking line you have been scrolling past for months. If your CSS is two small files and you are still making every phone do two extra round trips before it paints a single pixel, that delay is a choice you are making on behalf of your visitors. Ten minutes. One flag. One grep to prove it worked.

If you run the check and the numbers look weird, send them to me, happy to look.

Next.jsWeb PerformanceCSSLighthouseCore Web Vitals
Next move

We build custom platforms, websites, and automation.

The two people who build it are the two you talk to, and every price is on the page.

Garvit Sharma

Garvit Sharma

Full-stack developer and co-founder of Webight, a two-person web and AI studio in India. He writes these from real client work. More about us.