release notes
release notes
Published 3/4/2026
Contains new features#15668 1118ac4 Thanks @florian-lefebvre! - Changes TypeScript configuration - (v6 upgrade guidance)
#15726 6f19ecc Thanks @ocavue! - Updates dependency shiki to v4
Check Shiki's upgrade guide.
#15694 66449c9 Thanks @matthewp! - Adds preserveBuildClientDir option to adapter features
Adapters can now opt in to preserving the client/server directory structure for static builds by setting preserveBuildClientDir: true in their adapter features. When enabled, static builds will output files to build.client instead of directly to outDir.
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 08437d5 Thanks @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 .astro pages) or context.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 using experimental.routeRules, without modifying route code.
This feature requires on-demand rendering. Prerendered pages are already static and do not use route caching.
Enable the feature by configuring experimental.cache with 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(),
},
},
});
Astro.cache and context.cacheIn .astro pages, use Astro.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.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 multiple set() calls within a request.lastModified (Date): When multiple set() calls provide lastModified, 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, lastModified uses most-recent-wins, and tags accumulate.
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' });
Use experimental.routeRules to 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-route cache.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 behavior is determined by the configured cache provider. There are two types:
CDN-Cache-Control, Cache-Tag) and let the CDN handle caching. Astro strips these headers before sending the response to the client.onRequest() to intercept and cache responses in-process, adding an X-Astro-Cache header (HIT/MISS/STALE) for observability.Astro includes a built-in, in-memory LRU runtime cache provider. Import memoryCache from astro/config to configure it.
Features:
X-Astro-Cache response header: HIT, MISS, or STALE?b=2&a=1 and ?a=1&b=2 hit the same entry)utm_*, fbclid, gclid, etc.) excluded from cache keys by defaultVary header support — responses that set Vary automatically get separate cache entries per variantquery.exclude (glob patterns) and query.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.
#15721 e6e146c Thanks @matthewp! - Fixes action route handling to return 404 for requests to prototype method names like constructor or toString used as action paths
#15704 862d77b Thanks @umutkeltek! - Fixes i18n fallback middleware intercepting non-404 responses
The 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 829182b Thanks @matthewp! - Fixes server islands returning a 500 error in dev mode for adapters that do not set adapterFeatures.buildOutput (e.g. @astrojs/netlify)
#15749 573d188 Thanks @ascorbic! - Fixes a bug that caused session.regenerate() to silently lose session data
Previously, regenerated session data was not saved under the new session ID unless set() was also called.
#15685 1a323e5 Thanks @jcayzac! - Fix regression where SVG images in content collection image() fields could not be rendered as inline components. This behavior is now restored while preserving the TLA deadlock fix.
#15740 c5016fc Thanks @matthewp! - Removes an escape hatch that skipped attribute escaping for URL values containing &, ensuring all dynamic attribute values are consistently escaped
#15744 fabb710 Thanks @matthewp! - Fixes cookie handling during error page rendering to ensure cookies set by middleware are consistently included in the response
#15742 9d9699c Thanks @matthewp! - Hardens clientAddress resolution to respect security.allowedDomains for X-Forwarded-For, consistent with the existing handling of X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-Port. The X-Forwarded-For header is now only used to determine Astro.clientAddress when the request's host has been validated against an allowedDomains entry. Without a matching domain, clientAddress falls back to the socket's remote address.
#15696 a9fd221 Thanks @Princesseuh! - Fixes images not working in MDX when using the Cloudflare adapter in certain cases
#15693 4db2089 Thanks @ArmandPhilippot! - Fixes the links to Astro Docs to match the v6 structure.
#15717 4000aaa Thanks @matthewp! - Ensures that URLs with multiple leading slashes (e.g. //admin) are normalized to a single slash before reaching middleware, so that pathname checks like context.url.pathname.startsWith('/admin') work consistently regardless of the request URL format
#15752 918d394 Thanks @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 3b4252a Thanks @matthewp! - Hardens config-based redirects with catch-all parameters to prevent producing protocol-relative URLs (e.g. //example.com) in the Location header
#15718 14f37b8 Thanks @florian-lefebvre! - Fixes a case where internal headers may leak when rendering error pages
Updated dependencies [6f19ecc, f94d3c5]:
release notes
Published 3/4/2026
Contains new features#15668 1118ac4 Thanks @florian-lefebvre! - Changes TypeScript configuration - (v6 upgrade guidance)
#15726 6f19ecc Thanks @ocavue! - Updates dependency shiki to v4
Check Shiki's upgrade guide.
#15694 66449c9 Thanks @matthewp! - Adds preserveBuildClientDir option to adapter features
Adapters can now opt in to preserving the client/server directory structure for static builds by setting preserveBuildClientDir: true in their adapter features. When enabled, static builds will output files to build.client instead of directly to outDir.
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 08437d5 Thanks @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 .astro pages) or context.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 using experimental.routeRules, without modifying route code.
This feature requires on-demand rendering. Prerendered pages are already static and do not use route caching.
Enable the feature by configuring experimental.cache with 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(),
},
},
});
Astro.cache and context.cacheIn .astro pages, use Astro.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.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 multiple set() calls within a request.lastModified (Date): When multiple set() calls provide lastModified, 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, lastModified uses most-recent-wins, and tags accumulate.
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' });
Use experimental.routeRules to 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-route cache.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 behavior is determined by the configured cache provider. There are two types:
CDN-Cache-Control, Cache-Tag) and let the CDN handle caching. Astro strips these headers before sending the response to the client.onRequest() to intercept and cache responses in-process, adding an X-Astro-Cache header (HIT/MISS/STALE) for observability.Astro includes a built-in, in-memory LRU runtime cache provider. Import memoryCache from astro/config to configure it.
Features:
X-Astro-Cache response header: HIT, MISS, or STALE?b=2&a=1 and ?a=1&b=2 hit the same entry)utm_*, fbclid, gclid, etc.) excluded from cache keys by defaultVary header support — responses that set Vary automatically get separate cache entries per variantquery.exclude (glob patterns) and query.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.
#15721 e6e146c Thanks @matthewp! - Fixes action route handling to return 404 for requests to prototype method names like constructor or toString used as action paths
#15704 862d77b Thanks @umutkeltek! - Fixes i18n fallback middleware intercepting non-404 responses
The 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 829182b Thanks @matthewp! - Fixes server islands returning a 500 error in dev mode for adapters that do not set adapterFeatures.buildOutput (e.g. @astrojs/netlify)
#15749 573d188 Thanks @ascorbic! - Fixes a bug that caused session.regenerate() to silently lose session data
Previously, regenerated session data was not saved under the new session ID unless set() was also called.
#15685 1a323e5 Thanks @jcayzac! - Fix regression where SVG images in content collection image() fields could not be rendered as inline components. This behavior is now restored while preserving the TLA deadlock fix.
#15740 c5016fc Thanks @matthewp! - Removes an escape hatch that skipped attribute escaping for URL values containing &, ensuring all dynamic attribute values are consistently escaped
#15744 fabb710 Thanks @matthewp! - Fixes cookie handling during error page rendering to ensure cookies set by middleware are consistently included in the response
#15742 9d9699c Thanks @matthewp! - Hardens clientAddress resolution to respect security.allowedDomains for X-Forwarded-For, consistent with the existing handling of X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-Port. The X-Forwarded-For header is now only used to determine Astro.clientAddress when the request's host has been validated against an allowedDomains entry. Without a matching domain, clientAddress falls back to the socket's remote address.
#15696 a9fd221 Thanks @Princesseuh! - Fixes images not working in MDX when using the Cloudflare adapter in certain cases
#15693 4db2089 Thanks @ArmandPhilippot! - Fixes the links to Astro Docs to match the v6 structure.
#15717 4000aaa Thanks @matthewp! - Ensures that URLs with multiple leading slashes (e.g. //admin) are normalized to a single slash before reaching middleware, so that pathname checks like context.url.pathname.startsWith('/admin') work consistently regardless of the request URL format
#15752 918d394 Thanks @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 3b4252a Thanks @matthewp! - Hardens config-based redirects with catch-all parameters to prevent producing protocol-relative URLs (e.g. //example.com) in the Location header
#15718 14f37b8 Thanks @florian-lefebvre! - Fixes a case where internal headers may leak when rendering error pages
Updated dependencies [6f19ecc, f94d3c5]:
The web framework for content-driven websites. ⭐️ Star to support our work!