#Server-Only Data Fetching with Client Navigation in Nuxt

41 messages · Page 1 of 1 (latest)

tidal pollenBOT
#

Hi @south finch. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You're right—this is a common scenario for SSR frameworks, and Nuxt does provide mechanisms to achieve what you want, but there are some nuances.

Your Requirements Recap

  • Fetch on server only (never on client)
  • Serve in HTML (no client API calls)
  • Cache for 5 minutes
  • Stale-while-revalidate (SWR)
  • Works with <NuxtLink> navigation

The Nuxt Approach

1. useAsyncData/useFetch and SSR

By default, useAsyncData and useFetch in Nuxt are SSR-friendly: they fetch data on the server, serialize it into the payload, and hydrate it on the client—avoiding a second fetch on hydration. This works for initial page loads and direct navigation, but there’s a caveat: on client-side navigation (via <NuxtLink>), Nuxt will re-run the data fetching logic on the client. This is by design, to ensure up-to-date data and interactivity, but it means the client will hit your API unless you take extra stepsData Fetching Docs.

2. Route Rules: SWR and Caching

Nuxt 3+ supports route rules for caching and SWR. You can configure your route in nuxt.config:

export default defineNuxtConfig({
  routeRules: {
    '/your-page': {
      swr: 300, // 5 minutes
    },
  },
})

This tells Nuxt to cache the HTML and payload for 5 minutes and serve stale content while revalidating in the background.

3. Payload Extraction for SWR/ISR

#

Historically, payload extraction (serving the data as part of the HTML/payload, not requiring a client fetch) was only available for prerendered pages. However, as of a recent update, payload extraction is now supported for SWR/ISR routes. This means that when you use SWR/ISR route rules, Nuxt will generate and cache the payload, and client-side navigation via <NuxtLink> will use the cached payload instead of re-fetching data from the clientPR: Enable payload extraction for ISR/SWR routes.

4. No Need for Experimental Flags (in Nuxt 4+)

With the above PR merged, payload extraction for SWR/ISR is no longer experimental in Nuxt 4+. You do not need to enable any experimental flags—just use the route rules as shown.

5. You Do Not Need to Use <a> Instead of <NuxtLink>

Using <NuxtLink> is the correct approach. With SWR/ISR and payload extraction, Nuxt will serve the cached payload on navigation, and the client will not re-fetch the data from your API endpoint.


Example Setup

nuxt.config.ts:

export default defineNuxtConfig({
  routeRules: {
    '/kickstarter': {
      swr: 300, // 5 minutes
    },
  },
})

pages/kickstarter.vue:

<script setup lang="ts">
const { data } = await useAsyncData('kickstarter', () => $fetch('<https://api.kickstarter.com/your-endpoint'>))
</script>
<template>
  <div>{{ data }}</div>
</template>

Navigation:

<NuxtLink to="/kickstarter">Kickstarter</NuxtLink>

Summary

  • Use useAsyncData or useFetch for SSR data fetching.
  • Set SWR route rules for caching and revalidation.
  • Nuxt will extract and cache the payload for SWR/ISR routes, so client navigation uses the cached data.
  • No need for experimental flags or to avoid <NuxtLink>.
#

If you are on Nuxt 3 and not seeing this behavior, make sure you are on the latest version, as this feature was added recentlyPR: Enable payload extraction for ISR/SWR routes.


If you need more advanced control, you can still use an API route and fetch from there, but for your use case, the above should be sufficient and is now the recommended approach.

Let me know if you need a more detailed example or run into any issues!

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, your example is correct for setting up SWR caching for the root route:

export default defineNuxtConfig({
  routeRules: {
    '/': {
      swr: 300, // 5 minutes
    },
  },
})

This configuration tells Nuxt to cache the HTML and payload (including your useAsyncData data) for the / route for 5 minutes. During this time, all users will receive the same cached version of the page and its data—the cache is shared globally, not per user. After 5 minutes, the next request will trigger a background revalidation: the old (stale) content is served immediately, while Nuxt regenerates the page and payload in the background for subsequent requests. This is the "stale-while-revalidate" (SWR) behaviorHybrid Rendering.

