[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-nuxt-weekly-ssr-streaming-route-rules":44,"search-suggestions":466,"related-nuxt-weekly-ssr-streaming-route-rules":507,"code:ts:true:1pp4zz7":585,"code:ts:true:js7mkj":586,"code:ts:true:1ojhvkd":587,"code:ts:true:1d6xvdm":588,"code:ts:true:181hpo4":589,"code:ts:true:169cux9":590,"comments-01a0f8de-661d-724c-a8c3-3aef432860c4":591},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":74,"playground":76,"body":78,"bodyMd":427,"seo":428,"translationGroupId":430,"series":431,"podcastUrl":48,"verticalId":5,"thread":454,"assessments":456,"translations":462,"quiz":464},"01a0f8de-661d-724c-a8c3-3aef432860c4","nuxt-weekly-ssr-streaming-route-rules","Nuxt 4.5 SSR Streaming: The Route Rules That Disable It",null,"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.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-ssr-streaming-route-rules.png",14,"2026-10-04T12:03:48.007Z",42,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"nuxt","Nuxt",[64,65,68,71],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},"ssr","SSR",{"slug":69,"name":70,"color":48},"javascript","Javascript",{"slug":72,"name":73,"color":48},"tutorial","Tutorial",{"assessments":75},1,{"slug":46,"title":77},"Nuxt SSR streaming — route rule &amp; response-timeline playground",{"blocks":79,"version":75},[80,84,87,90,95,104,107,110,113,127,131,134,140,143,146,150,153,156,159,164,167,170,173,176,180,183,186,189,214,217,220,223,227,230,234,237,240,243,246,249,257,260,268,271,275,278,282,285,289,292,295,298,301,304,308,311,314,318,350,353,361,364,367,370,373,376,379,382,385,388,391,394,397,400,403,409,412,415,418,421],{"id":81,"html":82,"type":83},"b1","\u003Cp>Your team flips \u003Ccode>experimental.ssrStreaming: true\u003C\u002Fcode> in \u003Ccode>nuxt.config.ts\u003C\u002Fcode>, loads the homepage, and Time to First Byte drops from 1.8 seconds to 40 milliseconds. Everyone&#39;s thrilled. Someone ships it to every route in the app. Two days later, a teammate asks why the pricing page — behind a \u003Ccode>cache\u003C\u002Fcode> route rule, same layout, same components — didn&#39;t get any faster. Then a third teammate reports pages crashing in production with an error nobody on the team has seen before: \u003Ccode>ERR_HTTP_HEADERS_SENT\u003C\u002Fcode>.\u003C\u002Fp>","paragraph",{"id":85,"html":86,"type":83},"b2","\u003Cp>Nothing here is a Nuxt bug. It&#39;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&#39;t let you do anymore: change your mind about the response after you&#39;ve already sent the start of it.\u003C\u002Fp>",{"id":88,"html":89,"type":83},"b3","\u003Cp>This article is written against \u003Cstrong>Nuxt 4.5\u003C\u002Fstrong> (verified against the \u003Ccode>4.5.2\u003C\u002Fcode> release on npm&#39;s \u003Ccode>latest\u003C\u002Fcode> tag; \u003Ccode>experimental.ssrStreaming\u003C\u002Fcode> shipped in \u003Ccode>4.5.0\u003C\u002Fcode>, released July 18, 2026). Nuxt 3 reached end-of-life on July 31, 2026, so if you&#39;re still on it, this feature doesn&#39;t exist for you yet — it&#39;s 4.5-only, and still explicitly \u003Cstrong>experimental\u003C\u002Fstrong>: opt-in, and the options around it (we&#39;ll get to \u003Ccode>botRegex\u003C\u002Fcode>) are young enough that the Nuxt team is still renaming them for clarity.\u003C\u002Fp>",{"id":91,"html":92,"text":93,"type":94,"level":31},"b4","What you&#39;ll learn","What you'll learn","heading",{"id":96,"type":97,"items":98,"ordered":18},"b5","list",[99,100,101,102,103],"What \u003Ccode>experimental.ssrStreaming\u003C\u002Fcode> actually changes about the response Nuxt sends","Why six specific route rules — \u003Ccode>redirect\u003C\u002Fcode>, \u003Ccode>cache\u003C\u002Fcode>, \u003Ccode>isr\u003C\u002Fcode>, \u003Ccode>swr\u003C\u002Fcode>, \u003Ccode>noScripts\u003C\u002Fcode>, \u003Ccode>ssr: false\u003C\u002Fcode> — silently fall back to the old buffered renderer","How to opt a route out of streaming yourself with \u003Ccode>routeRules\u003C\u002Fcode>, 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",{"id":105,"html":106,"text":106,"type":94,"level":31},"b6","Who this is for",{"id":108,"html":109,"type":83},"b7","\u003Cp>You&#39;ve deployed a Nuxt app, you&#39;ve used \u003Ccode>routeRules\u003C\u002Fcode> in \u003Ccode>nuxt.config.ts\u003C\u002Fcode> for at least one of \u003Ccode>cache\u003C\u002Fcode>, \u003Ccode>prerender\u003C\u002Fcode>, or \u003Ccode>ssr: false\u003C\u002Fcode>, and you know roughly what &quot;Nuxt renders on the server, then hydrates on the client&quot; means. If that last part is fuzzy, read \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnuxt-hydration-mismatch-why-it-happens-and-how-to-fix-it-5b7i\">the hydration mismatch episode\u003C\u002Fa> first — this article assumes you already have that mental model and builds the streaming layer on top of it.\u003C\u002Fp>",{"id":111,"html":112,"text":112,"type":94,"level":31},"b8","Table of contents",{"id":114,"type":97,"items":115,"ordered":18},"b9",[116,117,118,119,120,121,122,123,124,125,126],"\u003Ca href=\"#the-problem-one-flag-and-half-the-app-doesnt-change\">The problem: one flag, and half the app doesn&#39;t change\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-commit-now-vs-decide-then-commit\">The mental model: commit now vs. decide, then commit\u003C\u002Fa>","\u003Ca href=\"#turning-it-on\">Turning it on\u003C\u002Fa>","\u003Ca href=\"#the-six-route-rules-that-disable-streaming\">The six route rules that disable streaming\u003C\u002Fa>","\u003Ca href=\"#protecting-crawlers-and-opting-out-yourself\">Protecting crawlers, and opting out yourself\u003C\u002Fa>","\u003Ca href=\"#what-breaks-when-something-mutates-the-response-too-late\">What breaks when something mutates the response too late\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key takeaways\u003C\u002Fa>",{"id":128,"html":129,"text":130,"type":94,"level":31},"b10","The problem: one flag, and half the app doesn&#39;t change","The problem: one flag, and half the app doesn't change",{"id":132,"html":133,"type":83},"b11","\u003Cp>Here&#39;s the naive version of what that team did:\u003C\u002Fp>",{"id":135,"code":136,"type":137,"language":138,"highlight":139},"b12","\u002F\u002F nuxt.config.ts — the \"just turn it on\" approach\nexport default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: true,\n  },\n})","code","ts",[],{"id":141,"html":142,"type":83},"b13","\u003Cp>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.\u003C\u002Fp>",{"id":144,"html":145,"type":83},"b14","\u003Cp>Then someone checks \u003Ccode>\u002Fpricing\u003C\u002Fcode>, which has this in the same config file:\u003C\u002Fp>",{"id":147,"code":148,"type":137,"language":138,"highlight":149},"b15","export default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: true,\n  },\n  routeRules: {\n    '\u002Fpricing': { cache: { maxAge: 60 } },\n  },\n})",[],{"id":151,"html":152,"type":83},"b16","\u003Cp>Same flag, same app, zero difference in TTFB. Not a regression — Nuxt silently buffered that route instead of streaming it, because \u003Ccode>cache\u003C\u002Fcode> is one of six route rules that disable streaming automatically. Nobody configured that. It isn&#39;t documented in the obvious place you&#39;d look (the route rule itself). It&#39;s a consequence of what streaming \u003Cem>is\u003C\u002Fem>, and once you see that, the whole fallback list stops looking arbitrary.\u003C\u002Fp>",{"id":154,"html":155,"text":155,"type":94,"level":31},"b17","The mental model: commit now vs. decide, then commit",{"id":157,"html":158,"type":83},"b18","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> SSR streaming doesn&#39;t change \u003Cem>what\u003C\u002Fem> Nuxt renders. It changes \u003Cem>when\u003C\u002Fem> the response is allowed to be final.\u003C\u002Fp>",{"id":160,"type":97,"items":161,"ordered":18},"b19",[162,163],"\u003Cstrong>Buffered SSR (the default, every Nuxt app until 4.5):\u003C\u002Fstrong> Nuxt renders the entire page to a string in memory first. Only once that&#39;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.","\u003Cstrong>Streaming SSR (\u003Ccode>experimental.ssrStreaming\u003C\u002Fcode>):\u003C\u002Fstrong> Nuxt renders the outer shell — your root layout, the \u003Ccode>&lt;head&gt;\u003C\u002Fcode>, anything above the first async boundary — and the moment that&#39;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.",{"id":165,"html":166,"type":83},"b20","\u003Cp>That&#39;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: \u003Cstrong>once the shell has flushed, Nuxt cannot change its mind about the response.\u003C\u002Fstrong> No new status code, no new header, no new cookie, no &quot;actually, redirect instead.&quot;\u003C\u002Fp>",{"id":168,"html":169,"type":83},"b21","\u003Cp>Read the fallback list through that lens and every entry explains itself: each of the six rules needs to make a decision about the \u003Cem>whole response\u003C\u002Fem> that can only be made correctly before anything is sent. Streaming removes the &quot;before&quot; — so Nuxt just doesn&#39;t stream those routes.\u003C\u002Fp>",{"id":171,"html":172,"text":172,"type":94,"level":31},"b22","Turning it on",{"id":174,"html":175,"type":83},"b23","\u003Cp>The flag lives under \u003Ccode>experimental\u003C\u002Fcode> because the feature is new and still finding its edges:\u003C\u002Fp>",{"id":177,"code":178,"type":137,"language":138,"highlight":179},"b24","export default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: true,\n  },\n})",[],{"id":181,"html":182,"type":83},"b25","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> this is a global switch, but &quot;global&quot; doesn&#39;t mean &quot;every route behaves identically&quot; — it means every route is now \u003Cem>eligible\u003C\u002Fem> for streaming, and Nuxt decides per-request whether a given route actually qualifies.\u003C\u002Fp>",{"id":184,"html":185,"text":185,"type":94,"level":31},"b26","The six route rules that disable streaming",{"id":187,"html":188,"type":83},"b27","\u003Cp>Per the official 4.5 release notes, routes carrying any of these \u003Ccode>routeRules\u003C\u002Fcode> automatically fall back to the buffered renderer, with no warning and no error:\u003C\u002Fp>",{"id":190,"head":191,"rows":194,"type":213},"b28",[192,193],"Route rule","Why it can&#39;t stream",[195,198,201,204,207,210],[196,197],"\u003Ccode>redirect\u003C\u002Fcode>","A redirect is a different status code and a \u003Ccode>Location\u003C\u002Fcode> header decided for the \u003Cem>whole\u003C\u002Fem> response. Once the shell has flushed with a \u003Ccode>200\u003C\u002Fcode>, Nuxt can no longer turn it into a \u003Ccode>30x\u003C\u002Fcode>.",[199,200],"\u003Ccode>cache\u003C\u002Fcode>","Caching a response means caching a complete, final payload. You can&#39;t cache &quot;half a page plus a promise to finish it later&quot; in any sane way.",[202,203],"\u003Ccode>isr\u003C\u002Fcode>","Incremental Static Regeneration writes a finished HTML artifact to disk\u002FCDN. Same problem as \u003Ccode>cache\u003C\u002Fcode>, with a build artifact instead of an in-memory cache entry.",[205,206],"\u003Ccode>swr\u003C\u002Fcode>","Stale-while-revalidate still needs one complete response to serve as &quot;stale&quot; while the real one regenerates — it&#39;s the \u003Ccode>cache\u003C\u002Fcode> problem again, with an extra background step.",[208,209],"\u003Ccode>noScripts\u003C\u002Fcode>","This rule exists to produce fully static, no-hydration-JS output. Streaming&#39;s entire value proposition is progressive rendering \u003Cem>as the app hydrates\u003C\u002Fem> — there&#39;s no app to progressively hydrate here.",[211,212],"\u003Ccode>ssr: false\u003C\u002Fcode>","This forces SPA mode for the route: the server sends a near-empty shell and the client renders everything. There&#39;s no server-rendered body to stream in the first place.","table",{"id":215,"html":216,"type":83},"b29","\u003Cp>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&#39;s whole mechanism is sending an \u003Cem>unfinished\u003C\u002Fem> thing on purpose. The two ideas are structurally incompatible, not just awkwardly combined.\u003C\u002Fp>",{"id":218,"html":219,"text":219,"type":94,"level":31},"b30","Protecting crawlers, and opting out yourself",{"id":221,"html":222,"type":83},"b31","\u003Cp>Streaming has one more built-in exception that isn&#39;t a route rule: \u003Cstrong>bots and crawlers get the buffered response too\u003C\u002Fstrong>, automatically, so search engines still receive one complete HTML document instead of a stream they may not handle well:\u003C\u002Fp>",{"id":224,"code":225,"type":137,"language":138,"highlight":226},"b32","export default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: {\n      \u002F\u002F tune which user agents count as crawlers\n      botRegex: \u002Fgooglebot|bingbot|my-internal-crawler\u002Fi,\n    },\n  },\n})",[],{"id":228,"html":229,"type":83},"b33","\u003Cp>And if you have a route that qualifies for streaming by the rules above, but you don&#39;t trust it yet — maybe it runs business logic you haven&#39;t audited (more on that next) — you can force it to stay buffered with its own route rule:\u003C\u002Fp>",{"id":231,"code":232,"type":137,"language":138,"highlight":233},"b34","export default defineNuxtConfig({\n  routeRules: {\n    '\u002Fcheckout\u002F**': { streaming: false },\n  },\n})",[],{"id":235,"html":236,"type":83},"b35","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> the six-rule fallback list is Nuxt protecting you automatically. \u003Ccode>streaming: false\u003C\u002Fcode> is you protecting yourself manually, for routes the automatic list doesn&#39;t know are risky.\u003C\u002Fp>",{"id":238,"html":239,"text":239,"type":94,"level":31},"b36","What breaks when something mutates the response too late",{"id":241,"html":242,"type":83},"b37","\u003Cp>This is the part the release notes describe as a caveat and real apps discovered as an outage. In 2026, the community auth module \u003Ccode>nuxt-auth-sanctum\u003C\u002Fcode> 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 \u003Cem>after\u003C\u002Fem> the shell had already flushed. The result, reported as \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmanchenkoff\u002Fnuxt-auth-sanctum\u002Fissues\u002F669\">issue #669\u003C\u002Fa>: every SSR request on every page using the module threw \u003Ccode>ERR_HTTP_HEADERS_SENT\u003C\u002Fcode>, the render died mid-stream, and the visitor got back an \u003Ccode>HTTP 200\u003C\u002Fcode> 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 \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmanchenkoff\u002Fnuxt-auth-sanctum\u002Fpull\u002F702\">PR #702\u003C\u002Fa> that made the module streaming-aware.\u003C\u002Fp>",{"id":244,"html":245,"type":83},"b38","\u003Cp>That&#39;s the general shape of the failure, and it isn&#39;t specific to that one module: \u003Cstrong>anything that tries to set a cookie, a header, or a status code from inside your render — an auth interceptor, an A\u002FB testing plugin, a geo-redirect check — is a candidate for this exact crash\u003C\u002Fstrong>, the instant you turn streaming on for a route it runs on.\u003C\u002Fp>",{"id":247,"html":248,"text":248,"type":94,"level":31},"b39","Edge cases and gotchas",{"id":250,"type":97,"items":251,"ordered":18},"b40",[252,253,254,255,256],"\u003Cstrong>Nested route rules compound.\u003C\u002Fstrong> If a parent path carries \u003Ccode>cache\u003C\u002Fcode> and a child path doesn&#39;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.","\u003Cstrong>This is still \u003Ccode>experimental\u003C\u002Fcode>.\u003C\u002Fstrong> The Nuxt team is still refining this feature after shipping 4.5.0 — \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fnuxt\u002Fissues\u002F36250\">issue #36250\u003C\u002Fa> flagged that \u003Ccode>botRegex\u003C\u002Fcode>&#39;s name doesn&#39;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.","\u003Cstrong>Caching and route rules got a real security patch, too.\u003C\u002Fstrong> Nuxt 4.5.1 fixed a cross-user payload disclosure affecting \u003Ccode>cache\u003C\u002Fcode>, \u003Ccode>swr\u003C\u002Fcode>, and \u003Ccode>isr\u003C\u002Fcode> route rules, alongside a route rule authorization bypass and a separate server-island RCE. If you use any of those three rules, make sure you&#39;re past \u003Ccode>4.5.1\u003C\u002Fcode> — and if you were caching behind a CDN before upgrading, purge it, since a leaked \u003Ccode>_payload.json\u003C\u002Fcode> could already be sitting in that cache. This is the same family of bug as \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnuxt-usestate-vs-ref-why-server-state-leaks-across-users-47n1\">the cross-request state leak episode\u003C\u002Fa>: one user&#39;s server-rendered data ending up in another user&#39;s response.","\u003Cstrong>Streaming doesn&#39;t make a cached route faster.\u003C\u002Fstrong> If a route already serves from \u003Ccode>cache\u003C\u002Fcode>\u002F\u003Ccode>isr\u003C\u002Fcode>\u002F\u003Ccode>swr\u003C\u002Fcode>, it was likely already fast — streaming has nothing to add there and nothing to take away; it simply doesn&#39;t apply.","\u003Cstrong>A route can go in and out of the fallback list at runtime.\u003C\u002Fstrong> If a route rule is applied conditionally (some Nuxt setups compute \u003Ccode>routeRules\u003C\u002Fcode> per-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&#39;t match across environments.",{"id":258,"html":259,"text":259,"type":94,"level":31},"b41","Best practices",{"id":261,"type":97,"items":262,"ordered":18},"b42",[263,264,265,266,267],"\u003Cstrong>Turn it on for one route group first\u003C\u002Fstrong>, watch TTFB and your error rate for a day, then widen it. Don&#39;t flip it globally based on one benchmark page.","\u003Cstrong>Audit anything that touches the response during render\u003C\u002Fstrong> — auth modules, A\u002FB testing, geo-redirects, custom server middleware that sets cookies — before enabling streaming on the routes they run on. If you can&#39;t audit it quickly, reach for \u003Ccode>routeRules: { streaming: false }\u003C\u002Fcode> on that path and revisit later.","\u003Cstrong>Don&#39;t expect a route behind \u003Ccode>cache\u003C\u002Fcode>, \u003Ccode>isr\u003C\u002Fcode>, \u003Ccode>swr\u003C\u002Fcode>, \u003Ccode>redirect\u003C\u002Fcode>, \u003Ccode>noScripts\u003C\u002Fcode>, or \u003Ccode>ssr: false\u003C\u002Fcode> to get faster from this flag.\u003C\u002Fstrong> It structurally can&#39;t; measure elsewhere.","\u003Cstrong>Stay current on the 4.5.x patch line specifically\u003C\u002Fstrong>, not just &quot;on Nuxt 4&quot; — the route-rule caching security fix landed in a patch release, not a minor.","\u003Cstrong>Re-read the experimental features doc before each upgrade\u003C\u002Fstrong> while this stays experimental; the option shape is still being adjusted.",{"id":269,"html":270,"text":270,"type":94,"level":31},"b43","FAQ",{"id":272,"html":273,"text":274,"type":94,"level":43},"b44","Does enabling \u003Ccode>experimental.ssrStreaming\u003C\u002Fcode> change my \u003Ccode>routeRules\u003C\u002Fcode>?","Does enabling experimental.ssrStreaming change my routeRules?",{"id":276,"html":277,"type":83},"b45","\u003Cp>No. It changes how Nuxt \u003Cem>delivers\u003C\u002Fem> a route&#39;s response. Your \u003Ccode>routeRules\u003C\u002Fcode> are unchanged; Nuxt just reads them to decide, per route, whether it&#39;s allowed to stream or must fall back to buffering.\u003C\u002Fp>",{"id":279,"html":280,"text":281,"type":94,"level":43},"b46","Why didn&#39;t my cached page get faster after I turned on streaming?","Why didn't my cached page get faster after I turned on streaming?",{"id":283,"html":284,"type":83},"b47","\u003Cp>Because \u003Ccode>cache\u003C\u002Fcode> 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 \u003Ccode>cache\u003C\u002Fcode>\u002F\u003Ccode>isr\u003C\u002Fcode>\u002F\u003Ccode>swr\u003C\u002Fcode>\u002F\u003Ccode>redirect\u003C\u002Fcode>\u002F\u003Ccode>noScripts\u003C\u002Fcode>\u002F\u003Ccode>ssr: false\u003C\u002Fcode> rule instead.\u003C\u002Fp>",{"id":286,"html":287,"text":288,"type":94,"level":43},"b48","Can I force a route to stream even though it has a \u003Ccode>cache\u003C\u002Fcode> rule?","Can I force a route to stream even though it has a cache rule?",{"id":290,"html":291,"type":83},"b49","\u003Cp>No — that&#39;s not a setting Nuxt exposes, and for good reason: caching a response requires one finished response to cache, which streaming doesn&#39;t produce. You can go the other direction, though: force a normally-streamable route to stay buffered with \u003Ccode>routeRules: { &#39;\u002Fpath&#39;: { streaming: false } }\u003C\u002Fcode>.\u003C\u002Fp>",{"id":293,"html":294,"text":294,"type":94,"level":43},"b50","Is SSR streaming stable in Nuxt 4.5?",{"id":296,"html":297,"type":83},"b51","\u003Cp>No. It lives under \u003Ccode>experimental\u003C\u002Fcode> in \u003Ccode>nuxt.config.ts\u003C\u002Fcode>, it&#39;s opt-in, and related option names (like \u003Ccode>botRegex\u003C\u002Fcode>) were still being refined after the initial 4.5.0 release. Treat it as something to pilot, not something to assume is final.\u003C\u002Fp>",{"id":299,"html":300,"text":300,"type":94,"level":43},"b52","Does this affect what Google sees when it crawls my site?",{"id":302,"html":303,"type":83},"b53","\u003Cp>No, by design. Nuxt detects bots and crawlers via \u003Ccode>botRegex\u003C\u002Fcode> and serves them the old buffered, fully-rendered HTML — streaming is specifically for real browsers that can render progressively.\u003C\u002Fp>",{"id":305,"html":306,"text":307,"type":94,"level":43},"b54","What&#39;s the actual error if my code tries to set a cookie too late?","What's the actual error if my code tries to set a cookie too late?",{"id":309,"html":310,"type":83},"b55","\u003Cp>\u003Ccode>ERR_HTTP_HEADERS_SENT\u003C\u002Fcode> — Node&#39;s standard error for writing headers after they&#39;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.\u003C\u002Fp>",{"id":312,"html":313,"text":313,"type":94,"level":31},"b56","Cheat sheet",{"id":315,"code":316,"type":137,"language":138,"highlight":317},"b57","\u002F\u002F nuxt.config.ts — SSR streaming, route rules, and the escape hatches\n\nexport default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: {\n      \u002F\u002F bots\u002Fcrawlers always get the buffered response, regardless of this regex\n      botRegex: \u002Fgooglebot|bingbot\u002Fi,\n    },\n  },\n\n  routeRules: {\n    \u002F\u002F These six rules ALWAYS fall back to buffered rendering, automatically:\n    \u002F\u002F   redirect | cache | isr | swr | noScripts | ssr: false\n    '\u002Fpricing': { cache: { maxAge: 60 } },      \u002F\u002F buffered — caching needs a finished response\n    '\u002Fold-docs': { redirect: '\u002Fdocs' },          \u002F\u002F buffered — status\u002Fheader decided before render\n    '\u002Flanding': { isr: 3600 },                   \u002F\u002F buffered — writes a finished static artifact\n    '\u002Fcatalog\u002F**': { swr: 300 },                 \u002F\u002F buffered — same as cache, plus revalidation\n    '\u002Fprint\u002F**': { noScripts: true },            \u002F\u002F buffered — no hydration to stream toward\n    '\u002Flegacy-app\u002F**': { ssr: false },            \u002F\u002F buffered — SPA shell, no server body to stream\n\n    \u002F\u002F Manual opt-out for a route that WOULD stream, but you don't trust yet:\n    '\u002Fcheckout\u002F**': { streaming: false },\n  },\n})",[],{"id":319,"head":320,"rows":324,"type":213},"b58",[321,322,323],"Route rule present","Streams?","Why",[325,329,332,334,336,338,340,342,346],[326,327,328],"none","✅ Yes","Nothing forces a pre-render decision",[196,330,331],"❌ No","Status\u002Fheader decided before body exists",[199,330,333],"Needs one finished response to cache",[202,330,335],"Writes a finished static artifact",[205,330,337],"Same as \u003Ccode>cache\u003C\u002Fcode>, plus a revalidation step",[208,330,339],"No hydration for streaming to serve",[211,330,341],"No server-rendered body at all",[343,344,345],"bot\u002Fcrawler request","❌ No (forced)","Crawlers get one complete document",[347,348,349],"\u003Ccode>streaming: false\u003C\u002Fcode> set manually","❌ No (your choice)","You opted this route out yourself",{"id":351,"html":352,"text":352,"type":94,"level":31},"b59","Key takeaways",{"id":354,"type":97,"items":355,"ordered":18},"b60",[356,357,358,359,360],"\u003Cstrong>Streaming changes \u003Cem>when\u003C\u002Fem> Nuxt commits the response, not what it renders.\u003C\u002Fstrong> Buffered SSR decides everything, then sends; streaming sends the shell immediately and can&#39;t take it back.","\u003Cstrong>Six route rules — \u003Ccode>redirect\u003C\u002Fcode>, \u003Ccode>cache\u003C\u002Fcode>, \u003Ccode>isr\u003C\u002Fcode>, \u003Ccode>swr\u003C\u002Fcode>, \u003Ccode>noScripts\u003C\u002Fcode>, \u003Ccode>ssr: false\u003C\u002Fcode> — automatically fall back to buffering\u003C\u002Fstrong>, because each one needs to finalize the response before streaming&#39;s &quot;send now&quot; moment would allow.","\u003Cstrong>Crawlers always get the buffered version\u003C\u002Fstrong>, and you can force any other route to stay buffered yourself with \u003Ccode>streaming: false\u003C\u002Fcode>.","\u003Cstrong>Anything that mutates the response during render — cookies, headers, redirects from your own code or a module — is a crash risk under streaming\u003C\u002Fstrong> once the shell has flushed; audit before widening the rollout.","\u003Cstrong>The route rules this feature reads are the same ones that got a cross-user payload security patch in 4.5.1\u003C\u002Fstrong> — if you use \u003Ccode>cache\u003C\u002Fcode>, \u003Ccode>swr\u003C\u002Fcode>, or \u003Ccode>isr\u003C\u002Fcode>, staying current matters for more than speed.",{"id":362,"html":363,"type":83},"b61","\u003Cp>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&#39;t speed up the way you expected, you now know exactly which line in \u003Ccode>routeRules\u003C\u002Fcode> to go check first — and what to look for in your own code before you widen the rollout.\u003C\u002Fp>",{"id":365,"html":366,"type":83},"b62","\u003Cp>Have you hit the \u003Ccode>ERR_HTTP_HEADERS_SENT\u003C\u002Fcode> version of this, or a route that silently didn&#39;t stream when you expected it to? Drop it in the comments — it&#39;s early enough in this feature&#39;s life that real reports like these are still shaping how it gets documented.\u003C\u002Fp>",{"id":368,"html":369,"type":83},"b63","\u003C!-- playground:start -->",{"id":371,"html":372,"text":372,"type":94,"level":31},"b64","🎮 Try it yourself",{"id":374,"html":375,"type":83},"b65","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-ssr-streaming-route-rules\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":377,"html":378,"type":83},"b66","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":380,"html":381,"type":83},"b67","\u003C!-- playground:end -->",{"id":383,"html":384,"type":83},"b68","\u003C!-- quiz:start -->",{"id":386,"html":387,"text":387,"type":94,"level":31},"b69","🧠 Test yourself",{"id":389,"html":390,"type":83},"b70","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-ssr-streaming-route-rules\u002Fquiz\">Take the 9-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":392,"html":393,"type":83},"b71","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":395,"html":396,"type":83},"b72","\u003C!-- quiz:end -->",{"id":398,"html":399,"type":83},"b73","\u003C!-- related:start -->",{"id":401,"html":402,"text":402,"type":94,"level":31},"b74","📚 Read next",{"id":404,"type":97,"items":405,"ordered":18},"b75",[406,407,408],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-route-handlers-caching-streaming\">Next.js Route Handlers: GET Stopped Caching in 15 — How to Cache in 16\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-hydration-mismatch\">Nuxt Hydration Mismatch: Why It Happens and How to Fix It\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-useasyncdata-keys-dedupe\">useAsyncData Keys in Nuxt: Caching, Dedupe &amp; the Sharing Bug\u003C\u002Fa>",{"id":410,"html":411,"type":83},"b76","\u003C!-- related:end -->",{"id":413,"type":414},"b77","divider",{"id":416,"html":417,"type":83},"b78","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":419,"html":420,"type":83},"b79","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":422,"type":97,"items":423,"ordered":18},"b80",[424,425,426],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","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`.\n\nNothing 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.\n\nThis 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.\n\n## What you'll learn\n\n- What `experimental.ssrStreaming` actually changes about the response Nuxt sends\n- Why six specific route rules — `redirect`, `cache`, `isr`, `swr`, `noScripts`, `ssr: false` — silently fall back to the old buffered renderer\n- How to opt a route out of streaming yourself with `routeRules`, and how Nuxt protects crawlers from it automatically\n- 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\n- How this connects to the rendering modes and route rules you already use in Nuxt\n\n## Who this is for\n\nYou'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](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnuxt-hydration-mismatch-why-it-happens-and-how-to-fix-it-5b7i) first — this article assumes you already have that mental model and builds the streaming layer on top of it.\n\n## Table of contents\n\n- [The problem: one flag, and half the app doesn't change](#the-problem-one-flag-and-half-the-app-doesnt-change)\n- [The mental model: commit now vs. decide, then commit](#the-mental-model-commit-now-vs-decide-then-commit)\n- [Turning it on](#turning-it-on)\n- [The six route rules that disable streaming](#the-six-route-rules-that-disable-streaming)\n- [Protecting crawlers, and opting out yourself](#protecting-crawlers-and-opting-out-yourself)\n- [What breaks when something mutates the response too late](#what-breaks-when-something-mutates-the-response-too-late)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n- [Key takeaways](#key-takeaways)\n\n## The problem: one flag, and half the app doesn't change\n\nHere's the naive version of what that team did:\n\n```ts\n\u002F\u002F nuxt.config.ts — the \"just turn it on\" approach\nexport default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: true,\n  },\n})\n```\n\nIt 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.\n\nThen someone checks `\u002Fpricing`, which has this in the same config file:\n\n```ts\nexport default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: true,\n  },\n  routeRules: {\n    '\u002Fpricing': { cache: { maxAge: 60 } },\n  },\n})\n```\n\nSame 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.\n\n## The mental model: commit now vs. decide, then commit\n\n**The mental model:** SSR streaming doesn't change *what* Nuxt renders. It changes *when* the response is allowed to be final.\n\n- **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.\n- **Streaming SSR (`experimental.ssrStreaming`):** Nuxt renders the outer shell — your root layout, the `\u003Chead>`, 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.\n\nThat'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.\"\n\nRead 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.\n\n## Turning it on\n\nThe flag lives under `experimental` because the feature is new and still finding its edges:\n\n```ts\nexport default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: true,\n  },\n})\n```\n\n**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.\n\n## The six route rules that disable streaming\n\nPer 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:\n\n| Route rule | Why it can't stream |\n| --- | --- |\n| `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`. |\n| `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. |\n| `isr` | Incremental Static Regeneration writes a finished HTML artifact to disk\u002FCDN. Same problem as `cache`, with a build artifact instead of an in-memory cache entry. |\n| `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. |\n| `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. |\n| `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. |\n\nNotice 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.\n\n## Protecting crawlers, and opting out yourself\n\nStreaming 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:\n\n```ts\nexport default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: {\n      \u002F\u002F tune which user agents count as crawlers\n      botRegex: \u002Fgooglebot|bingbot|my-internal-crawler\u002Fi,\n    },\n  },\n})\n```\n\nAnd 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:\n\n```ts\nexport default defineNuxtConfig({\n  routeRules: {\n    '\u002Fcheckout\u002F**': { streaming: false },\n  },\n})\n```\n\n**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.\n\n## What breaks when something mutates the response too late\n\nThis 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](https:\u002F\u002Fgithub.com\u002Fmanchenkoff\u002Fnuxt-auth-sanctum\u002Fissues\u002F669): 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](https:\u002F\u002Fgithub.com\u002Fmanchenkoff\u002Fnuxt-auth-sanctum\u002Fpull\u002F702) that made the module streaming-aware.\n\nThat'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\u002FB 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.\n\n## Edge cases and gotchas\n\n- **Nested route rules compound.** If a parent path carries `cache` and 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.\n- **This is still `experimental`.** The Nuxt team is still refining this feature after shipping 4.5.0 — [issue #36250](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fnuxt\u002Fissues\u002F36250) flagged that `botRegex`'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.\n- **Caching and route rules got a real security patch, too.** Nuxt 4.5.1 fixed a cross-user payload disclosure affecting `cache`, `swr`, and `isr` route 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 past `4.5.1` — and if you were caching behind a CDN before upgrading, purge it, since a leaked `_payload.json` could already be sitting in that cache. This is the same family of bug as [the cross-request state leak episode](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnuxt-usestate-vs-ref-why-server-state-leaks-across-users-47n1): one user's server-rendered data ending up in another user's response.\n- **Streaming doesn't make a cached route faster.** If a route already serves from `cache`\u002F`isr`\u002F`swr`, it was likely already fast — streaming has nothing to add there and nothing to take away; it simply doesn't apply.\n- **A route can go in and out of the fallback list at runtime.** If a route rule is applied conditionally (some Nuxt setups compute `routeRules` per-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.\n\n## Best practices\n\n- **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.\n- **Audit anything that touches the response during render** — auth modules, A\u002FB 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.\n- **Don't expect a route behind `cache`, `isr`, `swr`, `redirect`, `noScripts`, or `ssr: false` to get faster from this flag.** It structurally can't; measure elsewhere.\n- **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.\n- **Re-read the experimental features doc before each upgrade** while this stays experimental; the option shape is still being adjusted.\n\n## FAQ\n\n### Does enabling `experimental.ssrStreaming` change my `routeRules`?\n\nNo. 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.\n\n### Why didn't my cached page get faster after I turned on streaming?\n\nBecause `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`\u002F`isr`\u002F`swr`\u002F`redirect`\u002F`noScripts`\u002F`ssr: false` rule instead.\n\n### Can I force a route to stream even though it has a `cache` rule?\n\nNo — 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: { '\u002Fpath': { streaming: false } }`.\n\n### Is SSR streaming stable in Nuxt 4.5?\n\nNo. 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.\n\n### Does this affect what Google sees when it crawls my site?\n\nNo, 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.\n\n### What's the actual error if my code tries to set a cookie too late?\n\n`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.\n\n## Cheat sheet\n\n```ts\n\u002F\u002F nuxt.config.ts — SSR streaming, route rules, and the escape hatches\n\nexport default defineNuxtConfig({\n  experimental: {\n    ssrStreaming: {\n      \u002F\u002F bots\u002Fcrawlers always get the buffered response, regardless of this regex\n      botRegex: \u002Fgooglebot|bingbot\u002Fi,\n    },\n  },\n\n  routeRules: {\n    \u002F\u002F These six rules ALWAYS fall back to buffered rendering, automatically:\n    \u002F\u002F   redirect | cache | isr | swr | noScripts | ssr: false\n    '\u002Fpricing': { cache: { maxAge: 60 } },      \u002F\u002F buffered — caching needs a finished response\n    '\u002Fold-docs': { redirect: '\u002Fdocs' },          \u002F\u002F buffered — status\u002Fheader decided before render\n    '\u002Flanding': { isr: 3600 },                   \u002F\u002F buffered — writes a finished static artifact\n    '\u002Fcatalog\u002F**': { swr: 300 },                 \u002F\u002F buffered — same as cache, plus revalidation\n    '\u002Fprint\u002F**': { noScripts: true },            \u002F\u002F buffered — no hydration to stream toward\n    '\u002Flegacy-app\u002F**': { ssr: false },            \u002F\u002F buffered — SPA shell, no server body to stream\n\n    \u002F\u002F Manual opt-out for a route that WOULD stream, but you don't trust yet:\n    '\u002Fcheckout\u002F**': { streaming: false },\n  },\n})\n```\n\n| Route rule present | Streams? | Why |\n| --- | --- | --- |\n| none | ✅ Yes | Nothing forces a pre-render decision |\n| `redirect` | ❌ No | Status\u002Fheader decided before body exists |\n| `cache` | ❌ No | Needs one finished response to cache |\n| `isr` | ❌ No | Writes a finished static artifact |\n| `swr` | ❌ No | Same as `cache`, plus a revalidation step |\n| `noScripts` | ❌ No | No hydration for streaming to serve |\n| `ssr: false` | ❌ No | No server-rendered body at all |\n| bot\u002Fcrawler request | ❌ No (forced) | Crawlers get one complete document |\n| `streaming: false` set manually | ❌ No (your choice) | You opted this route out yourself |\n\n## Key takeaways\n\n- **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.\n- **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.\n- **Crawlers always get the buffered version**, and you can force any other route to stay buffered yourself with `streaming: false`.\n- **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.\n- **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`, or `isr`, staying current matters for more than speed.\n\nStreaming 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.\n\nHave 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.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-ssr-streaming-route-rules\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 9-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-ssr-streaming-route-rules\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Next.js Route Handlers: GET Stopped Caching in 15 — How to Cache in 16](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-route-handlers-caching-streaming)\n- [Nuxt Hydration Mismatch: Why It Happens and How to Fix It](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-hydration-mismatch)\n- [useAsyncData Keys in Nuxt: Caching, Dedupe & the Sharing Bug](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-useasyncdata-keys-dedupe)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":47,"canonical":429,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnuxt-weekly-ssr-streaming-route-rules","01a0f8de-661d-724c-a8c3-3f3e9c259d03",{"name":432,"part":433,"total":433,"items":434},"Nuxt Deep Dive",5,[435,439,444,448,453],{"slug":436,"title":437,"publishedAt":438,"readingMinutes":51},"nuxt-weekly-cross-request-state-leak","Nuxt useState vs ref(): Why Server State Leaks Across Users","2026-08-30T11:37:11.335Z",{"slug":440,"title":441,"publishedAt":442,"readingMinutes":443},"nuxt-weekly-useasyncdata-keys-dedupe","useAsyncData Keys in Nuxt: Caching, Dedupe & the Sharing Bug","2026-09-13T11:26:28.014Z",15,{"slug":445,"title":446,"publishedAt":447,"readingMinutes":51},"nuxt-weekly-hydration-mismatch","Nuxt Hydration Mismatch: Why It Happens and How to Fix It","2026-09-20T16:26:05.191Z",{"slug":449,"title":450,"publishedAt":451,"readingMinutes":452},"nuxt-weekly-nitro-server-routes","Nuxt Server Routes Explained: How Nitro Builds Your API","2026-09-29T12:10:53.560Z",13,{"slug":46,"title":47,"publishedAt":52,"readingMinutes":51},{"id":455,"locked":18},"01a0f8de-6657-7020-9c82-37039f88a5e8",[457],{"id":458,"slug":46,"title":459,"_count":460},"01a0f8de-6676-735c-b831-557dc2983403","Nuxt SSR Streaming & Route Rules",{"questions":461},9,[463],{"locale":13,"slug":46},{"id":458,"slug":46,"title":459,"_count":465,"questionCount":461},{"questions":461},[467,471,473,477,479,483,487,490,493,497,500,503],{"slug":468,"name":469,"articles":470},"webdev","Webdev",122,{"slug":69,"name":70,"articles":472},101,{"slug":474,"name":475,"articles":476},"frontend","Frontend",78,{"slug":72,"name":73,"articles":478},47,{"slug":480,"name":481,"articles":482},"css","Css",40,{"slug":484,"name":485,"articles":486},"typescript","Typescript",18,{"slug":488,"name":489,"articles":443},"react","React",{"slug":491,"name":492,"articles":443},"performance","Performance",{"slug":494,"name":495,"articles":496},"node","Node",11,{"slug":498,"name":499,"articles":496},"browser","Browser",{"slug":501,"name":502,"articles":461},"html","Html",{"slug":504,"name":505,"articles":506},"accessibility","Accessibility",8,{"items":508,"meta":583},[509,521,538,553,568],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":510,"likeCount":19,"commentCount":19,"author":511,"vertical":512,"topic":513,"tags":514,"_count":519,"playground":520,"hasQuiz":17,"hasPlayground":17},43,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[515,516,517,518],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":46},{"id":522,"slug":449,"title":450,"subtitle":48,"excerpt":523,"coverUrl":524,"locale":13,"readingMinutes":452,"publishedAt":451,"viewCount":525,"likeCount":19,"commentCount":19,"author":526,"vertical":527,"topic":528,"tags":529,"_count":536,"playground":537,"hasQuiz":17,"hasPlayground":17},"01a0ec72-faae-767b-a924-5ebb5891d4cd","Nuxt server routes turn server\u002Fapi into a real backend via Nitro and h3. Learn routing rules, defineEventHandler, middleware order, and the useState trap.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-nitro-server-routes.png",231,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[530,531,534,535],{"slug":61,"name":62,"color":48},{"slug":532,"name":533,"color":48},"backend","Backend",{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":449},{"id":539,"slug":445,"title":446,"subtitle":48,"excerpt":540,"coverUrl":541,"locale":13,"readingMinutes":51,"publishedAt":447,"viewCount":542,"likeCount":19,"commentCount":19,"author":543,"vertical":544,"topic":545,"tags":546,"_count":551,"playground":552,"hasQuiz":17,"hasPlayground":17},"01a0b07e-f66a-736f-a8f4-3c87da225d8f","A Nuxt hydration mismatch happens when the server's HTML disagrees with the client's first render. Learn why, and the fixes that actually work.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-hydration-mismatch.png",314,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[547,548,549,550],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":445},{"id":554,"slug":440,"title":441,"subtitle":48,"excerpt":555,"coverUrl":556,"locale":13,"readingMinutes":443,"publishedAt":442,"viewCount":557,"likeCount":19,"commentCount":19,"author":558,"vertical":559,"topic":560,"tags":561,"_count":566,"playground":567,"hasQuiz":17,"hasPlayground":17},"01a08c59-464c-7039-acf3-b32a0b9ee7ce","Learn how Nuxt's useAsyncData and useFetch generate cache keys, how dedupe (cancel vs defer) really works, and why wrapper composables silently share data.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-useasyncdata-keys-dedupe.png",332,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[562,563,564,565],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":440},{"id":569,"slug":436,"title":437,"subtitle":48,"excerpt":570,"coverUrl":571,"locale":13,"readingMinutes":51,"publishedAt":438,"viewCount":572,"likeCount":19,"commentCount":19,"author":573,"vertical":574,"topic":575,"tags":576,"_count":581,"playground":582,"hasQuiz":17,"hasPlayground":17},"01a04446-3f08-701c-8cdc-e5ab5e87086e","A module-scope ref() in Nuxt is shared by every request that hits your server. Learn why useState isolates state per user, and how to fix the leak.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-cross-request-state-leak.png",368,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[577,578,579,580],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":436},{"page":75,"perPage":584,"total":433,"totalPages":75},7,"\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F nuxt.config.ts — the \"just turn it on\" approach\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineNuxtConfig\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  experimental\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    ssrStreaming\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineNuxtConfig\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  experimental\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    ssrStreaming\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  routeRules\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    '\u002Fpricing'\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cache\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> maxAge\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">60\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineNuxtConfig\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  experimental\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    ssrStreaming\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineNuxtConfig\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  experimental\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    ssrStreaming\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-cmnt\">\u002F\u002F tune which user agents count as crawlers\n\u003C\u002Fspan>      botRegex\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002Fgooglebot\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">|\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">bingbot\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">|\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">my-internal-crawler\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">i\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineNuxtConfig\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  routeRules\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    '\u002Fcheckout\u002F**'\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> streaming\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F nuxt.config.ts — SSR streaming, route rules, and the escape hatches\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineNuxtConfig\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  experimental\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    ssrStreaming\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-cmnt\">\u002F\u002F bots\u002Fcrawlers always get the buffered response, regardless of this regex\n\u003C\u002Fspan>      botRegex\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002Fgooglebot\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">|\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">bingbot\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">i\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\n  routeRules\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-cmnt\">\u002F\u002F These six rules ALWAYS fall back to buffered rendering, automatically:\n\u003C\u002Fspan>    \u003Cspan class=\"shj-cmnt\">\u002F\u002F   redirect | cache | isr | swr | noScripts | ssr: false\n\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Fpricing'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cache\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> maxAge\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">60\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>      \u003Cspan class=\"shj-cmnt\">\u002F\u002F buffered — caching needs a finished response\n\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Fold-docs'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> redirect\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fdocs'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>          \u003Cspan class=\"shj-cmnt\">\u002F\u002F buffered — status\u002Fheader decided before render\n\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Flanding'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> isr\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">3600\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>                   \u003Cspan class=\"shj-cmnt\">\u002F\u002F buffered — writes a finished static artifact\n\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Fcatalog\u002F**'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> swr\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">300\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>                 \u003Cspan class=\"shj-cmnt\">\u002F\u002F buffered — same as cache, plus revalidation\n\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Fprint\u002F**'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> noScripts\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>            \u003Cspan class=\"shj-cmnt\">\u002F\u002F buffered — no hydration to stream toward\n\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Flegacy-app\u002F**'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> ssr\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>            \u003Cspan class=\"shj-cmnt\">\u002F\u002F buffered — SPA shell, no server body to stream\n\u003C\u002Fspan>\n    \u003Cspan class=\"shj-cmnt\">\u002F\u002F Manual opt-out for a route that WOULD stream, but you don't trust yet:\n\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Fcheckout\u002F**'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> streaming\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":592},[]]