--- title: "Nuxt SSR Streaming: Faster TTFB and the Response You Can't Change Later" author: "Danny Holloran" published: "2026-10-27T10:12:04.910Z" updated: "2026-10-27T10:12:04.910Z" url: "https://zyvop.com/nuxt-ssr-streaming-faster-ttfb-and-the-response-you-can-t-change-later-ln1ac" canonical: "https://danholloran.me/posts/nuxt-ssr-streaming-faster-ttfb-and-the-response-you-cant-change-later" tags: ["vue","nuxt.js","node","performance"] --- # Nuxt SSR Streaming: Faster TTFB and the Response You Can't Change Later ![Cover image]() Classic server rendering in Nuxt is all or nothing. The server runs every plugin, resolves every awaited `useFetch`, renders the whole Vue tree to a string, and only then sends a single byte back. If one API call on the page takes 600ms, the browser sits staring at a blank tab for 600ms, unable to even start downloading your CSS. Streaming SSR has been one of the longest-standing open issues in the Nuxt repo, and as of Nuxt 4.5 it finally ships behind a flag. The win is real, but it comes with a contract change that is easy to trip over. Once the first byte is on the wire, the HTTP status and headers are locked in. Let's look at both halves. ## Turning it on Streaming is experimental and off by default. Enable it in `nuxt.config.ts`: ```ts export default defineNuxtConfig({ experimental: { ssrStreaming: true, }, }); ``` With this on, Nuxt immediately flushes an HTML shell (your ``, styles, preload hints, and entry scripts) and then streams the body as Vue renders it. The browser can start fetching CSS, fonts, and the JS entry chunk while your server is still waiting on that slow API. Bots and crawlers are excluded by default, so Googlebot and friends still get a fully buffered response. If you have an internal crawler or a link-preview service that chokes on streamed HTML, you can widen the match: ```ts export default defineNuxtConfig({ experimental: { ssrStreaming: { botRegex: /googlebot|bingbot|slackbot|my-internal-crawler/i, }, }, }); ``` Be realistic about what improves. Time to First Byte drops reliably, because the shell no longer waits on your data. Largest Contentful Paint _may_ improve, depending on how much of the route's JavaScript overlaps the entry chunk and whether your LCP element sits behind the slow fetch. A page whose hero is rendered from an awaited API call still has to wait for that call before the hero arrives; you just stop wasting that time on idle network. ## The rule: the response is committed at byte one HTTP sends the status line and headers before the body. A buffered renderer can change its mind right up until the end. A streaming renderer cannot. Anything that tries to mutate the response _after_ rendering has begun simply can't reach the client. That covers more code than you might expect: ```vue ``` The release notes call out `setResponseStatus()` in `