You do not need to worry about the client making a new API call after 5 minutes. The client will not fetch the data directly from your API; instead, Nuxt's server will handle the revalidation and serve the updated payload. The client always receives the data as part of the HTML/payload, even after the cache expires, as long as you use useAsyncData and SWR route rules. This works seamlessly with <NuxtLink> navigation as wellNuxt Performance: Fetching Data.

So, to summarize:

  • The cache is global (shared for all users).
  • After 5 minutes, the server regenerates the page in the background, but users still get the stale content until the new version is ready.
  • The client never fetches the API directly; it always gets the data from the Nuxt server payload.

This is the intended and recommended way to achieve your requirements in Nuxt 3/4.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, with Nuxt's built-in SWR caching via routeRules, you do not need to implement your own cache logic in the composable. Nuxt will handle caching and revalidation for you at the server level. Your composable can be greatly simplified:

export const useKickstarterData = () => {
  const { data, pending, error } = useAsyncData(
    'kickstarter',
    async () => {
      const response = await $fetch('<https://www.kickstarter.com/projects/gritdark/rustborn-bastards/stats.json?v=1',> { timeout: 10000 });
      return {
        pledged: Math.floor(parseFloat(response.project.pledged)),
        backers: response.project.backers_count,
      };
    }
  );

  return {
    data,
    pending,
    error,
  };
};

Key points:

  • No need for manual cache variables or timestamps.
  • No need for getCachedData unless you want custom client-side caching (Nuxt handles server-side cache with SWR).
  • The SWR cache duration and revalidation are controlled in your nuxt.config with routeRules:
export default defineNuxtConfig({
  routeRules: {
    '/': { swr: 300 }, // 5 minutes
  },
});

With this setup, Nuxt will cache the payload for 5 minutes for all users, and serve stale content while revalidating in the background—no client-side API calls or manual cache logic neededHybrid Rendering Data Fetching.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

When using Nuxt's built-in SWR caching with routeRules, the cache management (including what happens on errors) is handled by Nuxt itself, not by your composable. If a fetch fails during revalidation, Nuxt will continue to serve the last successfully cached data (the "stale" data) until a successful revalidation occurs. This is the core of the "stale-while-revalidate" strategy: users never see an error page due to a failed background fetch—they just keep seeing the last good data until new data is availableHybrid Rendering.

You do not need to manually handle "use the last data if there is an error" logic in your composable. Nuxt's server will not update the cache with failed or errored responses; it only updates the cache when the fetch is successful. If an error occurs, the cache remains unchanged and the stale data is served.

If you want to handle errors for display purposes in your component, you can still use the error ref returned by useAsyncData, but the cache itself is safe and will not be overwritten by failed fetchesData Fetching.

In summary:

  • On fetch error during SWR revalidation, Nuxt keeps serving the last good data.
  • You do not need to implement custom logic for this.
  • The error ref is for UI purposes only; it does not affect the cache.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

When using Nuxt's SWR (swr) route rules, the entire page output—including the HTML and the payload (which contains all data from useAsyncData/useFetch on that page)—is cached and served as a single unit. If a fetch fails during revalidation, Nuxt will continue to serve the previously cached (stale) version of the whole page, not just individual fetches. This means users will see the last successfully generated page, including all its data, until a new successful revalidation occurs. The cache is not updated with partial or failed data; it only updates when the page is fully and successfully regenerated Hybrid Rendering.

So, to answer your question:
If a single fetch on the page fails during SWR revalidation, Nuxt serves the whole stale page (including all data), not just the stale data for that one fetch.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, your configuration for applying SWR caching to the main index route / is correct:

export default defineNuxtConfig({
  routeRules: {
    '/': {
      swr: 300, // 5 minutes
    },
  },
  // ...other config
});

