Nuxt 4.5 SSR Streaming: The Route Rules That Disable It
Nuxt 4.5's experimental SSR streaming flushes HTML early, but cache, isr, swr, redirect, and ssr:false route rules silently fall back to buffering. Cheat sheet included.

- 1Nuxt useState vs ref(): Why Server State Leaks Across Users14 min
- 2useAsyncData Keys in Nuxt: Caching, Dedupe & the Sharing Bug15 min
- 3Nuxt Hydration Mismatch: Why It Happens and How to Fix It14 min
- 4Nuxt Server Routes Explained: How Nitro Builds Your API13 min
- 5Nuxt 4.5 SSR Streaming: The Route Rules That Disable Ityou are here
Your team flips experimental.ssrStreaming: true in nuxt.config.ts, loads the homepage, and Time to First Byte drops from 1.8 seconds to 40 milliseconds. Everyone's thrilled. Someone ships it to every route in the app. Two days later, a teammate asks why the pricing page — behind a cache route rule, same layout, same components — didn't get any faster. Then a third teammate reports pages crashing in production with an error nobody on the team has seen before: ERR_HTTP_HEADERS_SENT.
Nothing here is a Nuxt bug. It's six route rules quietly opting themselves out of streaming, and one very normal pattern — a request interceptor that sets a cookie — running straight into the one thing streaming doesn't let you do anymore: change your mind about the response after you've already sent the start of it.
This article is written against Nuxt 4.5 (verified against the 4.5.2 release on npm's latest tag; experimental.ssrStreaming shipped in 4.5.0, released July 18, 2026). Nuxt 3 reached end-of-life on July 31, 2026, so if you're still on it, this feature doesn't exist for you yet — it's 4.5-only, and still explicitly experimental: opt-in, and the options around it (we'll get to botRegex) are young enough that the Nuxt team is still renaming them for clarity.
- What
experimental.ssrStreamingactually changes about the response Nuxt sends - Why six specific route rules —
redirect,cache,isr,swr,noScripts,ssr: false— silently fall back to the old buffered renderer - How to opt a route out of streaming yourself with
routeRules, and how Nuxt protects crawlers from it automatically - The real failure mode when code tries to mutate the response after the shell has flushed, and how to audit for it before you turn streaming on broadly
- How this connects to the rendering modes and route rules you already use in Nuxt
You've deployed a Nuxt app, you've used routeRules in nuxt.config.ts for at least one of cache, prerender, or ssr: false, and you know roughly what "Nuxt renders on the server, then hydrates on the client" means. If that last part is fuzzy, read the hydration mismatch episode first — this article assumes you already have that mental model and builds the streaming layer on top of it.
- The problem: one flag, and half the app doesn't change
- The mental model: commit now vs. decide, then commit
- Turning it on
- The six route rules that disable streaming
- Protecting crawlers, and opting out yourself
- What breaks when something mutates the response too late
- Edge cases and gotchas
- Best practices
- FAQ
- Cheat sheet
- Key takeaways
Here's the naive version of what that team did:
It works exactly as advertised on a plain page: no route rule, no redirect logic, nothing fancy — just a component tree that fetches some data and renders. Time to First Byte on that page drops hard, because Nuxt no longer waits for the whole page to finish rendering before it sends anything.
Then someone checks /pricing, which has this in the same config file:
Same flag, same app, zero difference in TTFB. Not a regression — Nuxt silently buffered that route instead of streaming it, because cache is one of six route rules that disable streaming automatically. Nobody configured that. It isn't documented in the obvious place you'd look (the route rule itself). It's a consequence of what streaming is, and once you see that, the whole fallback list stops looking arbitrary.
The mental model: SSR streaming doesn't change what Nuxt renders. It changes when the response is allowed to be final.
- Buffered SSR (the default, every Nuxt app until 4.5): Nuxt renders the entire page to a string in memory first. Only once that's done does it decide the final HTTP status code, the headers, any cookies, and send the whole thing in one response. Nothing reaches the browser until Nuxt is completely finished deciding.
- Streaming SSR (
experimental.ssrStreaming): Nuxt renders the outer shell — your root layout, the<head>, anything above the first async boundary — and the moment that's ready, it commits the status code and headers and flushes them to the socket immediately. Then it keeps writing the rest of the body to that same open connection as the remaining components finish rendering.
That's the entire feature. The speed win is real: the browser gets bytes — and can start painting and fetching sub-resources — while Nuxt is still working on the rest of the page. But committing the headers early has a one-way-door property: once the shell has flushed, Nuxt cannot change its mind about the response. No new status code, no new header, no new cookie, no "actually, redirect instead."
Read the fallback list through that lens and every entry explains itself: each of the six rules needs to make a decision about the whole response that can only be made correctly before anything is sent. Streaming removes the "before" — so Nuxt just doesn't stream those routes.
The flag lives under experimental because the feature is new and still finding its edges:
Key concept: this is a global switch, but "global" doesn't mean "every route behaves identically" — it means every route is now eligible for streaming, and Nuxt decides per-request whether a given route actually qualifies.
Per the official 4.5 release notes, routes carrying any of these routeRules automatically fall back to the buffered renderer, with no warning and no error:
| Route rule | Why it can't stream |
|---|---|
redirect | A redirect is a different status code and a Location header decided for the whole response. Once the shell has flushed with a 200, Nuxt can no longer turn it into a 30x. |
cache | Caching a response means caching a complete, final payload. You can't cache "half a page plus a promise to finish it later" in any sane way. |
isr | Incremental Static Regeneration writes a finished HTML artifact to disk/CDN. Same problem as cache, with a build artifact instead of an in-memory cache entry. |
swr | Stale-while-revalidate still needs one complete response to serve as "stale" while the real one regenerates — it's the cache problem again, with an extra background step. |
noScripts | This rule exists to produce fully static, no-hydration-JS output. Streaming's entire value proposition is progressive rendering as the app hydrates — there's no app to progressively hydrate here. |
ssr: false | This forces SPA mode for the route: the server sends a near-empty shell and the client renders everything. There's no server-rendered body to stream in the first place. |
Notice the pattern: every rule on this list needs to produce one finished, final thing — a cached payload, a static file, a redirect, a client-only shell — and streaming's whole mechanism is sending an unfinished thing on purpose. The two ideas are structurally incompatible, not just awkwardly combined.
Streaming has one more built-in exception that isn't a route rule: bots and crawlers get the buffered response too, automatically, so search engines still receive one complete HTML document instead of a stream they may not handle well:
And if you have a route that qualifies for streaming by the rules above, but you don't trust it yet — maybe it runs business logic you haven't audited (more on that next) — you can force it to stay buffered with its own route rule:
Key concept: the six-rule fallback list is Nuxt protecting you automatically. streaming: false is you protecting yourself manually, for routes the automatic list doesn't know are risky.
This is the part the release notes describe as a caveat and real apps discovered as an outage. In 2026, the community auth module nuxt-auth-sanctum shipped a response interceptor that — like a lot of auth and session code — tried to set a cookie on the outgoing response during SSR. Under streaming, that interceptor ran after the shell had already flushed. The result, reported as issue #669: every SSR request on every page using the module threw ERR_HTTP_HEADERS_SENT, the render died mid-stream, and the visitor got back an HTTP 200 with a dead, partially-written page — not even a clean error, because the status code was already committed as a success before anything went wrong. The maintainers shipped a fix in PR #702 that made the module streaming-aware.
That's the general shape of the failure, and it isn't specific to that one module: anything that tries to set a cookie, a header, or a status code from inside your render — an auth interceptor, an A/B testing plugin, a geo-redirect check — is a candidate for this exact crash, the instant you turn streaming on for a route it runs on.
- Nested route rules compound. If a parent path carries
cacheand a child path doesn't override it, the child inherits the fallback too. Audit route rule inheritance before assuming a specific page streams — check what matches its full path, not just the rule you wrote for it. - This is still
experimental. The Nuxt team is still refining this feature after shipping 4.5.0 — issue #36250 flagged thatbotRegex's name doesn't make clear which way the match goes (exclude bots from streaming, as it turns out). It was resolved by clarifying the docs rather than renaming the option, but the discussion shows the shape and naming are still being questioned this soon after release. Expect wording and defaults to keep moving through the 4.x line; re-check the docs before you lock a specific option name into muscle memory. - Caching and route rules got a real security patch, too. Nuxt 4.5.1 fixed a cross-user payload disclosure affecting
cache,swr, andisrroute rules, alongside a route rule authorization bypass and a separate server-island RCE. If you use any of those three rules, make sure you're past4.5.1— and if you were caching behind a CDN before upgrading, purge it, since a leaked_payload.jsoncould already be sitting in that cache. This is the same family of bug as the cross-request state leak episode: one user's server-rendered data ending up in another user's response. - Streaming doesn't make a cached route faster. If a route already serves from
cache/isr/swr, it was likely already fast — streaming has nothing to add there and nothing to take away; it simply doesn't apply. - A route can go in and out of the fallback list at runtime. If a route rule is applied conditionally (some Nuxt setups compute
routeRulesper-environment or per-deploy), a route that streamed in staging can silently stop streaming in production. Diff your resolved route rules, not just your source file, if TTFB numbers don't match across environments.
- Turn it on for one route group first, watch TTFB and your error rate for a day, then widen it. Don't flip it globally based on one benchmark page.
- Audit anything that touches the response during render — auth modules, A/B testing, geo-redirects, custom server middleware that sets cookies — before enabling streaming on the routes they run on. If you can't audit it quickly, reach for
routeRules: { streaming: false }on that path and revisit later. - Don't expect a route behind
cache,isr,swr,redirect,noScripts, orssr: falseto get faster from this flag. It structurally can't; measure elsewhere. - Stay current on the 4.5.x patch line specifically, not just "on Nuxt 4" — the route-rule caching security fix landed in a patch release, not a minor.
- Re-read the experimental features doc before each upgrade while this stays experimental; the option shape is still being adjusted.
No. It changes how Nuxt delivers a route's response. Your routeRules are unchanged; Nuxt just reads them to decide, per route, whether it's allowed to stream or must fall back to buffering.
Because cache is one of the six route rules that force buffering. Streaming literally does not apply to that route — look for the TTFB win on a route with no cache/isr/swr/redirect/noScripts/ssr: false rule instead.
No — that's not a setting Nuxt exposes, and for good reason: caching a response requires one finished response to cache, which streaming doesn't produce. You can go the other direction, though: force a normally-streamable route to stay buffered with routeRules: { '/path': { streaming: false } }.
No. It lives under experimental in nuxt.config.ts, it's opt-in, and related option names (like botRegex) were still being refined after the initial 4.5.0 release. Treat it as something to pilot, not something to assume is final.
No, by design. Nuxt detects bots and crawlers via botRegex and serves them the old buffered, fully-rendered HTML — streaming is specifically for real browsers that can render progressively.
ERR_HTTP_HEADERS_SENT — Node's standard error for writing headers after they've already been sent. Under streaming, that moment arrives as soon as the shell flushes, which is much earlier in the request than most response-mutating code expects.
| Route rule present | Streams? | Why |
|---|---|---|
| none | ✅ Yes | Nothing forces a pre-render decision |
redirect | ❌ No | Status/header decided before body exists |
cache | ❌ No | Needs one finished response to cache |
isr | ❌ No | Writes a finished static artifact |
swr | ❌ No | Same as cache, plus a revalidation step |
noScripts | ❌ No | No hydration for streaming to serve |
ssr: false | ❌ No | No server-rendered body at all |
| bot/crawler request | ❌ No (forced) | Crawlers get one complete document |
streaming: false set manually | ❌ No (your choice) | You opted this route out yourself |
- Streaming changes when Nuxt commits the response, not what it renders. Buffered SSR decides everything, then sends; streaming sends the shell immediately and can't take it back.
- Six route rules —
redirect,cache,isr,swr,noScripts,ssr: false— automatically fall back to buffering, because each one needs to finalize the response before streaming's "send now" moment would allow. - Crawlers always get the buffered version, and you can force any other route to stay buffered yourself with
streaming: false. - Anything that mutates the response during render — cookies, headers, redirects from your own code or a module — is a crash risk under streaming once the shell has flushed; audit before widening the rollout.
- The route rules this feature reads are the same ones that got a cross-user payload security patch in 4.5.1 — if you use
cache,swr, orisr, staying current matters for more than speed.
Streaming is the first Nuxt feature that makes you think about the response as a timeline instead of a single object, and that timeline is exactly what makes the fallback list make sense instead of feeling arbitrary. Next time a route doesn't speed up the way you expected, you now know exactly which line in routeRules to go check first — and what to look for in your own code before you widen the rollout.
Have you hit the ERR_HTTP_HEADERS_SENT version of this, or a route that silently didn't stream when you expected it to? Drop it in the comments — it's early enough in this feature's life that real reports like these are still shaping how it gets documented.
Runs right in your browser — poke at it and watch the concept react live.
Think it clicked? Take the 9-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
- Next.js Route Handlers: GET Stopped Caching in 15 — How to Cache in 16
- Nuxt Hydration Mismatch: Why It Happens and How to Fix It
- useAsyncData Keys in Nuxt: Caching, Dedupe & the Sharing Bug
🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 💬 Discord — join the frontend best-practices community: discord.gg/d9KRhuAwQ
- 📸 Instagram — frontend best practices, daily: @bestpractice___
Keep reading
One post a day, in your inbox
Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.
0 comments
Sign in to join the discussion, like comments, and save articles for later.