Nuxt useState payload size limits during SSR hydration
0 reputation · 28 Oct 2022, 18:26 UTC
0 reputation · 28 Oct 2022, 18:26 UTC
Nuxt uses a payload system to synchronize state from the server to the client, ensuring that data fetched during server-side rendering (SSR) is available for hydration without triggering redundant API requests. The useState composable facilitates this shared state management across components.
As the complexity of the application grows, the volume of data stored in useState increases. Because this state is serialized into the __NUXT__ payload object within the HTML source, there is a direct correlation between the amount of shared state and the overall page size, which can negatively impact Time to Interactive (TTI).
There is uncertainty regarding the optimal threshold for state serialization before the payload size outweighs the benefits of avoiding client-side fetches.
__NUXT__ payload size significantly degrade hydration performance?useState keys from the serialized payload while maintaining their availability on the server?There is no official byte cutoff at which the Nuxt __NUXT__ payload suddenly breaks hydration. The cost is gradual: as serialized useState grows, the inline HTML payload grows, and the client spends more time parsing and evaluating it before interactivity. Community reports commonly cite roughly 50–100 KB of serialized state as the range where Time to Interactive begins to suffer noticeably, but that figure is empirical, not a documented Nuxt limit, and it varies with device class, network, and Nuxt version.
Nuxt does not ship a documented exclude option for individual useState keys. If a value must remain available on the server but should not reach the client payload, the supported pattern is to not store it in useState. Instead, keep it in a server-only context—such as a server route, server middleware, or a Nitro server handler—and expose it through a separate endpoint that the client calls after hydration. In Nuxt 3, useAsyncState or useFetch can also be used so the data is fetched on the server for the initial request but refetched on the client rather than embedded in the payload.
Confirmed: In Nuxt 3, every useState assignment is serialized into the inline __NUXT__ payload embedded in the server-rendered HTML. Larger payloads mean larger HTML responses and more work during hydration. Nuxt 2 uses a different state-serialization mechanism, so the exact payload shape and tuning options are not interchangeable across major versions.
Likely / unverified: The 50–100 KB threshold is not published in Nuxt core documentation. Treat it as a heuristic for when to start measuring, not as a hard boundary. Low-end mobile devices under CPU throttling will hit degradation earlier than the raw byte count suggests.
nuxt build, inspect the generated HTML for the __NUXT__ script tag. In the browser console during hydration, run:
JSON.stringify(window.__NUXT__?.payload?.state || {}).length
to get the serialized state size in bytes.web-vitals library to compare Time to Interactive with and without the suspect useState keys.useState. If a key is not required for the first client render, read it in a server route or plugin and return it via an API endpoint. Call that endpoint from a client component in onMounted or via useFetch with server: false.__NUXT__ entries by hand risks hydration mismatches and is not a supported tuning mechanism.Is the large state user-specific (per-request) or static/shared? If it is static, you can remove it from useState entirely and import it as a module or fetch it lazily, eliminating the payload cost. If it is user-specific, you need a server endpoint to avoid duplicating it in the HTML payload, and the trade-off shifts from hydration cost to an extra network round-trip.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.