This will cache the entire output (HTML and payload) of the root page for 5 minutes and serve stale content while revalidating in the background, as described in the Nuxt documentation on hybrid rendering and route rules. The cache is global for all users, and Nuxt will only update the cache when the page is successfully regenerated after the SWR interval expires. If a fetch fails during revalidation, the last good (stale) version is served until a successful regeneration occurs Hybrid Rendering.

Your other configuration options (such as app.head, modules, and vite.plugins) do not affect the SWR behavior and are valid as shown.

If you want to confirm the syntax for routeRules and SWR on the root route, you can see a similar example in the official documentation:

export default defineNuxtConfig({
  routeRules: {
    '/': { swr: 300 },
  },
})

Hybrid Rendering

So, your setup is correct for enabling SWR caching on the main index route.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

Based on the official Nuxt documentation and recent updates, your approach and code look correct for enabling SWR caching and using useAsyncData for server-only data fetching on the root route. However, if you are experiencing issues where the data is missing after navigating back with <NuxtLink>, there are a few important details and limitations to consider:

1. Payload Extraction for SWR/ISR

Recent improvements in Nuxt (see Enable payload extraction for ISR/SWR routes) allow payload extraction for SWR/ISR routes, so the payload (including your useAsyncData data) should be cached and reused on client navigation. This means, in theory, your setup should work as expected if you are using a recent version of Nuxt 3/4.

2. Correct Use of Keys

You are providing an explicit key ('kickstarter') to useAsyncData, which is required for caching and payload extraction to work properly. This matches the documented best practices useAsyncData - Reactive Keys.

3. Nuxt Version

If you are not on the latest Nuxt version, you may not have the payload extraction for SWR/ISR feature. This feature is not available in older Nuxt 3 releases. Please ensure you are using a version that includes this PR.

4. Known Issues with Root Route

There was a recent fix for an issue where the root route (/) payload was not loading during client-side navigation, causing useAsyncData to re-execute instead of using the cached payload. If you are on a version before this fix, you may encounter exactly the problem you describe.

5. No Manual Cache Logic Needed

#

You do not need to implement manual cache logic in your composable. Nuxt's SWR/ISR and payload extraction should handle this for you Hybrid Rendering.


What to Check

  • Nuxt Version: Make sure you are using a version of Nuxt that includes both payload extraction for SWR/ISR and the root route payload fix.
  • Build/Generate: If you are using nuxt generate or running in production, ensure you have rebuilt your project after upgrading.
  • No Manual Cache: Your composable is correct and does not need manual cache logic.

If It Still Doesn't Work

If you are on the latest Nuxt and still see this issue, it may be a bug or a regression. In that case, you may want to check the Nuxt issues or open a new one with a minimal reproduction.


Summary:
Your setup is correct according to the latest Nuxt documentation and features. If the data is missing after navigation, it is most likely due to not having the latest Nuxt fixes for SWR/ISR payload extraction and root route payload handling. Please update your Nuxt version and try again. If the problem persists, it may be a bug not covered in the current documentation or codebase.


#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

To update Nuxt to the latest version (which will include the fix from 4.2.0), you should use the nuxt upgrade command. Here’s how you can do it:

npx nuxt upgrade --dedupe

This command will upgrade Nuxt and deduplicate your lockfile, ensuring you get the latest dependencies and fixes. You can also use yarn, pnpm, or bun if you prefer:

yarn nuxt upgrade
# or
pnpm nuxt upgrade
# or
bun x nuxt upgrade

For more details, see the official Nuxt upgrade guide.

If you want to specify a particular release channel (like v4), you can use the --channel option:

npx nuxt upgrade --channel=v4 --dedupe

Upgrade Command Reference

After upgrading, rebuild your project to ensure all changes take effect.


Summary:
Run npx nuxt upgrade --dedupe to update to the latest Nuxt version with the fix you need.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

No, upgrading to Nuxt 4.1.3 is not a canary or nightly version—it's the latest stable release available on the latest channel. Nuxt uses a stable release cycle, and 4.1.3 is a regular patch release, not a pre-release or nightly build. If you want to use unreleased features or fixes (such as those scheduled for 4.2.0), you would need to opt into the nightly release channel, which is different from the stable channel you are currently on Upgrade Guide Upgrade Command Reference.

