github withastro/astro astro@6.0.0-beta.18

pre-release7 hours ago

Major Changes

Minor Changes

  • #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.

    Getting started

    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(),
        },
      },
    });

    Using Astro.cache and context.cache

    In .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 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 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.

    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.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 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 an X-Astro-Cache header (HIT/MISS/STALE) for observability.

    Built-in memory cache provider

    Astro includes a built-in, in-memory LRU runtime cache provider. Import memoryCache from astro/config to 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-Cache response header: HIT, MISS, or STALE
    • Query parameter sorting for better hit rates (?b=2&a=1 and ?a=1&b=2 hit the same entry)
    • Common tracking parameters (utm_*, fbclid, gclid, etc.) excluded from cache keys by default
    • Vary header support — responses that set Vary automatically get separate cache entries per variant
    • Configurable query parameter filtering via query.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.

Patch Changes

  • #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]:

    • @astrojs/markdown-remark@7.0.0-beta.9

Don't miss a new astro release

NewReleases is sending notifications on new releases.