Major Changes
-
#15668
1118ac4Thanks @florian-lefebvre! - Changes TypeScript configuration - (v6 upgrade guidance) -
#15726
6f19eccThanks @ocavue! - Updates dependencyshikito v4Check Shiki's upgrade guide.
Minor Changes
-
#15694
66449c9Thanks @matthewp! - AddspreserveBuildClientDiroption to adapter featuresAdapters can now opt in to preserving the client/server directory structure for static builds by setting
preserveBuildClientDir: truein their adapter features. When enabled, static builds will output files tobuild.clientinstead of directly tooutDir.This is useful for adapters that require a consistent directory structure regardless of the build output type, such as deploying to platforms with specific file organization requirements.
// my-adapter/index.js export default function myAdapter() { return { name: 'my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: 'my-adapter', adapterFeatures: { buildOutput: 'static', preserveBuildClientDir: true, }, }); }, }, }; }
-
#15579
08437d5Thanks @ascorbic! - Adds two new experimental flags for a Route Caching API and further configuration-level Route Rules for controlling SSR response caching.Route caching gives you a platform-agnostic way to cache server-rendered responses, based on web standard cache headers. You set caching directives in your routes using
Astro.cache(in.astropages) orcontext.cache(in API routes and middleware), and Astro translates them into the appropriate headers or runtime behavior depending on your adapter. You can also define cache rules for routes declaratively in your config usingexperimental.routeRules, without modifying route code.This feature requires on-demand rendering. Prerendered pages are already static and do not use route caching.
Getting started
Enable the feature by configuring
experimental.cachewith a cache provider in your Astro config:// astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; import { memoryCache } from 'astro/config'; export default defineConfig({ adapter: node({ mode: 'standalone' }), experimental: { cache: { provider: memoryCache(), }, }, });
Using
Astro.cacheandcontext.cacheIn
.astropages, useAstro.cache.set()to control caching:--- // src/pages/index.astro Astro.cache.set({ maxAge: 120, // Cache for 2 minutes swr: 60, // Serve stale for 1 minute while revalidating tags: ['home'], // Tag for targeted invalidation }); --- <html><body>Cached page</body></html>
In API routes and middleware, use
context.cache:// src/pages/api/data.ts export function GET(context) { context.cache.set({ maxAge: 300, tags: ['api', 'data'], }); return Response.json({ ok: true }); }
Cache options
cache.set()accepts the following options:maxAge(number): Time in seconds the response is considered fresh.swr(number): Stale-while-revalidate window in seconds. During this window, stale content is served while a fresh response is generated in the background.tags(string[]): Cache tags for targeted invalidation. Tags accumulate across multipleset()calls within a request.lastModified(Date): When multipleset()calls providelastModified, the most recent date wins.etag(string): Entity tag for conditional requests.
Call
cache.set(false)to explicitly opt out of caching for a request.Multiple calls to
cache.set()within a single request are merged: scalar values use last-write-wins,lastModifieduses most-recent-wins, and tags accumulate.Invalidation
Purge cached entries by tag or path using
cache.invalidate():// Invalidate all entries tagged 'data' await context.cache.invalidate({ tags: ['data'] }); // Invalidate a specific path await context.cache.invalidate({ path: '/api/data' });
Config-level route rules
Use
experimental.routeRulesto set default cache options for routes without modifying route code. Supports Nitro-style shortcuts for ergonomic configuration:import { memoryCache } from 'astro/config'; export default defineConfig({ experimental: { cache: { provider: memoryCache(), }, routeRules: { // Shortcut form (Nitro-style) '/api/*': { swr: 600 }, // Full form with nested cache '/products/*': { cache: { maxAge: 3600, tags: ['products'] } }, }, }, });
Route patterns support static paths, dynamic parameters (
[slug]), and rest parameters ([...path]). Per-routecache.set()calls merge with (and can override) the config-level defaults.You can also read the current cache state via
cache.options:const { maxAge, swr, tags } = context.cache.options;
Cache providers
Cache behavior is determined by the configured cache provider. There are two types:
- CDN providers set response headers (e.g.
CDN-Cache-Control,Cache-Tag) and let the CDN handle caching. Astro strips these headers before sending the response to the client. - Runtime providers implement
onRequest()to intercept and cache responses in-process, adding anX-Astro-Cacheheader (HIT/MISS/STALE) for observability.
Built-in memory cache provider
Astro includes a built-in, in-memory LRU runtime cache provider. Import
memoryCachefromastro/configto configure it.Features:
- In-memory LRU cache with configurable max entries (default: 1000)
- Stale-while-revalidate support
- Tag-based and path-based invalidation
X-Astro-Cacheresponse header:HIT,MISS, orSTALE- Query parameter sorting for better hit rates (
?b=2&a=1and?a=1&b=2hit the same entry) - Common tracking parameters (
utm_*,fbclid,gclid, etc.) excluded from cache keys by default Varyheader support — responses that setVaryautomatically get separate cache entries per variant- Configurable query parameter filtering via
query.exclude(glob patterns) andquery.include(allowlist)
For more information on enabling and using this feature in your project, see the Experimental Route Caching docs.
For a complete overview and to give feedback on this experimental API, see the Route Caching RFC.
Patch Changes
-
#15721
e6e146cThanks @matthewp! - Fixes action route handling to return 404 for requests to prototype method names likeconstructorortoStringused as action paths -
#15704
862d77bThanks @umutkeltek! - Fixes i18n fallback middleware intercepting non-404 responsesThe fallback middleware was triggering for all responses with status >= 300, including legitimate 3xx redirects, 403 forbidden, and 5xx server errors. This broke auth flows and form submissions on localized server routes. The fallback now correctly only triggers for 404 (page not found) responses.
-
#15703
829182bThanks @matthewp! - Fixes server islands returning a 500 error in dev mode for adapters that do not setadapterFeatures.buildOutput(e.g.@astrojs/netlify) -
#15749
573d188Thanks @ascorbic! - Fixes a bug that causedsession.regenerate()to silently lose session dataPreviously, regenerated session data was not saved under the new session ID unless
set()was also called. -
#15685
1a323e5Thanks @jcayzac! - Fix regression where SVG images in content collectionimage()fields could not be rendered as inline components. This behavior is now restored while preserving the TLA deadlock fix. -
#15740
c5016fcThanks @matthewp! - Removes an escape hatch that skipped attribute escaping for URL values containing&, ensuring all dynamic attribute values are consistently escaped -
#15744
fabb710Thanks @matthewp! - Fixes cookie handling during error page rendering to ensure cookies set by middleware are consistently included in the response -
#15742
9d9699cThanks @matthewp! - HardensclientAddressresolution to respectsecurity.allowedDomainsforX-Forwarded-For, consistent with the existing handling ofX-Forwarded-Host,X-Forwarded-Proto, andX-Forwarded-Port. TheX-Forwarded-Forheader is now only used to determineAstro.clientAddresswhen the request's host has been validated against anallowedDomainsentry. Without a matching domain,clientAddressfalls back to the socket's remote address. -
#15696
a9fd221Thanks @Princesseuh! - Fixes images not working in MDX when using the Cloudflare adapter in certain cases -
#15693
4db2089Thanks @ArmandPhilippot! - Fixes the links to Astro Docs to match the v6 structure. -
#15717
4000aaaThanks @matthewp! - Ensures that URLs with multiple leading slashes (e.g.//admin) are normalized to a single slash before reaching middleware, so that pathname checks likecontext.url.pathname.startsWith('/admin')work consistently regardless of the request URL format -
#15752
918d394Thanks @ascorbic! - Fixes an issue where a session ID from a cookie with no matching server-side data was accepted as-is. The session now generates a new ID when the cookie value has no corresponding storage entry. -
#15743
3b4252aThanks @matthewp! - Hardens config-based redirects with catch-all parameters to prevent producing protocol-relative URLs (e.g.//example.com) in theLocationheader -
#15718
14f37b8Thanks @florian-lefebvre! - Fixes a case where internal headers may leak when rendering error pages -
Updated dependencies [
6f19ecc,f94d3c5]:- @astrojs/markdown-remark@7.0.0-beta.9