#how can you speed up navigation in astro?

11 messages · Page 1 of 1 (latest)

hollow berry
#

frameworks written in js can do "invalidate on load but use cached js for navigation" really easily

astro can't reuse the cache so easily for navigation, and prefetching might not even have much of an effect when cache-control is set to public, max-age=0, must-revalidate (default on a few providers, including netlify and cf static assets).

what are the best strategies to speed up navigation? the generic "use a good cache-control, turn on prefetchall, turn on speculation rules"? some way to do "cache on same-origin navigation but not on initial load"?

lofty pendantBOT
#

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

#

Astro gives you a few different tools that, combined, get you close to “fast SPA-like navigation,” but they work within normal HTTP caching rules rather than bypassing them.

1. Use Astro’s prefetching

Enable prefetch globally and (optionally) for all links:

// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  prefetch: {
    prefetchAll: true,
    defaultStrategy: 'viewport', // or 'hover' / 'load'
  },
});

This injects a small script that:

  • Prefetches internal links using <link rel="prefetch"> when supported, falling back to fetch() otherwise. [Prefetch guide; Browser support]
  • Respects data-saver and slow connections, and lets you tune behavior per-link via data-astro-prefetch. [Prefetch guide]

You can also prefetch programmatically for non-<a> navigations:

<script>
  import { prefetch } from 'astro:prefetch';
  prefetch('/next-page');
</script>

[Prefetch programmatically]

2. (Optional) Enable client-side prerendering (Speculation Rules)

To get closer to SPA-like “instant” navigations, you can turn on the experimental clientPrerender flag, which uses the Speculation Rules API to prerender prefetched pages in supported browsers (currently Chrome):

// astro.config.mjs
export default defineConfig({
  prefetch: {
    prefetchAll: true,
    defaultStrategy: 'viewport',
  },
  experimental: {
    clientPrerender: true,
  },
});
#

This upgrades prefetching so that, instead of just caching the response, the browser can prerender the whole page and swap it in on navigation. [Client prerender; Prefetch browser support]

You can further tune eagerness when calling prefetch():

prefetch('/data-heavy', { eagerness: 'conservative' });
prefetch('/critical-step'); // defaults to 'immediate'

[Prefetch programmatically]

3. Make cache headers work with prefetch

Astro’s prefetch relies on the browser cache. With Cache-Control: public, max-age=0, must-revalidate, the browser can still reuse cached responses but will revalidate on navigation. Prefetch still helps because:

The docs explicitly recommend:

“To best support all browsers, make sure your pages have the proper cache headers.” [Browser support – Recommendations]

So, where you can, ensure:

  • Static/prerendered pages: your host sets ETag (most do).
  • SSR/on‑demand pages: you set appropriate Cache-Control/ETag/Last-Modified yourself.
#

There isn’t a built‑in “cache only on same‑origin navigation but not on initial load” toggle in Astro; caching behavior is governed by normal HTTP caching and your host’s behavior. The docs don’t describe a way to distinguish “first load vs navigation” at the HTTP level.

4. Use client-side routing (View Transitions SPA mode)

If you want SPA-like navigations that reuse already-loaded JS, you can opt into Astro’s <ClientRouter /> (view transitions SPA mode). When you do:

  • Client-side routing is used between pages.
  • Prefetching is automatically enabled with { prefetchAll: true } by default, which aggressively prefetches internal links. [Using with View Transitions]

You can still override the global prefetch config to tune or disable it.

This doesn’t change HTTP caching semantics, but it does mean:

  • The shell JS/CSS is loaded once.
  • Subsequent navigations reuse that shell and only fetch HTML for the new page, which is then swapped in.

5. Platform-specific caching for SSR / on-demand pages

If you’re using SSR or on‑demand rendering, your adapter can help:

  • Netlify: cacheOnDemandPages: true caches on-demand pages for up to a year, and you can override per-page with headers like CDN-Cache-Control. [Netlify caching pages]
  • Vercel: ISR (isr option) caches rendered pages and lets you control expiration and invalidation. [Vercel ISR; ISR cache invalidation]
#

These don’t give you “no cache on first load, cache on navigation only,” but they do ensure that once a page has been rendered, subsequent navigations (including prefetched ones) are served quickly from the edge cache.


Within the docs you provided, the main levers for faster navigation are:

  • Turn on prefetch (and optionally prefetchAll + tuned strategies).
  • Optionally enable experimental experimental.clientPrerender for speculation‑rules‑based prerendering.
  • Use <ClientRouter /> if you want SPA-style client routing plus automatic prefetching.
  • Ensure your cache headers (and platform cache features like ISR/Netlify caching) cooperate with prefetching.

The knowledge sources do not describe any Astro-specific mechanism to “cache only on same-origin navigation but not on initial load”; that level of behavior is left to HTTP caching and your hosting platform.

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

#

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

hollow berry
#

@lofty pendant so... what i said already, the best you can do is fetch ahead of time? but that feels very workaroundy compared to in sveltekit where it's more "use some js you might have cached for ∞"

lofty pendantBOT
#

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

#

You’re correct that, within what the docs describe, Astro’s main built‑in lever for “faster navigation” is to fetch (and optionally prerender) ahead of time, plus optional client‑side routing. There isn’t a documented mechanism equivalent to “reuse an always‑cached JS app shell and just invalidate data” like you get in some JS‑first meta‑frameworks.

From the sources:

  • Prefetch: Astro adds a small script that prefetches internal links via <link rel="prefetch"> or fetch(), with configurable strategies (hover, tap, viewport, load) and prefetchAll. [Prefetch guide; Prefetch options]
  • Client prerender (Speculation Rules): Experimental experimental.clientPrerender upgrades prefetch to prerender in supported browsers (Chrome), so the whole next page is rendered in the background and swapped in on navigation. [Client prerender; Prefetch eagerness]
  • ClientRouter / SPA mode: If you opt into <ClientRouter />, Astro turns your MPA into a SPA‑style client router, intercepting navigation and swapping bodies, and it enables prefetching for all links by default ({ prefetchAll: true }). [View transitions overview; Using with View Transitions; Client-side navigation process]

Those features still operate within normal HTTP caching; the docs don’t describe:

#
  • Any Astro‑specific way to say “cache only for same‑origin navigations but not for initial load.”
  • Any special “forever‑cached JS shell” mechanism beyond what you’d normally get from caching your static assets in /_astro/ aggressively (which is recommended, but is just HTTP caching). [Starlight caching]

So, based on the docs you provided, the “best you can do” in Astro today is essentially:

  1. Aggressive prefetching (prefetchAll, tuned strategies, optional programmatic prefetch()), possibly plus
  2. Experimental client prerendering for instant swaps in supporting browsers, and/or
  3. <ClientRouter /> SPA mode so the already‑loaded JS/CSS shell is reused between navigations.

The knowledge sources do not mention a more “first‑class” app‑shell/data‑invalidation model like SvelteKit’s; anything beyond the above would be outside what’s documented.

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