If you specifically need a feature or fix that is only available in a future version (like 4.2.0 or a nightly build), you can upgrade to the nightly channel using:

npx nuxt upgrade --channel=nightly --dedupe

But by default, npx nuxt upgrade keeps you on the stable channel, not a canary or nightly version.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

Based on the knowledge sources provided, your setup is correct for enabling SWR caching on the root route (/) using Nuxt's routeRules and useAsyncData composable. Your code matches the documented approach for SSR-friendly data fetching and SWR caching:

  • You are using an explicit key with useAsyncData, which is recommended for caching and payload extraction to work properly.
  • You have set the SWR route rule for / to cache for 5 minutes.

Recent Nuxt updates (including payload extraction for ISR/SWR routes and the root route payload fix) should address the issues you described, and these are expected to be available in Nuxt 4.2.0 and later.

However, if you are still experiencing the issue where the data is missing after navigating back to / with <NuxtLink>, the knowledge sources do not provide enough information to explain why this is happening in your case. The documentation and recent PRs indicate that this should work as intended, but there may be an unresolved bug, a regression, or a configuration/environment issue not covered in the sources.

Summary:

  • Your configuration and composable usage are correct according to the official documentation and recent fixes.
  • If you are on Nuxt 4.2.0+ and still see this issue, the knowledge sources do not contain enough information to provide a definitive solution.
  • If the problem persists, it may be necessary to check for open issues or report a new one with a minimal reproduction, as this may be an edge case or a bug not yet addressed in the documentation or codebase.
#

If you need further troubleshooting, please provide more details or check the Nuxt issues for similar reports.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

I'm sorry you're still experiencing this issue. According to the official Nuxt documentation and recent PRs, when using useAsyncData (with an explicit key) and SWR route rules, Nuxt should cache the payload and serve it on client navigation, preventing a client-side fetch after hydration—especially with the recent fixes for SWR/ISR payload extraction and root route handling Hybrid Rendering.

However, if you are still seeing a network request for the fetch on client navigation (as shown in your network tab), this suggests that either:

  • The payload extraction is not working as intended for your setup, or
  • There is a bug or regression not covered in the current documentation or recent PRs.

The knowledge sources do not provide enough information to explain why this is still happening in your case, especially since you are on a version that should include the relevant fixes. There may be an unresolved issue or edge case affecting your project.

In summary:

  • Your configuration and usage are correct per the documentation.
  • With the latest Nuxt, this should work, but if you still see client-side fetches, the knowledge sources do not contain enough information to resolve this.
  • This may require further investigation or reporting to the Nuxt team, as it could be a bug or an unaddressed scenario.

If you want to pursue this further, consider creating a minimal reproduction and opening an issue on the Nuxt GitHub repository.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. One moment! :books:

#

With routeRules: { '/': { prerender: true } }, Nuxt will prerender the root route at build time, generating a static HTML and payload for /. This means that on initial load and on client-side navigation via <NuxtLink>, Nuxt should serve the prerendered HTML and payload, and should not re-fetch data from your API on the client. The data from useAsyncData should be included in the payload and hydrated on the client, avoiding a second fetch Shared Prerender Data.

If you are still seeing a client-side fetch after navigation, even with prerender: true, the knowledge sources do not provide enough information to explain this behavior. According to the documentation, this should not happen if everything is configured correctly and you are using a recent version of Nuxt with the relevant fixes.

Summary:

  • With prerender: true, Nuxt should serve the static payload and not re-fetch on the client.
  • If you still see client-side fetches, the knowledge sources do not contain enough information to explain why, and this may indicate a bug or an unaddressed scenario in Nuxt.

If this persists, it may be necessary to create a minimal reproduction and report it to the Nuxt team for further investigation.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@south finch Kapa.ai is still learning and improving, please let me know how I did by reacting below.