A prerendered Nuxt page is HTML generated at build time, but it is still a Vue application when it reaches the browser. Hydration attaches that application to the HTML, and any component code that requests data can run again on the client. A CMS call in the browser therefore means one of two things: a fetch ran outside the mechanism that carries server data into the client, or a fetch was deliberately placed to run after hydration. Prerendering by itself does not prevent either.
Prerendering, hydration and data fetching are separate steps
Three mechanisms are often treated as one, and that confusion causes most of the questions about this behaviour.
As an Amazon Associate I earn from qualifying purchases.
- Prerendering produces static HTML for the routes you select, at build time. It captures what the page looks like once rendered, nothing more.
- Hydration is the browser step in which the Vue application takes over the server-rendered markup, attaches event handlers and sets up reactivity. Hydration re-executes component setup code.
- Data fetching is whatever your code asks for. Nuxt offers a documented way to move data fetched on the server into the client so it is not requested twice, but only when you use that pattern.
Because hydration re-runs component code, a request written in a plain way will run again in the browser even though the page was prerendered. The HTML being static says nothing about whether the application will make further network calls.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Why a direct $fetch call in component setup repeats the request
The most common cause is calling $fetch directly inside a component’s setup function. Nuxt’s documentation describes this exact case and its consequence:
#1 Best Overall
“If the $fetch function is used to perform data fetching in the setup function of a Vue component, this may cause data to be fetched twice, once on the server (to render the HTML) and once again on the client (when the HTML is hydrated).” (Nuxt documentation, data fetching guidance; no individual author is named.)
The reason is structural. The server renders the component and calls the CMS, but the result is not written into the Nuxt payload. When the browser hydrates, the setup function runs again, finds no transferred data, and calls the CMS a second time. Your prerendered HTML looks correct, yet the network panel shows the request.
How the payload lets hydration skip the second request
For data that should be reused on the client, Nuxt’s supported pattern is useFetch or useAsyncData. On the server, these composables fetch the data, serialize it into the payload, and the client reads that serialized value during hydration instead of asking the CMS again. This works when the data is present and serialized, which is the default behaviour when you use the composables without overriding those options.
Rank #2
Payload extraction changes where that serialized data lives, not whether it exists. Depending on your configuration, the initial HTML may embed the payload, or client-side navigation may fetch a separate _payload.json file. That JSON request is Nuxt reading its own payload. It is not a CMS API call, and it should not be mistaken for one when you filter the network log.
Other causes of a browser request
Fetching with server: false
Setting server: false on useAsyncData tells Nuxt to leave the fetch until hydration. Nothing is fetched during server rendering, so the page is rendered without that data and the request happens in the browser. This is a legitimate choice for content that should not block the first response, but it is a browser request by design.
Fetching with serialize: false
Setting serialize: false keeps server-fetched data out of the payload. The server still fetches it, but the client does not receive it. If the component renders that data, the client refetches it during hydration. Developers often set this to reduce payload size and then wonder why the CMS is called again.
Rank #3
Client-only code paths
Code placed in onMounted, in client-only plugins, or in client-only components runs only in the browser. A fetch there is always a browser request, regardless of prerendering. Watchers configured to run immediately, and refresh actions tied to user interaction, can also trigger requests after the first render. Some of these are intentional freshness behaviour; others duplicate the initial load.
Recommended Free Tools
Client-side navigation
A request that appears only when you move between pages within the app is not a hydration problem. It is the normal path when a route’s data is not already in the payload, or when a fetch is tied to the navigation itself.
Diagnose the request step by step
- Record a cold load. In the browser’s Network panel, enable “Preserve log”, filter to Fetch/XHR, and reload the prerendered URL directly. Note the endpoint, the timing, and the Initiator column, which shows the script that started the request.
- Classify the moment. Decide whether the request happens during the document load (before or during hydration), after hydration with no navigation, or only when you navigate client-side. Each category points to a different cause.
- Check the payload. Open the page in Nuxt DevTools and use the Payload tab. If the CMS data for that page appears under its key, the server fetched it and serialized it. If it is missing, the server either did not fetch it or the data was excluded from the payload.
- Review the call site. Search the component, its composables and any plugins for
$fetch,useFetchanduseAsyncData. A direct$fetchin setup, or an async-data call withserver: falseorserialize: false, explains a repeated or deferred request. - Compare with the Initiator. If the Initiator points to a watcher, a lifecycle hook or a user action, the request is coming from that path rather than from the initial render.
If the payload contains the data but a CMS request still appears, do not conclude that prerendering failed. Look for an extra call in one of the paths above, then decide whether it is intended freshness behaviour or a duplicate of the initial fetch. The exact cause depends on your code and your request trace, which is why the Initiator column matters more than the page source.
Choosing the fetch pattern
Four axes decide the right fix: whether the data must exist in the HTML on the first response; whether the request should happen once or refresh reactively; whether the content is specific to a route; and whether the content is public or depends on request-specific credentials. The table compares the common patterns against these questions.
| Pattern | Where the fetch runs | Data reaches the payload | Browser CMS request on load | Typical use |
|---|---|---|---|---|
Direct $fetch in component setup |
Server and again during hydration | No | Yes, duplicate | Avoid for data that should be shared with the client |
useFetch or useAsyncData (defaults) |
Server, then reused on the client | Yes, serialized | No, when the payload holds the data | Public content that should be in the first HTML response |
useAsyncData with server: false |
Browser, after hydration | Not applicable | Yes, by design | Non-critical data that can load after the page renders |
useAsyncData with serialize: false |
Server, then browser if rendered | No | Yes, when the data is rendered | Large results you prefer not to embed in the payload |
Client-only fetch (for example in onMounted) |
Browser only | No | Yes, always | Data that depends on browser-only state |
The sources do not prescribe a CMS-specific delivery choice. The right pattern depends on whether your page needs the content in the first response, how often it should refresh, and whether the content is public.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Dynamic routes and shared async-data keys
On dynamic routes, an async-data key that does not include the route parameter can cause one page’s data to be reused for another. Nuxt’s upgrade guidance warns that a key missing a dynamic slug can be unsafe, and the problem can surface during prerendering when many routes share a key. Make the key identify the content:
Best Value
- Include the slug or ID in the key, for example
article-${route.params.slug}. - Check that every composable on the page that fetches related content uses a key that matches what it actually fetches.
- After changing keys, rebuild and inspect the payload for each prerendered route, not only one sample page.
Version and private content
The behaviour described here follows the current Nuxt 4 documentation; the prerendering page in that documentation set is listed as version 4.5.2. Nuxt’s documentation states that Nuxt 3 reached end of life on 31 July 2026 and no longer receives bug fixes or security patches. If you are still on Nuxt 3, check the versioned documentation for your release before changing fetch patterns, and plan the migration rather than applying the snippets here directly.
One caution applies regardless of version. Data serialized into the payload is delivered to the browser, so anything in it is visible to anyone who loads the page. Public CMS content is a reasonable candidate for the payload. Content that requires credentials, or that is tied to a specific user, should be fetched in a way that does not place restricted results in the payload, and should be checked in the payload tab before release.
No single fix fits every site. Start with the Initiator in the network log, use the payload to confirm what the server sent, and then correct the one call site that is fetching outside the supported pattern.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




