From d1ac58e917e78052d2b7dfc6b16f4522e8bb2bb2 Mon Sep 17 00:00:00 2001 From: Oliver Speir <115520730+OliverSpeir@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:44:35 -0600 Subject: [PATCH 1/8] fix: cloudflare passthrough image service endpoint (#15794) --- .changeset/cloudflare-passthrough-endpoint.md | 5 ++ packages/integrations/cloudflare/package.json | 1 + .../entrypoints/image-passthrough-endpoint.ts | 56 +++++++++++++++++++ .../cloudflare/src/utils/image-config.ts | 9 ++- 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 .changeset/cloudflare-passthrough-endpoint.md create mode 100644 packages/integrations/cloudflare/src/entrypoints/image-passthrough-endpoint.ts diff --git a/.changeset/cloudflare-passthrough-endpoint.md b/.changeset/cloudflare-passthrough-endpoint.md new file mode 100644 index 000000000000..280e84ad440b --- /dev/null +++ b/.changeset/cloudflare-passthrough-endpoint.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +Fixes image serving in `passthrough` mode by using the Cloudflare `ASSETS` binding instead of generic fetch, which does not work in Workers for local assets diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json index 2d4f1d966012..bfb4b04d594f 100644 --- a/packages/integrations/cloudflare/package.json +++ b/packages/integrations/cloudflare/package.json @@ -25,6 +25,7 @@ "./entrypoints/server.js": "./dist/entrypoints/server.js", "./image-service": "./dist/entrypoints/image-service-external.js", "./image-transform-endpoint": "./dist/entrypoints/image-transform-endpoint.js", + "./image-passthrough-endpoint": "./dist/entrypoints/image-passthrough-endpoint.js", "./image-service-workerd": "./dist/entrypoints/image-service-workerd.js", "./handler": "./dist/utils/handler.js", "./types.d.ts": "./types.d.ts", diff --git a/packages/integrations/cloudflare/src/entrypoints/image-passthrough-endpoint.ts b/packages/integrations/cloudflare/src/entrypoints/image-passthrough-endpoint.ts new file mode 100644 index 000000000000..a925b5b32ae5 --- /dev/null +++ b/packages/integrations/cloudflare/src/entrypoints/image-passthrough-endpoint.ts @@ -0,0 +1,56 @@ +import { imageConfig } from 'astro:assets'; +import { isRemotePath } from '@astrojs/internal-helpers/path'; +import { isRemoteAllowed } from '@astrojs/internal-helpers/remote'; +import type { APIRoute } from 'astro'; +import { env } from 'cloudflare:workers'; + +export const prerender = false; + +export const GET: APIRoute = async ({ request }) => { + try { + const url = new URL(request.url); + const href = url.searchParams.get('href'); + if (!href) return new Response('Bad Request', { status: 400 }); + + const isRemote = isRemotePath(href); + + let response: Response; + + if (isRemote) { + if (!isRemoteAllowed(href, imageConfig)) { + return new Response('Forbidden', { status: 403 }); + } + response = await fetch(href, { redirect: 'manual' }); + } else { + const sourceUrl = new URL(href, url.origin); + if (sourceUrl.origin !== url.origin) { + return new Response('Forbidden', { status: 403 }); + } + response = await env.ASSETS.fetch(new Request(sourceUrl, { headers: request.headers })); + } + + if (response.status >= 300 && response.status < 400) { + return new Response('Not Found', { status: 404 }); + } + + if (!response.ok) { + return new Response('Not Found', { status: 404 }); + } + + const contentType = response.headers.get('Content-Type') ?? ''; + if (!contentType.startsWith('image/')) { + return new Response('Forbidden', { status: 403 }); + } + + const headers = new Headers(); + headers.set('Content-Type', contentType); + headers.set('Cache-Control', 'public, max-age=31536000'); + headers.set('Date', new Date().toUTCString()); + const etag = response.headers.get('ETag'); + if (etag) headers.set('ETag', etag); + + return new Response(response.body, { status: 200, headers }); + } catch (_err) { + return new Response('Internal Server Error', { status: 500 }); + } +}; diff --git a/packages/integrations/cloudflare/src/utils/image-config.ts b/packages/integrations/cloudflare/src/utils/image-config.ts index fb0bb71190fc..a86db46234f9 100644 --- a/packages/integrations/cloudflare/src/utils/image-config.ts +++ b/packages/integrations/cloudflare/src/utils/image-config.ts @@ -39,6 +39,11 @@ export function normalizeImageServiceConfig(config: ImageServiceConfig | undefin // Use the generic endpoint instead, which loads images via fetch through the dev server. const GENERIC_ENDPOINT = { entrypoint: 'astro/assets/endpoint/generic' }; +// Passthrough endpoint that serves original images via the ASSETS binding. +const CLOUDFLARE_PASSTHROUGH_ENDPOINT = { + entrypoint: '@astrojs/cloudflare/image-passthrough-endpoint', +}; + // Workerd-compatible image service stub: baseService (no sharp) + passthrough transform. // Used by both `compile` and `cloudflare-binding` for URL generation in workerd. const WORKERD_IMAGE_SERVICE = { entrypoint: '@astrojs/cloudflare/image-service-workerd' }; @@ -56,7 +61,7 @@ export function setImageConfig( return { ...config, service: passthroughImageService(), - endpoint: command === 'dev' ? GENERIC_ENDPOINT : config.endpoint, + endpoint: command === 'dev' ? GENERIC_ENDPOINT : CLOUDFLARE_PASSTHROUGH_ENDPOINT, }; case 'cloudflare': @@ -83,7 +88,7 @@ export function setImageConfig( endpoint: command === 'dev' || runtimeService === 'cloudflare-binding' ? { entrypoint: '@astrojs/cloudflare/image-transform-endpoint' } - : GENERIC_ENDPOINT, + : CLOUDFLARE_PASSTHROUGH_ENDPOINT, }; case 'custom': From b2bd27bcb605d1e44e94ab922a8d7d2aa685149d Mon Sep 17 00:00:00 2001 From: Oliver Speir <115520730+OliverSpeir@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:48:28 -0600 Subject: [PATCH 2/8] Feat(cloudflare): configure prerender environment (#15711) * feat(cloudflare): add prerenderEnvironment config option * feat(cloudflare): dev server prerender middleware * chore: add changeset * fix changeset * Update .changeset/cloudflare-prerender-environment.md Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> * fix(cloudflare): register prerender middleware after Vite security checks * feat(cloudflare): route dev prerender handling through Astro core middleware * fix: remove additional astro environment * adds tests * update lockfile * fix: add todo comment back * move dev prerender environment from core to adapter * fix: remove unnecessary astro environment invalidation in routes plugin * Remove unused settingsSymbol * fix: add astro changeset for dev prerender core handling * Don't skip dots * Update .changeset/astro-dev-prerender-core-fixes.md Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --------- Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> Co-authored-by: Matthew Phillips Co-authored-by: Matthew Phillips --- .changeset/astro-dev-prerender-core-fixes.md | 7 + .../cloudflare-prerender-environment.md | 19 +++ packages/astro/src/core/constants.ts | 5 + .../src/vite-plugin-astro-server/plugin.ts | 146 ++++++++++++++---- packages/astro/src/vite-plugin-css/index.ts | 29 ++-- .../astro/src/vite-plugin-routes/index.ts | 25 ++- packages/integrations/cloudflare/src/index.ts | 63 +++++--- ...-plugin-dev-server-prerender-middleware.ts | 33 ++++ .../prerender-node-env/astro.config.mjs | 3 + .../fixtures/prerender-node-env/package.json | 9 ++ .../prerender-node-env/src/pages/index.astro | 24 +++ .../prerender-node-env/src/pages/ssr.astro | 19 +++ .../test/prerender-node-env.test.js | 64 ++++++++ pnpm-lock.yaml | 9 ++ 14 files changed, 381 insertions(+), 74 deletions(-) create mode 100644 .changeset/astro-dev-prerender-core-fixes.md create mode 100644 .changeset/cloudflare-prerender-environment.md create mode 100644 packages/integrations/cloudflare/src/vite-plugin-dev-server-prerender-middleware.ts create mode 100644 packages/integrations/cloudflare/test/fixtures/prerender-node-env/astro.config.mjs create mode 100644 packages/integrations/cloudflare/test/fixtures/prerender-node-env/package.json create mode 100644 packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/index.astro create mode 100644 packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/ssr.astro create mode 100644 packages/integrations/cloudflare/test/prerender-node-env.test.js diff --git a/.changeset/astro-dev-prerender-core-fixes.md b/.changeset/astro-dev-prerender-core-fixes.md new file mode 100644 index 000000000000..ff9c0b783ea8 --- /dev/null +++ b/.changeset/astro-dev-prerender-core-fixes.md @@ -0,0 +1,7 @@ +--- +astro: patch +--- + +Improves Astro core's dev environment handling for prerendered routes by ensuring route/CSS updates and prerender middleware behavior work correctly across both SSR and prerender environments. + +This enables integrations that use Astro's prerender dev environment (such as Cloudflare with `prerenderEnvironment: 'node'`) to get consistent route matching and HMR behavior during development. diff --git a/.changeset/cloudflare-prerender-environment.md b/.changeset/cloudflare-prerender-environment.md new file mode 100644 index 000000000000..c777f33eaf1d --- /dev/null +++ b/.changeset/cloudflare-prerender-environment.md @@ -0,0 +1,19 @@ +--- +'@astrojs/cloudflare': minor +--- + +Adds a `prerenderEnvironment` option to the Cloudflare adapter. + +By default, Cloudflare uses its workerd runtime for prerendering static pages. Set `prerenderEnvironment` to `'node'` to use Astro's built-in Node.js prerender environment instead, giving prerendered pages access to the full Node.js ecosystem during both build and dev. This is useful when your prerendered pages depend on Node.js-specific APIs or NPM packages that aren't compatible with workerd. + +```js +// astro.config.mjs +import cloudflare from '@astrojs/cloudflare'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + adapter: cloudflare({ + prerenderEnvironment: 'node', + }), +}); +``` diff --git a/packages/astro/src/core/constants.ts b/packages/astro/src/core/constants.ts index bd3094b90be4..38e31d7ab3bd 100644 --- a/packages/astro/src/core/constants.ts +++ b/packages/astro/src/core/constants.ts @@ -81,6 +81,11 @@ export const originPathnameSymbol = Symbol.for('astro.originPathname'); */ export const pipelineSymbol = Symbol.for('astro.pipeline'); +/** + * Use this symbol to opt into handling prerender routes in Astro core dev middleware. + */ +export const devPrerenderMiddlewareSymbol = Symbol.for('astro.devPrerenderMiddleware'); + /** * The symbol used as a field on the request object to store a cleanup callback associated with aborting the request when the underlying socket closes. */ diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index e893a5d5a689..615237bd06b4 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -5,7 +5,10 @@ import { isRunnableDevEnvironment, type RunnableDevEnvironment } from 'vite'; import { toFallbackType } from '../core/app/common.js'; import { toRoutingStrategy } from '../core/app/entrypoints/index.js'; import type { SSRManifest, SSRManifestCSP, SSRManifestI18n } from '../core/app/types.js'; -import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../core/constants.js'; +import { + ASTRO_VITE_ENVIRONMENT_NAMES, + devPrerenderMiddlewareSymbol, +} from '../core/constants.js'; import { getAlgorithm, getDirectives, @@ -22,6 +25,7 @@ import { AstroError, AstroErrorData } from '../core/errors/index.js'; import type { Logger } from '../core/logger/core.js'; import { NOOP_MIDDLEWARE_FN } from '../core/middleware/noop-middleware.js'; import { createViteLoader } from '../core/module-loader/index.js'; +import { matchAllRoutes } from '../core/routing/match.js'; import { resolveMiddlewareMode } from '../integrations/adapter-utils.js'; import { SERIALIZED_MANIFEST_ID } from '../manifest/serialized.js'; import type { AstroSettings } from '../types/astro.js'; @@ -50,24 +54,41 @@ export default function createVitePluginAstroServer({ return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr; }, async configureServer(viteServer) { - // Cloudflare handles its own requests + const ssrEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]; + const prerenderEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.prerender]; + + const runnableSsrEnvironment = isRunnableDevEnvironment(ssrEnvironment) + ? (ssrEnvironment as RunnableDevEnvironment) + : undefined; + const runnablePrerenderEnvironment = isRunnableDevEnvironment(prerenderEnvironment) + ? (prerenderEnvironment as RunnableDevEnvironment) + : undefined; + // TODO: let this handle non-runnable environments that don't intercept requests - if (!isRunnableDevEnvironment(viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr])) { + if (!runnableSsrEnvironment && !runnablePrerenderEnvironment) { return; } - const environment = viteServer.environments[ - ASTRO_VITE_ENVIRONMENT_NAMES.ssr - ] as RunnableDevEnvironment; - const loader = createViteLoader(viteServer, environment); - const { default: createAstroServerApp } = - await environment.runner.import< - typeof import('../vite-plugin-app/createAstroServerApp.js') - >(ASTRO_DEV_SERVER_APP_ID); - const controller = createController({ loader }); - const { handler } = await createAstroServerApp(controller, settings, loader, logger); - const { manifest } = await environment.runner.import<{ - manifest: SSRManifest; - }>(SERIALIZED_MANIFEST_ID); + + async function createHandler(environment: RunnableDevEnvironment) { + const loader = createViteLoader(viteServer, environment); + const { default: createAstroServerApp } = + await environment.runner.import< + typeof import('../vite-plugin-app/createAstroServerApp.js') + >(ASTRO_DEV_SERVER_APP_ID); + const controller = createController({ loader }); + const { handler } = await createAstroServerApp(controller, settings, loader, logger); + const { manifest } = await environment.runner.import<{ + manifest: SSRManifest; + }>(SERIALIZED_MANIFEST_ID); + return { controller, handler, loader, manifest, environment }; + } + + const ssrHandler = runnableSsrEnvironment + ? await createHandler(runnableSsrEnvironment) + : undefined; + const prerenderHandler = runnablePrerenderEnvironment + ? await createHandler(runnablePrerenderEnvironment) + : undefined; const localStorage = new AsyncLocalStorage(); function handleUnhandledRejection(rejection: any) { @@ -78,14 +99,25 @@ export default function createVitePluginAstroServer({ message: AstroErrorData.UnhandledRejection.message(rejection?.stack || rejection), }); const store = localStorage.getStore(); - if (store instanceof IncomingMessage) { - setRouteError(controller.state, store.url!, error); + const handlers = []; + if (ssrHandler) handlers.push(ssrHandler); + if (prerenderHandler) handlers.push(prerenderHandler); + for (const currentHandler of handlers) { + if (store instanceof IncomingMessage) { + setRouteError(currentHandler.controller.state, store.url!, error); + } + const { errorWithMetadata } = recordServerError( + currentHandler.loader, + currentHandler.manifest, + logger, + error, + ); + setTimeout( + async () => + currentHandler.loader.webSocketSend(await getViteErrorPayload(errorWithMetadata)), + 200, + ); } - const { errorWithMetadata } = recordServerError(loader, manifest, logger, error); - setTimeout( - async () => loader.webSocketSend(await getViteErrorPayload(errorWithMetadata)), - 200, - ); } process.on('unhandledRejection', handleUnhandledRejection); @@ -94,6 +126,14 @@ export default function createVitePluginAstroServer({ }); return () => { + const shouldHandlePrerenderInCore = Boolean( + (viteServer as any)[devPrerenderMiddlewareSymbol], + ); + + if (!ssrHandler && !(prerenderHandler && shouldHandlePrerenderInCore)) { + return; + } + // Push this middleware to the front of the stack so that it can intercept responses. // fix(#6067): always inject this to ensure zombie base handling is killed after restarts viteServer.middlewares.stack.unshift({ @@ -115,18 +155,58 @@ export default function createVitePluginAstroServer({ handle: secFetchMiddleware(logger, settings.config.security?.allowedDomains), }); - // Note that this function has a name so other middleware can find it. - viteServer.middlewares.use(async function astroDevHandler(request, response) { - if (request.url === undefined || !request.method) { - response.writeHead(500, 'Incomplete request'); - response.end(); - return; - } + if (prerenderHandler && shouldHandlePrerenderInCore) { + viteServer.middlewares.use( + async function astroDevPrerenderHandler(request, response, next) { + if (request.url === undefined || !request.method) { + response.writeHead(500, 'Incomplete request'); + response.end(); + return; + } + + if (request.url.startsWith('/@') || request.url.startsWith('/__')) { + return next(); + } + + if (request.url.includes('/node_modules/')) { + return next(); + } - localStorage.run(request, () => { - handler(request, response); + try { + const pathname = decodeURI(new URL(request.url, 'http://localhost').pathname); + const { routes } = + await prerenderHandler.environment.runner.import('virtual:astro:routes'); + const routesList = { routes: routes.map((r: any) => r.routeData) }; + const matches = matchAllRoutes(pathname, routesList); + + if (!matches.some((route) => route.prerender)) { + return next(); + } + + localStorage.run(request, () => { + prerenderHandler.handler(request, response); + }); + } catch (err) { + next(err); + } + }, + ); + } + + if (ssrHandler) { + // Note that this function has a name so other middleware can find it. + viteServer.middlewares.use(async function astroDevHandler(request, response) { + if (request.url === undefined || !request.method) { + response.writeHead(500, 'Incomplete request'); + response.end(); + return; + } + + localStorage.run(request, () => { + ssrHandler.handler(request, response); + }); }); - }); + } }; }, }; diff --git a/packages/astro/src/vite-plugin-css/index.ts b/packages/astro/src/vite-plugin-css/index.ts index 015cdb00c3f3..5ce8d76658a1 100644 --- a/packages/astro/src/vite-plugin-css/index.ts +++ b/packages/astro/src/vite-plugin-css/index.ts @@ -92,21 +92,26 @@ function* collectCSSWithOrder( * @param routesList */ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOptions): Plugin[] { - let ssrEnvironment: undefined | DevEnvironment = undefined; + let server: vite.ViteDevServer | undefined; // Cache CSS content by module ID to avoid re-reading const cssContentCache = new Map(); + function getCurrentEnvironment(pluginEnv?: DevEnvironment): DevEnvironment | undefined { + return pluginEnv ?? server?.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr] as DevEnvironment | undefined; + } + return [ { name: MODULE_DEV_CSS, - async configureServer(server) { - ssrEnvironment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]; + async configureServer(viteServer) { + server = viteServer; }, applyToEnvironment(env) { return ( env.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || - env.name === ASTRO_VITE_ENVIRONMENT_NAMES.client + env.name === ASTRO_VITE_ENVIRONMENT_NAMES.client || + env.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender ); }, @@ -144,9 +149,11 @@ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOption // The virtual module name for this page, like virtual:astro:dev-css:index@_@astro const componentPageId = getVirtualModulePageNameForComponent(componentPath); + const env = getCurrentEnvironment(this.environment as DevEnvironment); + // Ensure the page module is loaded. This will populate the graph and allow us to walk through. - await ssrEnvironment?.fetchModule(componentPageId); - const resolved = await ssrEnvironment?.pluginContainer.resolveId(componentPageId); + await env?.fetchModule(componentPageId); + const resolved = await env?.pluginContainer.resolveId(componentPageId); if (!resolved?.id) { return { @@ -155,7 +162,7 @@ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOption } // the vite.EnvironmentModuleNode has all of the info we need - const mod = ssrEnvironment?.moduleGraph.getModuleById(resolved.id); + const mod = env?.moduleGraph.getModuleById(resolved.id); if (!mod) { return { @@ -164,7 +171,7 @@ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOption } // Walk through the graph depth-first - for (const collected of collectCSSWithOrder(componentPageId, mod!)) { + for (const collected of collectCSSWithOrder(componentPageId, mod)) { // Use the CSS file ID as the key to deduplicate while keeping best ordering if (!cssWithOrder.has(collected.idKey)) { // Look up actual content from cache if available @@ -200,7 +207,8 @@ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOption } // Cache CSS content as we see it - const mod = ssrEnvironment?.moduleGraph.getModuleById(id); + const env = getCurrentEnvironment(this.environment as DevEnvironment); + const mod = env?.moduleGraph.getModuleById(id); if (mod) { cssContentCache.set(id, code); } @@ -210,11 +218,10 @@ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOption { name: MODULE_DEV_CSS_ALL, applyToEnvironment(env) { - // This should only run in dev mode so `prerender` is excluded. return ( env.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || env.name === ASTRO_VITE_ENVIRONMENT_NAMES.client || - env.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro + env.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender ); }, resolveId: { diff --git a/packages/astro/src/vite-plugin-routes/index.ts b/packages/astro/src/vite-plugin-routes/index.ts index b41f779bc749..b3b260951b82 100644 --- a/packages/astro/src/vite-plugin-routes/index.ts +++ b/packages/astro/src/vite-plugin-routes/index.ts @@ -109,15 +109,26 @@ export default async function astroPluginRoutes({ routeData: serializeRouteData(r, settings.config.trailingSlash), }; }); - let environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]; - const virtualMod = environment.moduleGraph.getModuleById(ASTRO_ROUTES_MODULE_ID_RESOLVED); - if (!virtualMod) return; + const environmentsToInvalidate = []; + for (const name of [ + ASTRO_VITE_ENVIRONMENT_NAMES.ssr, + ASTRO_VITE_ENVIRONMENT_NAMES.prerender, + ] as const) { + const environment = server.environments[name]; + if (environment) { + environmentsToInvalidate.push(environment); + } + } - environment.moduleGraph.invalidateModule(virtualMod); + for (const environment of environmentsToInvalidate) { + const virtualMod = environment.moduleGraph.getModuleById(ASTRO_ROUTES_MODULE_ID_RESOLVED); + if (!virtualMod) continue; - // Signal that routes have changed so running apps can update - // NOTE: Consider adding debouncing here if rapid file changes cause performance issues - environment.hot.send('astro:routes-updated', {}); + environment.moduleGraph.invalidateModule(virtualMod); + // Signal that routes have changed so running apps can update + // NOTE: Consider adding debouncing here if rapid file changes cause performance issues + environment.hot.send('astro:routes-updated', {}); + } } } return { diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 620239a10a52..8fff5f713836 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -13,6 +13,7 @@ import { setImageConfig, } from './utils/image-config.js'; import { createConfigPlugin } from './vite-plugin-config.js'; +import { createNodePrerenderPlugin } from './vite-plugin-dev-server-prerender-middleware.js'; import { cloudflareConfigCustomizer, DEFAULT_SESSION_KV_BINDING_NAME, @@ -76,6 +77,14 @@ export interface Options */ imagesBindingName?: string; + /** + * Controls which runtime is used for prerendering static pages at build time. + * + * - `'workerd'` (default): Uses Cloudflare's workerd runtime. + * - `'node'`: Uses Astro's default node prerender environment. + */ + prerenderEnvironment?: 'workerd' | 'node'; + experimental?: Pick< NonNullable, 'headersAndRedirectsDevModeSupport' @@ -86,6 +95,7 @@ export default function createIntegration({ imageService, sessionKVBindingName = DEFAULT_SESSION_KV_BINDING_NAME, imagesBindingName = DEFAULT_IMAGES_BINDING_NAME, + prerenderEnvironment = 'workerd', ...cloudflareOptions }: Options = {}): AstroIntegration { let _config: AstroConfig; @@ -146,20 +156,22 @@ export default function createIntegration({ imagesBindingName: needsImagesBinding || needsImagesBindingForDev ? imagesBindingName : false, }), - experimental: { - prerenderWorker: { - config(_, { entryWorkerConfig }) { - return { - ...entryWorkerConfig, - name: 'prerender', - ...(needsImagesBinding && - !entryWorkerConfig.images && { - images: { binding: imagesBindingName }, - }), - }; + ...(prerenderEnvironment === 'workerd' && { + experimental: { + prerenderWorker: { + config(_, { entryWorkerConfig }) { + return { + ...entryWorkerConfig, + name: 'prerender', + ...(needsImagesBinding && + !entryWorkerConfig.images && { + images: { binding: imagesBindingName }, + }), + }; + }, }, }, - }, + }), }; // The preview entrypoint uses Cloudflare's vite plugin and so it needs access @@ -175,6 +187,9 @@ export default function createIntegration({ session, vite: { plugins: [ + ...(prerenderEnvironment === 'node' && command === 'dev' + ? [createNodePrerenderPlugin()] + : []), cfVitePlugin({ ...cloudflareOptions, ...cfPluginConfig, @@ -344,17 +359,19 @@ export default function createIntegration({ } }, 'astro:build:start': ({ setPrerenderer }) => { - setPrerenderer( - createCloudflarePrerenderer({ - root: _config.root, - serverDir: _config.build.server, - clientDir: _config.build.client, - base: _config.base, - trailingSlash: _config.trailingSlash, - cfPluginConfig, - hasCompileImageService: buildService === 'compile', - }), - ); + if (prerenderEnvironment === 'workerd') { + setPrerenderer( + createCloudflarePrerenderer({ + root: _config.root, + serverDir: _config.build.server, + clientDir: _config.build.client, + base: _config.base, + trailingSlash: _config.trailingSlash, + cfPluginConfig, + hasCompileImageService: buildService === 'compile', + }), + ); + } }, 'astro:build:setup': ({ vite, target }) => { if (target === 'server') { diff --git a/packages/integrations/cloudflare/src/vite-plugin-dev-server-prerender-middleware.ts b/packages/integrations/cloudflare/src/vite-plugin-dev-server-prerender-middleware.ts new file mode 100644 index 000000000000..25fc90ba6d13 --- /dev/null +++ b/packages/integrations/cloudflare/src/vite-plugin-dev-server-prerender-middleware.ts @@ -0,0 +1,33 @@ +import type * as vite from 'vite'; + +const devPrerenderMiddlewareSymbol = Symbol.for('astro.devPrerenderMiddleware'); + +/** + * Enables Astro core prerender middleware in dev so prerendered routes can + * run in Node while non-prerendered routes continue through workerd. + */ +export function createNodePrerenderPlugin(): vite.Plugin { + return { + name: '@astrojs/cloudflare:dev-server-prerender-middleware', + + config() { + return { + environments: { + prerender: { dev: {} }, + }, + }; + }, + + // Disable dep optimization for the `prerender` environment so dependencies + // are loaded via native import() with correct import.meta.url semantics. + configEnvironment(environmentName) { + if (environmentName === 'prerender') { + return { optimizeDeps: { noDiscovery: true, include: [] } }; + } + }, + + configureServer(server) { + (server as any)[devPrerenderMiddlewareSymbol] = true; + }, + }; +} diff --git a/packages/integrations/cloudflare/test/fixtures/prerender-node-env/astro.config.mjs b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/astro.config.mjs new file mode 100644 index 000000000000..86dbfb924824 --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/astro.config.mjs @@ -0,0 +1,3 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({}); diff --git a/packages/integrations/cloudflare/test/fixtures/prerender-node-env/package.json b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/package.json new file mode 100644 index 000000000000..19e3ed1bd820 --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/astro-cloudflare-prerender-node-env", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/cloudflare": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/index.astro new file mode 100644 index 000000000000..92c69edf9b55 --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/index.astro @@ -0,0 +1,24 @@ +--- +export const prerender = true; + +import fs from 'node:fs'; +import path from 'node:path'; + +const packageJsonPath = path.join(process.cwd(), 'package.json'); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); +--- + + + + + Prerendered with Node + + + +

Prerendered

+

{packageJson.name}

+ + diff --git a/packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/ssr.astro b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/ssr.astro new file mode 100644 index 000000000000..8a4ecac1deb2 --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/prerender-node-env/src/pages/ssr.astro @@ -0,0 +1,19 @@ +--- +export const prerender = false; + +const hasCf = Astro.request.cf ? 'true' : 'false'; +--- + + + + + SSR page + + + +

SSR

+

{hasCf}

+ + diff --git a/packages/integrations/cloudflare/test/prerender-node-env.test.js b/packages/integrations/cloudflare/test/prerender-node-env.test.js new file mode 100644 index 000000000000..6c4cca4e48da --- /dev/null +++ b/packages/integrations/cloudflare/test/prerender-node-env.test.js @@ -0,0 +1,64 @@ +import * as assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import cloudflare from '../dist/index.js'; +import { loadFixture } from './_test-utils.js'; + +describe('prerenderEnvironment: node', () => { + /** @type {import('../../../astro/test/test-utils').Fixture} */ + let fixture; + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/prerender-node-env/', import.meta.url).toString(), + adapter: cloudflare({ + prerenderEnvironment: 'node', + }), + }); + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer?.stop(); + await fixture.clean(); + }); + + it('renders prerendered page using Node.js APIs', async () => { + const res = await fixture.fetch('/'); + assert.equal(res.status, 200); + const html = await res.text(); + assert.ok( + html.includes('id="pkg-name"'), + 'Expected the prerendered page to contain the pkg-name element', + ); + // The package.json name should be read via node:fs — cwd resolves to + // the cloudflare package root, so we check for that name + assert.ok( + html.includes('@astrojs/cloudflare'), + 'Expected node:fs to successfully read a package.json name', + ); + }); + + it('includes styles in prerendered page', async () => { + const res = await fixture.fetch('/'); + const html = await res.text(); + assert.ok( + html.includes('rebeccapurple'), + 'Expected scoped styles to be included in the prerendered page', + ); + }); + + it('renders SSR page through workerd with Astro.request.cf', async () => { + const res = await fixture.fetch('/ssr'); + assert.equal(res.status, 200); + const html = await res.text(); + assert.ok( + html.includes('id="has-cf"'), + 'Expected the SSR page to contain the has-cf element', + ); + assert.ok( + html.includes('>true<'), + 'Expected Astro.request.cf to be available in the SSR page', + ); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25e6a71825d3..593b7672fc23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5018,6 +5018,15 @@ importers: specifier: workspace:* version: link:../../../../../astro + packages/integrations/cloudflare/test/fixtures/prerender-node-env: + dependencies: + '@astrojs/cloudflare': + specifier: workspace:* + version: link:../../.. + astro: + specifier: workspace:* + version: link:../../../../../astro + packages/integrations/cloudflare/test/fixtures/prerender-styles: dependencies: '@astrojs/cloudflare': From 50fcc8be3b3ae67558933b61fa00c706de27dc40 Mon Sep 17 00:00:00 2001 From: Giray Date: Wed, 11 Mar 2026 19:49:12 +0300 Subject: [PATCH 3/8] fix(cloudflare): show actionable error when running preview without prior build (#15845) * fix(cloudflare): show actionable error when running preview without build (fix #15837) * fix(cloudflare): fix resolve naming conflict --- .changeset/bright-apples-travel.md | 5 +++++ .../cloudflare/src/entrypoints/preview.ts | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .changeset/bright-apples-travel.md diff --git a/.changeset/bright-apples-travel.md b/.changeset/bright-apples-travel.md new file mode 100644 index 000000000000..c13a0065b39a --- /dev/null +++ b/.changeset/bright-apples-travel.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +fix: show actionable error when running astro preview without prior build diff --git a/packages/integrations/cloudflare/src/entrypoints/preview.ts b/packages/integrations/cloudflare/src/entrypoints/preview.ts index e6d8affc2020..46da435bcd85 100644 --- a/packages/integrations/cloudflare/src/entrypoints/preview.ts +++ b/packages/integrations/cloudflare/src/entrypoints/preview.ts @@ -1,3 +1,5 @@ +import { existsSync } from 'node:fs'; +import { resolve as resolvePath } from 'node:path'; import type { CreatePreviewServer } from 'astro'; import { preview, @@ -20,6 +22,14 @@ const createPreviewServer: CreatePreviewServer = async ({ host, root, }) => { + const wranglerConfigPath = resolvePath(fileURLToPath(root), '.wrangler/deploy/config.json'); + if (!existsSync(wranglerConfigPath)) { + logger.error( + 'No build output found. Run `astro build` before running `astro preview`.', + ); + process.exit(1); + } + const startServerTime = performance.now(); let previewServer: VitePreviewServer; @@ -150,4 +160,4 @@ function getNetworkLogging(host: string | undefined): 'none' | 'host-to-expose' } } -export { createPreviewServer as default }; +export { createPreviewServer as default }; \ No newline at end of file From 0c8121664a66de159c7fd0ed781e994b5e2932d0 Mon Sep 17 00:00:00 2001 From: Giray Date: Wed, 11 Mar 2026 16:51:29 +0000 Subject: [PATCH 4/8] [ci] format --- packages/astro/src/vite-plugin-astro-server/plugin.ts | 5 +---- packages/astro/src/vite-plugin-css/index.ts | 5 ++++- .../integrations/cloudflare/src/entrypoints/preview.ts | 6 ++---- .../cloudflare/test/prerender-node-env.test.js | 10 ++-------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index 615237bd06b4..f4866f99b5a0 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -5,10 +5,7 @@ import { isRunnableDevEnvironment, type RunnableDevEnvironment } from 'vite'; import { toFallbackType } from '../core/app/common.js'; import { toRoutingStrategy } from '../core/app/entrypoints/index.js'; import type { SSRManifest, SSRManifestCSP, SSRManifestI18n } from '../core/app/types.js'; -import { - ASTRO_VITE_ENVIRONMENT_NAMES, - devPrerenderMiddlewareSymbol, -} from '../core/constants.js'; +import { ASTRO_VITE_ENVIRONMENT_NAMES, devPrerenderMiddlewareSymbol } from '../core/constants.js'; import { getAlgorithm, getDirectives, diff --git a/packages/astro/src/vite-plugin-css/index.ts b/packages/astro/src/vite-plugin-css/index.ts index 5ce8d76658a1..33d2c8adde00 100644 --- a/packages/astro/src/vite-plugin-css/index.ts +++ b/packages/astro/src/vite-plugin-css/index.ts @@ -97,7 +97,10 @@ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOption const cssContentCache = new Map(); function getCurrentEnvironment(pluginEnv?: DevEnvironment): DevEnvironment | undefined { - return pluginEnv ?? server?.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr] as DevEnvironment | undefined; + return ( + pluginEnv ?? + (server?.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr] as DevEnvironment | undefined) + ); } return [ diff --git a/packages/integrations/cloudflare/src/entrypoints/preview.ts b/packages/integrations/cloudflare/src/entrypoints/preview.ts index 46da435bcd85..cf98a30f99f6 100644 --- a/packages/integrations/cloudflare/src/entrypoints/preview.ts +++ b/packages/integrations/cloudflare/src/entrypoints/preview.ts @@ -24,9 +24,7 @@ const createPreviewServer: CreatePreviewServer = async ({ }) => { const wranglerConfigPath = resolvePath(fileURLToPath(root), '.wrangler/deploy/config.json'); if (!existsSync(wranglerConfigPath)) { - logger.error( - 'No build output found. Run `astro build` before running `astro preview`.', - ); + logger.error('No build output found. Run `astro build` before running `astro preview`.'); process.exit(1); } @@ -160,4 +158,4 @@ function getNetworkLogging(host: string | undefined): 'none' | 'host-to-expose' } } -export { createPreviewServer as default }; \ No newline at end of file +export { createPreviewServer as default }; diff --git a/packages/integrations/cloudflare/test/prerender-node-env.test.js b/packages/integrations/cloudflare/test/prerender-node-env.test.js index 6c4cca4e48da..672093cf3950 100644 --- a/packages/integrations/cloudflare/test/prerender-node-env.test.js +++ b/packages/integrations/cloudflare/test/prerender-node-env.test.js @@ -52,13 +52,7 @@ describe('prerenderEnvironment: node', () => { const res = await fixture.fetch('/ssr'); assert.equal(res.status, 200); const html = await res.text(); - assert.ok( - html.includes('id="has-cf"'), - 'Expected the SSR page to contain the has-cf element', - ); - assert.ok( - html.includes('>true<'), - 'Expected Astro.request.cf to be available in the SSR page', - ); + assert.ok(html.includes('id="has-cf"'), 'Expected the SSR page to contain the has-cf element'); + assert.ok(html.includes('>true<'), 'Expected Astro.request.cf to be available in the SSR page'); }); }); From ca76ff1dedafdc764f551e753e0772b54f807fa1 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 11 Mar 2026 13:07:55 -0400 Subject: [PATCH 5/8] Harden server island POST endpoint to use own-property checks (#15765) Co-authored-by: astro-security[bot] --- .changeset/harden-server-island-validation.md | 5 +++ .../astro/src/core/server-islands/endpoint.ts | 4 +- .../units/server-islands/endpoint.test.js | 38 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .changeset/harden-server-island-validation.md diff --git a/.changeset/harden-server-island-validation.md b/.changeset/harden-server-island-validation.md new file mode 100644 index 000000000000..29dc809acb19 --- /dev/null +++ b/.changeset/harden-server-island-validation.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Hardens server island POST endpoint validation to use own-property checks for improved consistency diff --git a/packages/astro/src/core/server-islands/endpoint.ts b/packages/astro/src/core/server-islands/endpoint.ts index 7b2c4d9bd58b..c7f08298c47f 100644 --- a/packages/astro/src/core/server-islands/endpoint.ts +++ b/packages/astro/src/core/server-islands/endpoint.ts @@ -84,12 +84,12 @@ export async function getRequestData( const data = JSON.parse(raw); // Validate that slots is not plaintext - if ('slots' in data && typeof data.slots === 'object') { + if (Object.hasOwn(data, 'slots') && typeof data.slots === 'object') { return badRequest('Plaintext slots are not allowed. Slots must be encrypted.'); } // Validate that componentExport is not plaintext - if ('componentExport' in data && typeof data.componentExport === 'string') { + if (Object.hasOwn(data, 'componentExport') && typeof data.componentExport === 'string') { return badRequest( 'Plaintext componentExport is not allowed. componentExport must be encrypted.', ); diff --git a/packages/astro/test/units/server-islands/endpoint.test.js b/packages/astro/test/units/server-islands/endpoint.test.js index 63e9d9f4dbd4..3cbe5abca746 100644 --- a/packages/astro/test/units/server-islands/endpoint.test.js +++ b/packages/astro/test/units/server-islands/endpoint.test.js @@ -146,6 +146,44 @@ describe('getRequestData', () => { assert.equal(result.encryptedProps, ''); assert.equal(result.encryptedSlots, ''); }); + + it('only checks own properties for `slots` validation', async () => { + // Temporarily pollute Object.prototype to simulate inherited properties + Object.prototype.slots = { default: 'polluted' }; + try { + const req = makePostRequest({ + encryptedComponentExport: 'encExport', + encryptedProps: 'encProps', + encryptedSlots: 'encSlots', + }); + const result = await getRequestData(req); + assert.ok( + !(result instanceof Response), + `Expected RenderOptions but got Response with status ${result instanceof Response ? result.status : 'N/A'} — inherited 'slots' should not trigger rejection`, + ); + } finally { + delete Object.prototype.slots; + } + }); + + it('only checks own properties for `componentExport` validation', async () => { + // Temporarily pollute Object.prototype to simulate inherited properties + Object.prototype.componentExport = 'default'; + try { + const req = makePostRequest({ + encryptedComponentExport: 'encExport', + encryptedProps: 'encProps', + encryptedSlots: 'encSlots', + }); + const result = await getRequestData(req); + assert.ok( + !(result instanceof Response), + `Expected RenderOptions but got Response with status ${result instanceof Response ? result.status : 'N/A'} — inherited 'componentExport' should not trigger rejection`, + ); + } finally { + delete Object.prototype.componentExport; + } + }); }); // #endregion From 5a77dbc2223d2a091216078bc75a1a1552e18f7e Mon Sep 17 00:00:00 2001 From: "Houston (Bot)" <108291165+astrobot-houston@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:18:53 -0700 Subject: [PATCH 6/8] [ci] release (#15851) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/astro-dev-prerender-core-fixes.md | 7 --- .changeset/bright-apples-travel.md | 5 -- .changeset/cloudflare-passthrough-endpoint.md | 5 -- .../cloudflare-prerender-environment.md | 19 -------- .changeset/dirty-breads-wish.md | 5 -- .changeset/fix-cloudflare-configpath.md | 9 ---- .changeset/harden-server-island-validation.md | 5 -- .changeset/tall-loops-take.md | 5 -- examples/basics/package.json | 2 +- examples/blog/package.json | 2 +- examples/component/package.json | 2 +- examples/container-with-vitest/package.json | 2 +- examples/framework-alpine/package.json | 2 +- examples/framework-multiple/package.json | 2 +- examples/framework-preact/package.json | 2 +- examples/framework-react/package.json | 2 +- examples/framework-solid/package.json | 2 +- examples/framework-svelte/package.json | 2 +- examples/framework-vue/package.json | 2 +- examples/hackernews/package.json | 2 +- examples/integration/package.json | 2 +- examples/minimal/package.json | 2 +- examples/portfolio/package.json | 2 +- examples/ssr/package.json | 2 +- examples/starlog/package.json | 2 +- examples/toolbar-app/package.json | 2 +- examples/with-markdoc/package.json | 2 +- examples/with-mdx/package.json | 2 +- examples/with-nanostores/package.json | 2 +- examples/with-tailwindcss/package.json | 2 +- examples/with-vitest/package.json | 2 +- packages/astro/CHANGELOG.md | 12 +++++ packages/astro/package.json | 2 +- packages/integrations/cloudflare/CHANGELOG.md | 37 +++++++++++++++ packages/integrations/cloudflare/package.json | 2 +- pnpm-lock.yaml | 46 +++++++++---------- 36 files changed, 97 insertions(+), 108 deletions(-) delete mode 100644 .changeset/astro-dev-prerender-core-fixes.md delete mode 100644 .changeset/bright-apples-travel.md delete mode 100644 .changeset/cloudflare-passthrough-endpoint.md delete mode 100644 .changeset/cloudflare-prerender-environment.md delete mode 100644 .changeset/dirty-breads-wish.md delete mode 100644 .changeset/fix-cloudflare-configpath.md delete mode 100644 .changeset/harden-server-island-validation.md delete mode 100644 .changeset/tall-loops-take.md diff --git a/.changeset/astro-dev-prerender-core-fixes.md b/.changeset/astro-dev-prerender-core-fixes.md deleted file mode 100644 index ff9c0b783ea8..000000000000 --- a/.changeset/astro-dev-prerender-core-fixes.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -astro: patch ---- - -Improves Astro core's dev environment handling for prerendered routes by ensuring route/CSS updates and prerender middleware behavior work correctly across both SSR and prerender environments. - -This enables integrations that use Astro's prerender dev environment (such as Cloudflare with `prerenderEnvironment: 'node'`) to get consistent route matching and HMR behavior during development. diff --git a/.changeset/bright-apples-travel.md b/.changeset/bright-apples-travel.md deleted file mode 100644 index c13a0065b39a..000000000000 --- a/.changeset/bright-apples-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/cloudflare': patch ---- - -fix: show actionable error when running astro preview without prior build diff --git a/.changeset/cloudflare-passthrough-endpoint.md b/.changeset/cloudflare-passthrough-endpoint.md deleted file mode 100644 index 280e84ad440b..000000000000 --- a/.changeset/cloudflare-passthrough-endpoint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/cloudflare': patch ---- - -Fixes image serving in `passthrough` mode by using the Cloudflare `ASSETS` binding instead of generic fetch, which does not work in Workers for local assets diff --git a/.changeset/cloudflare-prerender-environment.md b/.changeset/cloudflare-prerender-environment.md deleted file mode 100644 index c777f33eaf1d..000000000000 --- a/.changeset/cloudflare-prerender-environment.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@astrojs/cloudflare': minor ---- - -Adds a `prerenderEnvironment` option to the Cloudflare adapter. - -By default, Cloudflare uses its workerd runtime for prerendering static pages. Set `prerenderEnvironment` to `'node'` to use Astro's built-in Node.js prerender environment instead, giving prerendered pages access to the full Node.js ecosystem during both build and dev. This is useful when your prerendered pages depend on Node.js-specific APIs or NPM packages that aren't compatible with workerd. - -```js -// astro.config.mjs -import cloudflare from '@astrojs/cloudflare'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - adapter: cloudflare({ - prerenderEnvironment: 'node', - }), -}); -``` diff --git a/.changeset/dirty-breads-wish.md b/.changeset/dirty-breads-wish.md deleted file mode 100644 index b89eb1520b7a..000000000000 --- a/.changeset/dirty-breads-wish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes a regression where the the routes emitted by the `astro:build:done` hook didn't have the `distURL` array correctly populated. diff --git a/.changeset/fix-cloudflare-configpath.md b/.changeset/fix-cloudflare-configpath.md deleted file mode 100644 index 932327d1d430..000000000000 --- a/.changeset/fix-cloudflare-configpath.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@astrojs/cloudflare': patch ---- - -fix(cloudflare): forward `configPath` and other `PluginConfig` options to the Cloudflare Vite Plugin - -Options like `configPath`, `inspectorPort`, `persistState`, `remoteBindings`, and `auxiliaryWorkers` were accepted by the type system but never forwarded to `cfVitePlugin()`, making them silently ignored. - -Also fixes `addWatchFile` for `configPath` which resolved the path relative to the adapter's `node_modules` directory instead of the project root. diff --git a/.changeset/harden-server-island-validation.md b/.changeset/harden-server-island-validation.md deleted file mode 100644 index 29dc809acb19..000000000000 --- a/.changeset/harden-server-island-validation.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Hardens server island POST endpoint validation to use own-property checks for improved consistency diff --git a/.changeset/tall-loops-take.md b/.changeset/tall-loops-take.md deleted file mode 100644 index 1e377c54922e..000000000000 --- a/.changeset/tall-loops-take.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/cloudflare': patch ---- - -fix cloudflare image transform ignoring quality parameter diff --git a/examples/basics/package.json b/examples/basics/package.json index b6bf74d5f2e5..048ddf3aa22d 100644 --- a/examples/basics/package.json +++ b/examples/basics/package.json @@ -13,6 +13,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^6.0.2" + "astro": "^6.0.3" } } diff --git a/examples/blog/package.json b/examples/blog/package.json index 8806e1e75b25..bc93678d5a2c 100644 --- a/examples/blog/package.json +++ b/examples/blog/package.json @@ -16,7 +16,7 @@ "@astrojs/mdx": "^5.0.0", "@astrojs/rss": "^4.0.17", "@astrojs/sitemap": "^3.7.1", - "astro": "^6.0.2", + "astro": "^6.0.3", "sharp": "^0.34.3" } } diff --git a/examples/component/package.json b/examples/component/package.json index e1ce8b9b15b6..5e7a39d238fd 100644 --- a/examples/component/package.json +++ b/examples/component/package.json @@ -18,7 +18,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^6.0.2" + "astro": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0 || ^6.0.0" diff --git a/examples/container-with-vitest/package.json b/examples/container-with-vitest/package.json index 358f65c35a53..04d93ad3cd8f 100644 --- a/examples/container-with-vitest/package.json +++ b/examples/container-with-vitest/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@astrojs/react": "^5.0.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", "vitest": "^3.2.4" diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json index 5199ee48b5d4..26fa2c98f665 100644 --- a/examples/framework-alpine/package.json +++ b/examples/framework-alpine/package.json @@ -16,6 +16,6 @@ "@astrojs/alpinejs": "^0.5.0", "@types/alpinejs": "^3.13.11", "alpinejs": "^3.15.8", - "astro": "^6.0.2" + "astro": "^6.0.3" } } diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json index b674b9a9bd23..843134b2119f 100644 --- a/examples/framework-multiple/package.json +++ b/examples/framework-multiple/package.json @@ -20,7 +20,7 @@ "@astrojs/vue": "^6.0.0", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "astro": "^6.0.2", + "astro": "^6.0.3", "preact": "^10.28.4", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json index 4ce0e3d434d1..e070e795aa06 100644 --- a/examples/framework-preact/package.json +++ b/examples/framework-preact/package.json @@ -15,7 +15,7 @@ "dependencies": { "@astrojs/preact": "^5.0.0", "@preact/signals": "^2.8.1", - "astro": "^6.0.2", + "astro": "^6.0.3", "preact": "^10.28.4" } } diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json index 6f350bb02c68..065f84981fb0 100644 --- a/examples/framework-react/package.json +++ b/examples/framework-react/package.json @@ -16,7 +16,7 @@ "@astrojs/react": "^5.0.0", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "astro": "^6.0.2", + "astro": "^6.0.3", "react": "^18.3.1", "react-dom": "^18.3.1" } diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json index f4ecb35cab3f..1b6543d6f9a6 100644 --- a/examples/framework-solid/package.json +++ b/examples/framework-solid/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/solid-js": "^6.0.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "solid-js": "^1.9.11" } } diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json index 9c79cbe0f380..55540a70c823 100644 --- a/examples/framework-svelte/package.json +++ b/examples/framework-svelte/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/svelte": "^8.0.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "svelte": "^5.53.5" } } diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json index e3495a184d1a..4901916bbf56 100644 --- a/examples/framework-vue/package.json +++ b/examples/framework-vue/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/vue": "^6.0.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "vue": "^3.5.29" } } diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index 8d1fcdf42605..90d1088fa71f 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -14,6 +14,6 @@ }, "dependencies": { "@astrojs/node": "^10.0.0", - "astro": "^6.0.2" + "astro": "^6.0.3" } } diff --git a/examples/integration/package.json b/examples/integration/package.json index 07d651529af5..a3cb1a73533c 100644 --- a/examples/integration/package.json +++ b/examples/integration/package.json @@ -18,7 +18,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^6.0.2" + "astro": "^6.0.3" }, "peerDependencies": { "astro": "^4.0.0" diff --git a/examples/minimal/package.json b/examples/minimal/package.json index 346a09693be0..46bc9cb45aad 100644 --- a/examples/minimal/package.json +++ b/examples/minimal/package.json @@ -13,6 +13,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^6.0.2" + "astro": "^6.0.3" } } diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json index bd31884b5b13..f6ef17cdeb2f 100644 --- a/examples/portfolio/package.json +++ b/examples/portfolio/package.json @@ -13,6 +13,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^6.0.2" + "astro": "^6.0.3" } } diff --git a/examples/ssr/package.json b/examples/ssr/package.json index f1526e701667..43f4af177cfd 100644 --- a/examples/ssr/package.json +++ b/examples/ssr/package.json @@ -16,7 +16,7 @@ "dependencies": { "@astrojs/node": "^10.0.0", "@astrojs/svelte": "^8.0.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "svelte": "^5.53.5" } } diff --git a/examples/starlog/package.json b/examples/starlog/package.json index ae999b119354..dfbc1c6b3ebd 100644 --- a/examples/starlog/package.json +++ b/examples/starlog/package.json @@ -9,7 +9,7 @@ "astro": "astro" }, "dependencies": { - "astro": "^6.0.2", + "astro": "^6.0.3", "sass": "^1.97.3", "sharp": "^0.34.3" }, diff --git a/examples/toolbar-app/package.json b/examples/toolbar-app/package.json index 7fccf9e806f7..85c304d3798c 100644 --- a/examples/toolbar-app/package.json +++ b/examples/toolbar-app/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@types/node": "^18.17.8", - "astro": "^6.0.2" + "astro": "^6.0.3" }, "engines": { "node": ">=22.12.0" diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json index c63782127b76..ab8b7d2db922 100644 --- a/examples/with-markdoc/package.json +++ b/examples/with-markdoc/package.json @@ -14,6 +14,6 @@ }, "dependencies": { "@astrojs/markdoc": "^1.0.0", - "astro": "^6.0.2" + "astro": "^6.0.3" } } diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json index 9f06ce842d2f..fe3a86495b2e 100644 --- a/examples/with-mdx/package.json +++ b/examples/with-mdx/package.json @@ -15,7 +15,7 @@ "dependencies": { "@astrojs/mdx": "^5.0.0", "@astrojs/preact": "^5.0.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "preact": "^10.28.4" } } diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json index eb9a3e6ebb06..3c18a953b0dc 100644 --- a/examples/with-nanostores/package.json +++ b/examples/with-nanostores/package.json @@ -15,7 +15,7 @@ "dependencies": { "@astrojs/preact": "^5.0.0", "@nanostores/preact": "^1.0.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "nanostores": "^1.1.1", "preact": "^10.28.4" } diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json index 67ddcbe22089..af203918d9c2 100644 --- a/examples/with-tailwindcss/package.json +++ b/examples/with-tailwindcss/package.json @@ -16,7 +16,7 @@ "@astrojs/mdx": "^5.0.0", "@tailwindcss/vite": "^4.2.1", "@types/canvas-confetti": "^1.9.0", - "astro": "^6.0.2", + "astro": "^6.0.3", "canvas-confetti": "^1.9.4", "tailwindcss": "^4.2.1" } diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json index e994b30e5367..0354f295c730 100644 --- a/examples/with-vitest/package.json +++ b/examples/with-vitest/package.json @@ -14,7 +14,7 @@ "test": "vitest" }, "dependencies": { - "astro": "^6.0.2", + "astro": "^6.0.3", "vitest": "^3.2.4" } } diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index 029d084542d1..73feeba6630d 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,17 @@ # astro +## 6.0.3 + +### Patch Changes + +- [#15711](https://github.com/withastro/astro/pull/15711) [`b2bd27b`](https://github.com/withastro/astro/commit/b2bd27bcb605d1e44e94ab922a8d7d2aa685149d) Thanks [@OliverSpeir](https://github.com/OliverSpeir)! - Improves Astro core's dev environment handling for prerendered routes by ensuring route/CSS updates and prerender middleware behavior work correctly across both SSR and prerender environments. + + This enables integrations that use Astro's prerender dev environment (such as Cloudflare with `prerenderEnvironment: 'node'`) to get consistent route matching and HMR behavior during development. + +- [#15852](https://github.com/withastro/astro/pull/15852) [`1cdaf9f`](https://github.com/withastro/astro/commit/1cdaf9f488e4db158db2c80ce192890b0b9bfa00) Thanks [@ematipico](https://github.com/ematipico)! - Fixes a regression where the the routes emitted by the `astro:build:done` hook didn't have the `distURL` array correctly populated. + +- [#15765](https://github.com/withastro/astro/pull/15765) [`ca76ff1`](https://github.com/withastro/astro/commit/ca76ff1dedafdc764f551e753e0772b54f807fa1) Thanks [@matthewp](https://github.com/matthewp)! - Hardens server island POST endpoint validation to use own-property checks for improved consistency + ## 6.0.2 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index 787d31d56b0d..c0f613f06a4c 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "astro", - "version": "6.0.2", + "version": "6.0.3", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "type": "module", "author": "withastro", diff --git a/packages/integrations/cloudflare/CHANGELOG.md b/packages/integrations/cloudflare/CHANGELOG.md index 858236ea3a24..b72378afe7f2 100644 --- a/packages/integrations/cloudflare/CHANGELOG.md +++ b/packages/integrations/cloudflare/CHANGELOG.md @@ -1,5 +1,42 @@ # @astrojs/cloudflare +## 13.1.0 + +### Minor Changes + +- [#15711](https://github.com/withastro/astro/pull/15711) [`b2bd27b`](https://github.com/withastro/astro/commit/b2bd27bcb605d1e44e94ab922a8d7d2aa685149d) Thanks [@OliverSpeir](https://github.com/OliverSpeir)! - Adds a `prerenderEnvironment` option to the Cloudflare adapter. + + By default, Cloudflare uses its workerd runtime for prerendering static pages. Set `prerenderEnvironment` to `'node'` to use Astro's built-in Node.js prerender environment instead, giving prerendered pages access to the full Node.js ecosystem during both build and dev. This is useful when your prerendered pages depend on Node.js-specific APIs or NPM packages that aren't compatible with workerd. + + ```js + // astro.config.mjs + import cloudflare from '@astrojs/cloudflare'; + import { defineConfig } from 'astro/config'; + + export default defineConfig({ + adapter: cloudflare({ + prerenderEnvironment: 'node', + }), + }); + ``` + +### Patch Changes + +- [#15845](https://github.com/withastro/astro/pull/15845) [`50fcc8b`](https://github.com/withastro/astro/commit/50fcc8be3b3ae67558933b61fa00c706de27dc40) Thanks [@aqiray](https://github.com/aqiray)! - fix: show actionable error when running astro preview without prior build + +- [#15794](https://github.com/withastro/astro/pull/15794) [`d1ac58e`](https://github.com/withastro/astro/commit/d1ac58e917e78052d2b7dfc6b16f4522e8bb2bb2) Thanks [@OliverSpeir](https://github.com/OliverSpeir)! - Fixes image serving in `passthrough` mode by using the Cloudflare `ASSETS` binding instead of generic fetch, which does not work in Workers for local assets + +- [#15850](https://github.com/withastro/astro/pull/15850) [`660da74`](https://github.com/withastro/astro/commit/660da74854c17ecf7dc326c8731b55b4dcc17615) Thanks [@tristanbes](https://github.com/tristanbes)! - fix(cloudflare): forward `configPath` and other `PluginConfig` options to the Cloudflare Vite Plugin + + Options like `configPath`, `inspectorPort`, `persistState`, `remoteBindings`, and `auxiliaryWorkers` were accepted by the type system but never forwarded to `cfVitePlugin()`, making them silently ignored. + + Also fixes `addWatchFile` for `configPath` which resolved the path relative to the adapter's `node_modules` directory instead of the project root. + +- [#15843](https://github.com/withastro/astro/pull/15843) [`fcd237d`](https://github.com/withastro/astro/commit/fcd237d62b1b4fdf499ad5a36694d86ffdcd5250) Thanks [@Calvin-LL](https://github.com/Calvin-LL)! - fix cloudflare image transform ignoring quality parameter + +- Updated dependencies []: + - @astrojs/underscore-redirects@1.0.1 + ## 13.0.2 ### Patch Changes diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json index bfb4b04d594f..e8e92fb381a9 100644 --- a/packages/integrations/cloudflare/package.json +++ b/packages/integrations/cloudflare/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/cloudflare", "description": "Deploy your site to Cloudflare Workers", - "version": "13.0.2", + "version": "13.1.0", "type": "module", "types": "./dist/index.d.ts", "author": "withastro", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 593b7672fc23..a03e0b733713 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,7 +189,7 @@ importers: examples/basics: dependencies: astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/blog: @@ -204,7 +204,7 @@ importers: specifier: ^3.7.1 version: link:../../packages/integrations/sitemap astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro sharp: specifier: ^0.34.3 @@ -213,7 +213,7 @@ importers: examples/component: devDependencies: astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/container-with-vitest: @@ -222,7 +222,7 @@ importers: specifier: ^5.0.0 version: link:../../packages/integrations/react astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro react: specifier: ^18.3.1 @@ -253,7 +253,7 @@ importers: specifier: ^3.15.8 version: 3.15.8 astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/framework-multiple: @@ -280,7 +280,7 @@ importers: specifier: ^18.3.7 version: 18.3.7(@types/react@18.3.28) astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro preact: specifier: ^10.28.4 @@ -310,7 +310,7 @@ importers: specifier: ^2.8.1 version: 2.8.1(preact@10.28.4) astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro preact: specifier: ^10.28.4 @@ -328,7 +328,7 @@ importers: specifier: ^18.3.7 version: 18.3.7(@types/react@18.3.28) astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro react: specifier: ^18.3.1 @@ -343,7 +343,7 @@ importers: specifier: ^6.0.0 version: link:../../packages/integrations/solid astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro solid-js: specifier: ^1.9.11 @@ -355,7 +355,7 @@ importers: specifier: ^8.0.0 version: link:../../packages/integrations/svelte astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro svelte: specifier: ^5.53.5 @@ -367,7 +367,7 @@ importers: specifier: ^6.0.0 version: link:../../packages/integrations/vue astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro vue: specifier: ^3.5.29 @@ -379,25 +379,25 @@ importers: specifier: ^10.0.0 version: link:../../packages/integrations/node astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/integration: devDependencies: astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/minimal: dependencies: astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/portfolio: dependencies: astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/ssr: @@ -409,7 +409,7 @@ importers: specifier: ^8.0.0 version: link:../../packages/integrations/svelte astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro svelte: specifier: ^5.53.5 @@ -418,7 +418,7 @@ importers: examples/starlog: dependencies: astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro sass: specifier: ^1.97.3 @@ -433,7 +433,7 @@ importers: specifier: ^18.17.8 version: 18.19.130 astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/with-markdoc: @@ -442,7 +442,7 @@ importers: specifier: ^1.0.0 version: link:../../packages/integrations/markdoc astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro examples/with-mdx: @@ -454,7 +454,7 @@ importers: specifier: ^5.0.0 version: link:../../packages/integrations/preact astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro preact: specifier: ^10.28.4 @@ -469,7 +469,7 @@ importers: specifier: ^1.0.0 version: 1.0.0(nanostores@1.1.1)(preact@10.28.4) astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro nanostores: specifier: ^1.1.1 @@ -490,7 +490,7 @@ importers: specifier: ^1.9.0 version: 1.9.0 astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro canvas-confetti: specifier: ^1.9.4 @@ -502,7 +502,7 @@ importers: examples/with-vitest: dependencies: astro: - specifier: ^6.0.2 + specifier: ^6.0.3 version: link:../../packages/astro vitest: specifier: ^3.2.4 From bc200b2dc72021a9514feb85574723df8a741709 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 11 Mar 2026 14:24:46 -0400 Subject: [PATCH 7/8] Update minimumReleaseAgeExclude config for Starlight (#15855) * Update minimumReleaseAgeExclude config for Starlight * Update pnpm-workspace.yaml Co-authored-by: Chris Swithinbank --------- Co-authored-by: Chris Swithinbank --- pnpm-workspace.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 03a005f61b84..16fb7962a1ba 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -33,9 +33,7 @@ minimumReleaseAge: 4320 minimumReleaseAgeExclude: # TODO: remove once more stable - '@flue/*' - - '@astrojs/compiler' - - '@astrojs/compiler-rs' - - '@astrojs/compiler-binding*' + - '@astrojs/*' # Renovate security update: fast-xml-parser@5.3.8 - fast-xml-parser@5.3.8 # Renovate security update: svelte@5.53.5 From d1872ee6456e4a8ed821ee2b357465a116690be1 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 11 Mar 2026 16:24:42 -0400 Subject: [PATCH 8/8] fix(cloudflare): prebundle SSR deps for Starlight + Cloudflare dev (#15815) * fix(cloudflare): prebundle SSR deps needed for Starlight dev * fix(cloudflare): drop server entry from optimizeDeps * docs(changeset): add cloudflare optimize deps note * Improve changeset * fix(cloudflare): only prebundle content runtime in dev --- .changeset/quiet-trains-accept.md | 5 ++++ packages/integrations/cloudflare/src/index.ts | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .changeset/quiet-trains-accept.md diff --git a/.changeset/quiet-trains-accept.md b/.changeset/quiet-trains-accept.md new file mode 100644 index 000000000000..c523779ced59 --- /dev/null +++ b/.changeset/quiet-trains-accept.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +Prebundle additional Astro runtime dependencies for Cloudflare development server, speeding up initial start time and preventing required restarts. diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 8fff5f713836..1886b5dbd134 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -47,6 +47,25 @@ function usesCloudflareKVSessionDriver(session: AstroConfig['session']): boolean export type { Runtime } from './utils/handler.js'; +function hasContentCollectionsConfig(srcDir: URL) { + const contentConfigPaths = [ + 'content.config.mjs', + 'content.config.js', + 'content.config.mts', + 'content.config.ts', + 'content/config.mjs', + 'content/config.js', + 'content/config.mts', + 'content/config.ts', + 'live.config.mjs', + 'live.config.js', + 'live.config.mts', + 'live.config.ts', + ]; + + return contentConfigPaths.some((configPath) => existsSync(new URL(`./${configPath}`, srcDir))); +} + export interface Options extends Pick< PluginConfig, @@ -148,6 +167,8 @@ export default function createIntegration({ // (the image-transform-endpoint uses it). At build time, // `compile` uses Sharp on the Node side instead. const needsImagesBindingForDev = isCompile && command === 'dev'; + const usesContentCollections = hasContentCollectionsConfig(config.srcDir); + const prebundleContentRuntime = command === 'dev' && usesContentCollections; cfPluginConfig = { config: cloudflareConfigCustomizer({ @@ -217,6 +238,7 @@ export default function createIntegration({ return { optimizeDeps: { include: [ + '@astrojs/cloudflare/image-service-workerd', 'astro', 'astro/runtime/**', 'astro > html-escaper', @@ -234,8 +256,14 @@ export default function createIntegration({ 'astro > picomatch', 'astro/app', 'astro/assets', + 'astro/assets/runtime', + 'astro/assets/utils/inferRemoteSize.js', + 'astro/assets/fonts/runtime.js', + ...(prebundleContentRuntime ? (['astro/content/runtime'] as const) : []), 'astro/compiler-runtime', + 'astro/jsx-runtime', 'astro/app/entrypoint/dev', + 'astro/virtual-modules/middleware.js', ], exclude: [ 'unstorage/drivers/cloudflare-kv-binding',