Conversation
🦋 Changeset detectedLatest commit: 2a10742 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…time Adds the foundational building blocks for the route caching feature (RFC #1245): - CacheOptions, CacheProvider, CacheProviderFactory, InvalidateOptions types - Zod config schema (CacheSchema) registered under experimental.cache - AstroCache class with set() merge semantics, tags accumulation, invalidate(), _applyHeaders() - NoopAstroCache for dev mode - Utility functions: normalizeCacheDriverConfig, cacheConfigToManifest, defaultSetHeaders - Public type exports from astro package
Integrates the cache system into the full request lifecycle: - Virtual module vite-plugin for cache driver resolution (resolves from project root) - SSRManifest: cacheDriver and cacheConfig fields - BasePipeline: getCacheProvider() with lazy resolution - RenderContext: creates AstroCache/NoopAstroCache per request, exposes on Astro.cache and context.cache (API routes, actions, middleware) - App base: wraps onRequest for runtime providers, strips CDN headers after; applies _applyHeaders for CDN-based providers - Serialized manifest: cache driver import + config serialization - Throwing getter on Astro global for prerendered routes
Compiles cache.routes patterns using Astro's existing route parsing infrastructure (getParts, getPattern, routeComparator). Patterns use the same [param]/[...rest] syntax as file-based routing. Most specific route wins, computed once at startup via compileCacheRoutes().
72 unit tests covering: - AstroCache runtime: set() merge semantics, tags, invalidate, _applyHeaders, _isActive - Utils: defaultSetHeaders, normalizeCacheDriverConfig, cacheConfigToManifest, isCacheHint, isLiveDataEntry - NoopAstroCache: all methods callable and no-op - Route matching: exact paths, dynamic params, rest params, priority ordering 6 integration tests with mock CDN provider: - CDN-Cache-Control and Cache-Tag from context.cache.set() - cache.set(false) opt-out, tags-only, config-level routes - .astro page and API route support
Runtime cache provider for @astrojs/node with: - In-memory LRU cache (custom Map-based, no external deps) - SWR support via stale-while-revalidate with background revalidation - Tag-based and path-based invalidation - X-Astro-Cache response header (HIT/MISS/STALE) for observability - Auto-set as default when experimental.cache is enabled without a driver - Exported as @astrojs/node/cache subpath
8 tests covering the full runtime caching lifecycle: - Default driver auto-configuration - Cache hit/miss behavior with body verification - CDN header stripping for runtime providers - Tag-based and path-based invalidation - cache.set(false) opt-out - Uncached route passthrough
Full phased implementation plan for RFC #1245 covering: - Phases 1-3: Core types, pipeline wiring, route matching (complete) - Phase 4: Node adapter with in-memory LRU (complete) - Phase 5: Cloudflare adapter with CacheW - Phase 6: Vercel & Netlify adapters - Phase 7: Documentation
Replace _applyHeaders() and _isActive underscore convention with symbol-keyed methods behind applyCacheHeaders() and isCacheActive() helper functions. The symbols are not exported from the astro package, so users can't access them. Framework call sites import the helpers from the module directly. NoopAstroCache no longer needs the internal methods — the helpers handle the no-op case via 'in' checks.
Breaking: cache.routes moved to experimental.routeRules This commit addresses RFC #1245 feedback: 1. Terminology: Rename 'driver' to 'provider' throughout the cache API. Providers have a richer interface (headers, middleware, invalidation) than the simpler driver pattern used by sessions/unstorage. 2. Configuration: Move cache route config from cache.routes to experimental.routeRules with Nitro-style shortcuts: - Flat cache options: { maxAge: 600, swr: 60 } - Nested form: { cache: { maxAge: 600 } } - Prerender control: { prerender: true } 3. API: Add cache.options getter for full cache state access, complementing the existing cache.tags getter. Files renamed: - cache-driver.ts → cache-provider.ts (both astro + node adapter) - mock-cache-driver.mjs → mock-cache-provider.mjs (test fixture) All 109 tests pass (95 unit + 7 core + 7 node adapter).
routeRules is focused on cache configuration only. Prerender control will be handled separately if/when it's added to the feature.
1d9acb9 to
4c23661
Compare
4c23661 to
35aaf46
Compare
…tructor, fix test import)
…om SSR bundle - Move runtime cache code (AstroCache, noop, route-matching, utils) into cache/runtime/ directory following Astro conventions - Remove node:url and node:path imports that leaked into SSR bundle, breaking Cloudflare/edge deployments and verify-no-node-stuff test plugin - Guard getCacheProvider() call in hot path to skip async overhead when no cache provider is configured (addresses CodSpeed benchmark regression)
- Move in-memory LRU cache provider from @astrojs/node to astro core (it's runtime-agnostic, not Node-specific) - Export memoryCache() from astro/config (follows sharpImageService pattern) - Runtime entrypoint at astro/cache/memory (separate from config context) - Remove bare string form for cache provider config (object-only) - Export CacheProviderConfig from public types
The memory cache provider is now in core, so its tests belong here too. Uses testAdapter() + app.render() instead of a live Node server.
# Conflicts: # packages/astro/src/core/app/base.ts
sarah11918
left a comment
There was a problem hiding this comment.
I'll let @yanthomasdev tackle the commas, but just some thoughts from me re: error docs and changeset!
I'll note the changeset is... huge! I do not object, nor do I object to offloading some of that heavy lifting to the docs themselves, if you decide you want to just give more superficial overviews of the various parts of the feature.
|
|
||
| /** | ||
| * @docs | ||
| * @kind heading |
There was a problem hiding this comment.
Just noting that this leaves one errant SVG error at the end, also within this heading.
(Would argue it's that one that's in the wrong place, but if we don't fix it as out of scope in this PR, would be nice to get that error moved somewhere more sensible!
There was a problem hiding this comment.
I moved the SVG one up with the image errors
| public partial: undefined | boolean = undefined, | ||
| public shouldInjectCspMetaTags = pipeline.manifest.shouldInjectCspMetaTags, | ||
| public session: AstroSession | undefined = undefined, | ||
| public cache: AstroCache | NoopAstroCache | DisabledAstroCache = disabledAstroCache, |
There was a problem hiding this comment.
Is there a good reasons to have different types VS just AstroCache?
There was a problem hiding this comment.
I've moved it to use an interface
Changes
Adds a new experimental Route Caching API and Route Rules for controlling SSR response caching. See the RFC for full details.
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.Getting started
Enable the feature by configuring
experimental.cachewith a cache provider in your Astro config:Using
Astro.cacheandcontext.cacheIn
.astropages, useAstro.cache.set()to control caching:In API routes and middleware, use
context.cache: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():Config-level route rules
Use
experimental.routeRulesto set default cache options for routes without modifying route code. Supports Nitro-style shortcuts for ergonomic configuration: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:Cache providers
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 anX-Astro-Cacheheader (HIT/MISS/STALE) for observability.Built-in memory cache provider
Astro includes a built-in in-memory LRU cache provider. Import
memoryCachefromastro/configto configure it.Features:
X-Astro-Cacheresponse header:HIT,MISS, orSTALEWriting a custom cache provider
A cache provider is a module that exports a factory function as its default export:
Error handling
If you use
Astro.cacheorcontext.cachewithout enabling the feature, Astro throws anAstroErrorwith the nameCacheNotEnabledand a message explaining how to configure it. If the configured provider cannot be resolved, Astro throwsCacheProviderNotFoundat build time.Testing
Adds comprehensive test suite
Docs
withastro/docs#13305