AstroEco is Contributing…
Display your GitHub pull requests using astro-loader-github-prs
Changes
The markdown.processor part of withastro/roadmap#1364, please see the RFC for details.
Testing
Updated tests that relied on specific remark plugins and what not. Added tests using a custom processor to make sure it works from e2e
Docs
Changes
Added a minimal reproduction for the transport invoke timed out after 60000ms error that occurs when running Astro dev server inside Docker with WSL2 backend on Windows, and also on macOS.
Reproduction is located at reproductions/docker-wsl-timeout/ and includes:
Dockerfiledocker-compose.ymlpackage.jsonREADME.mdwith steps to reproduce
Testing
This was tested by following the Docker/WSL2 setup described in the README. The error appears in container logs after starting the dev server and hitting http://localhost:4321.
No code changes were made to the core Astro package, so no changeset is needed.
Docs
No docs changes needed. This only adds a reproduction case for an existing open issue.
Changes
- Upgrades
@flue/sdk@0.3.10→@flue/runtime@0.7.0and@flue/cli@0.3.10→@flue/cli@0.7.0. The SDK package was renamed to@flue/runtimein 0.6.0. - Migrates all agents and workflows from the removed
defineCommand/commandsAPI to the newlocal()sandbox factory from@flue/runtime/node. Thelocal()sandbox runs commands directly on the host viachild_process.exec, so shell pipes work natively — fixing a bug where piped commands (e.g.gh ... | node -e "...") hung indefinitely under the old in-memory bash emulator. - Updates structured output calls to destructure
{ data }fromprompt()/skill()results, matching the new return shape.
The pipe bug caused a triage job timeout when the verify agent ran gh issue view | node -e "..." — the old defineCommand executor used execFile which cannot pipe stdin between commands.
Testing
- Created a temporary
test-node-pipeagent that exercises plainnode -e,echo | node -e, andgh | node -e— all three pass with the newlocal()sandbox. The test agent is not included in this PR.
Docs
- No docs update needed — these are internal CI agent infrastructure changes with no user-facing impact.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to 5-legacy, this PR will be updated.
Releases
astro@5.18.2
Patch Changes
Fixes #16838.
Changes
The problem (#16838): @astrojs/sitemap writes per-URL <lastmod> into the child sitemaps but never into the <sitemap> entries of sitemap-index.xml. The index gets a <lastmod> only if you set the global lastmod option, and then every entry carries the same date. So the index cannot tell a crawler which child sitemap actually changed — even though the freshness data is already computed and sitting in the child sitemaps.
This PR derives each index entry's <lastmod> from the child sitemap it points to:
- Each
<sitemap>entry is stamped with the most recent<lastmod>among the URLs that land in that file. URLs are written in source order,limitper file, so the date is computed fromitems.slice(i * limit, (i + 1) * limit). - Works for both chunked (
chunks) and non-chunked output, and stays accurate when a sitemap overflows into multiple numbered files. - When a child sitemap has no per-URL
lastmod, the entry falls back to the configuredlastmodoption — existing behaviour preserved. customSitemapsentries keep using the globallastmod(there are no items to derive a date from).
Before / after, for the reproduction in #16838:
<!-- before -->
<sitemap><loc>https://example.com/sitemap-0.xml</loc></sitemap>
<!-- after -->
<sitemap><loc>https://example.com/sitemap-0.xml</loc><lastmod>2024-09-15T00:00:00.000Z</lastmod></sitemap>The changeset is patch. It is a behaviour change for anyone setting per-URL lastmod via serialize (their index now carries accurate per-file dates), so happy to bump to minor if preferred.
Testing
New test/index-lastmod.test.ts:
- Chunked — distinct
lastmodvalues acrossblog/glossarychunks; asserts each index entry surfaces the newest date in its child sitemap, and that a chunk with no per-URLlastmodfalls back to the configuredlastmod. - Non-chunked, multiple files —
entryLimit: 1so each URL gets its own file; asserts every index entry'slastmodequals the date in the child sitemap it points to (exercises the per-file slicing fori > 0).
Full @astrojs/sitemap suite passes (40/40). biome, eslint, knip, and tsc -b are clean.
Docs
No docs change needed — this refines the existing lastmod behaviour with no new or changed API surface. The lastmod option keeps working as a fallback for child sitemaps without per-URL dates.
Changes
Document on config reference page that compressHTML: "jsx" is only available starting Astro 6.2.0.
Without this reference, it looks like the option has always been available but users of older versions (e.g. 6.1.9) will see the following message that contradicts current documentation:
[config] Astro found issue(s) with your configuration:
! compressHTML: Expected type "boolean", received "string"
I tried to follow the writing style from i18n.routing which also introduced a new option, by starting the sentence similarly.
From some past PRs I checked, doesn't seem like minor doc text change requires changeset, please correct me otherwise.
Testing
Not tested since it's a doc text change.
Docs
This is a doc change itself, which I previously mistakenly tried to do on the generated doc: withastro/docs#13920
Description
This PR disables the minimum release age check of 3 days recently added for withastro/automation updates.
While reviewing #3903, and even after retrying the PR, I was confused why the renovate/stability-days check was still pending, even though all the updates were released around 10 days ago (last change for withastro/automation - last release for pnpm/action-setup).
I'm not a Renovate expert, but I think I finally understand the reason:
- The
withastro/automationis a digest update as we're using the latest commit SHA on themainbranch in workflow files without a version tag (@<sha> # main). - As documented here, for digest updates, having a release timestamp that can have
minimumReleaseAgeenforced is "Generally not supported". - In Renovate 42, the absence of a release timestamp will be treated as if the release is not yet past the timestamp, which provides a safer default.
I'm not 100% sure, but I think this is the reason why the renovate/stability-days check is still pending, and will probably be always pending, preventing us from merging other updates.
A few extra points:
- If merging this PR and retrying #3903 does not resolve the issue, we can revert this PR and investigate further.
- If this works, it could still be a short-term solution and maybe a more long term solution would be to add proper releases with tags to
withastro/automation. - No matter what we end up doing, we will probably need to do the same to the Docs repo as I think withastro/docs#13873 may have the same issue.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@astrojs/starlight@0.39.3
Patch Changes
- #3910
dddf405Thanks @andreialba! - Improves Romanian UI translations
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.8
Patch Changes
-
#16830
f2bf3cbThanks @matthewp! - Fixes 404s for dynamically imported JS chunks when using an adapter withassetQueryParams(e.g. Vercel skew protection) -
#16831
ace96baThanks @astrobot-houston! - Fixes a misleadingGetStaticPathsRequirederror when a redirect is configured from a dynamic route to a static (or less-dynamic) destination. For example,'/project/[slug]': '/'previously produced a confusing error pointing atindex.astro. Astro now detects the parameter mismatch at config validation time and throws a clearInvalidRedirectDestinationerror naming the missing parameters. -
#16702
b7d1758Thanks @matthewp! - Fixes scoped styles from.astrocomponents being dropped when rendered inside MDX content (<Content />fromrender(entry)) passed through a named slot using<Fragment slot="X">. The Fragment component now eagerly evaluates its slot contents to ensure propagating components register their styles before head content is flushed. -
#16836
3d7adfaThanks @LongYC! - Document compressHTML: "jsx" config is only available since Astro v6.2.0
@astrojs/cloudflare@13.5.5
Patch Changes
-
#16607
98297afThanks @alexanderflodin! - Fixes incorrectassets.directoryin the generatedwrangler.jsonwhen abasepath is configured -
Updated dependencies []:
- @astrojs/underscore-redirects@1.0.3
Changes
- Fixes misleading
GetStaticPathsRequirederror when redirects from dynamic routes to static destinations are configured (e.g.,'/project/[slug]': '/') - Adds parameter validation in
createRedirectRoutes()to check that destination routes have all required parameters from source routes - Extends
InvalidRedirectDestinationerror message to clearly identify missing parameters, preventing confusion about which file is causing the issue
Testing
- Added unit test for dynamic origin redirecting to static destination (
/project/[slug]→/) - Added unit test for dynamic origin with more params than destination (
/old/[id]/[page]→/posts/[id]) - Both tests verify the error is
InvalidRedirectDestinationwith clear parameter information, not the misleadingGetStaticPathsRequired
Docs
- No docs update needed, as this fixes an existing error case to be clearer rather than introducing new functionality.
Closes #16482
Changes
- Moves
plugin-chunk-importsfromrenderChunktogenerateBundleso it runs after Vite's CSS plugin cleans up pure CSS wrapper chunks. Previously, appending?dpl=...query params duringrenderChunkbroke Vite's regex-based/* empty css */replacement, leaving dangling imports to deleted chunks that 404 at runtime.
Fixes #16520
Testing
- New
css-pure-chunk-query-params.test.tswith a Vue fixture where two async components share the same CSS import, forcing Rollup to create a shared pure CSS chunk. Verifies no JS imports reference deleted files, and that query params are still appended to surviving imports.
Docs
- No docs needed — internal build pipeline fix with no user-facing API change.
Changes
- Adds
GH_TOKENto the "Remove Preview Label" step in the preview release workflow. PR #16810 replaced theactions-ecosystem/action-remove-labelsaction with aghCLI call but did not set the token —ghrequiresGH_TOKENexplicitly, unlike GitHub Actions which inject auth automatically for actions.
Testing
- No test changes. This is a CI-only fix — the step was failing with exit code 4 on every preview release since #16810 merged.
Docs
- No docs needed — internal CI fix.
Fixes #16817
When a tsconfig uses project references that include .vue or .svelte files, TypeScript's getOutputDeclarationFileName returns the input path unchanged (since changeExtension doesn't recognize these extensions). This creates self-referencing entries in the redirect maps (Hello.vue → Hello.vue), causing infinite recursion in findSourceFile.
The fix provides getParsedCommandLine on the language service host, which is TypeScript's official hook for customizing how referenced tsconfigs are parsed. Our implementation parses them normally but filters .vue/.svelte/.astro files from the file list, preventing the self-referencing redirect entries. Project references remain fully functional for all standard TS/JS files.
Changes
- Builds now properly fail when components throw errors during workerd prerendering, instead of silently producing truncated HTML with exit code 0
- Dev mode now displays Astro's error overlay when rendering errors occur, matching the behavior of regular Astro dev servers
- Creates separate non-streaming app instance for prerender requests so errors propagate synchronously as 500 responses instead of being deferred
- Adds missing
!response.okcheck in prerenderer'srender()method to detect and throw on error responses - Wraps response body streams in dev mode to catch mid-stream errors and inject error overlay scripts
Testing
- Added
prerenderer-render-errors.test.tsthat verifies builds fail with clear error messages when components have missing imports - Test fixture includes intentionally broken component to trigger
ReferenceErrorduring rendering - Verified fix works for both build and dev modes with the issue reporter's test case
Docs
- No docs update needed, this fixes broken behavior to match expected Astro error handling.
Closes #16809
This PR targets astro-bot’s branch for #16783 and applies the requested CI/test clean up + a small dev-server regression test for the actual stale inline CSS behaviour. It does not change the fix implementation.
Changes
- Adds
idToModuleMap: new Map()to the existinghmr-reload.test.tsmock so it matches the new module graph iteration added in #16783. - Replaces the unsafe
Functioncast inhmr-css-invalidation.test.tswith a local typed hot update handler helper. - Adds a minimal dev-server fixture test that requests a
getStaticPaths()route, edits global CSS, then verifies the next SSR response contains updated inline CSS. - Keeps the main CSS HMR invalidation implementation unchanged.
Testing
- Fixes the current
Test (astro)failure caused by the mockedenvironment.moduleGraphmissingidToModuleMap. - Fixes the lint/type failure from
@typescript-eslint/no-unsafe-function-type. - Keeps unit coverage for
virtual:astro:dev-css:*module graph invalidation. - Adds integration-style coverage for the actual stale SSR inline CSS behavior using Astro’s dev server,
fixture.fetch(), andfixture.editFile().
Docs
No docs changes needed. This only fixes internal dev-server HMR cache invalidation behavior and test coverage.
Changes
- Fixes CSS for conditionally rendered Svelte components being missing from production builds when the condition is
falseduring SSR - During the SSR build, tracks which component exports were actually rendered (
ssrRenderedExportsonBuildInternals) - Saves CSS assets before deletion during client build deduplication and restores them only when the
cssScopeTotarget component was not rendered in SSR — meaning the styles were genuinely tree-shaken and missing from the page. CSS for components that were rendered in SSR (and already on the page) stays deleted to avoid duplicate stylesheets. - Updates test fixture to use deterministic condition (
$state(false)with$effect()) instead of random condition that was passing by coincidence
Testing
- Updated
packages/integrations/svelte/test/fixtures/conditional-rendering/src/components/Parent.svelteto use$state(false)instead ofMath.random() > 0.5for deterministic reproduction - Existing
conditional-rendering.test.tsnow consistently verifies that conditionally rendered component CSS is included in production builds - Verified that the existing
0-css.test.ts"remove unused styles from client:load components" test continues to pass (CSS for normally renderedclient:loadcomponents is still correctly deduplicated)
Docs
- No docs update needed — this fixes existing documented behavior rather than introducing new functionality
Closes #16251
Changes
Adds styleDirective.unsafeInline opt-in flag to Astro's CSP configuration (fixes #14798).
Per the CSP spec, browsers silently ignore unsafe-inline when hashes or nonces are present in the same directive. This meant users who needed unsafe-inline for styles (e.g. third-party libraries, inline style attributes) were forced to abandon Astro's CSP feature entirely — losing script hashing in the process.
When styleDirective.unsafeInline: true is set:
- Astro emits
unsafe-inlinein style-src instead of style hashes - Script hashing is unaffected
Testing
Unit tests added in test/units/csp/rendering.test.ts covering:
unsafe-inlineis emitted and style hashes are omitted when the flag is set- Script hashes are unaffected when the flag is set
- Default behavior (hashes emitted, no
unsafe-inline) is unchanged
Docs
The unsafeInline property is documented inline in config.ts alongside the existing hashes and resources properties. The CspStyleDirective exported type is also updated.
/cc @withastro/maintainers-docs for feedback!
Changes
- Fixes Node adapter in middleware mode incorrectly JSON-stringifying
Bufferbodies fromserverless-http - Adds binary data check (
ArrayBuffer.isView()andinstanceof ArrayBuffer) before generic object branch inmakeRequestBody - Buffer, Uint8Array, and other typed arrays now pass through directly as valid
BodyInitvalues
Testing
- Added 5 new test cases in
test/units/app/node.test.tscovering Buffer, Uint8Array, ArrayBuffer, plain objects, and strings - Verifies binary data passes through unchanged while preserving existing string/object handling
- All existing tests pass with no regressions
Docs
- No docs update needed, this restores expected behavior for a specific integration pattern
Closes #16820
Changes
- Custom elements (tags with hyphens like
<my-element>) in MDX files now go through the renderer pipeline, matching.astrofile behavior - Adds hyphen check in JSX runtime so custom elements fall through to
renderComponentToString()instead ofrenderElement() - Preserves backward compatibility — custom elements with no registered renderer still render as raw HTML
Testing
- Added unit tests in
packages/astro/test/units/render/jsx-custom-elements.test.tscovering renderer pipeline integration and fallback behavior - Verified standard HTML elements continue using
renderElement()path - Confirmed no regressions in existing MDX and render test suites
Docs
- No docs update needed — this fixes existing documented behavior rather than introducing new functionality
Closes #16273
Changes
- normalizes the
namefield of wrangler.jsonc files thatastro add cloudflarecreates, ensuring that even if a project has a name incompatible with Cloudflare Worker's name constraint the command still produces a working project
Testing
- manually I created a new astro project via
npm create astrocalled"astro.app", I runnpx astro add cloudflareon it and it generated a wrangler.jsonc file with the invalid name"astro.app", then I locally build astro and retried again with my version and the name in the wrangler.jsonc is now a correct"astro-app" - The
astro add cloudflarecommand doesn't seem to be test covered
Docs
This is a self explanatory/minimal fix
This change is so that users can look at manifest.routes[].styles and add Preload link headers. Previously we didn't include this information for prerendered routes because we didn't think it was necessary.
Changes
- Populates external stylesheet URLs in the SSR manifest for prerendered routes. Previously these had
styles: [], making it impossible for workers to discover which CSS files belong to a prerendered page. Inline styles are still omitted (already in the HTML).
Testing
- No test changes — this is a data-only change to the serialized manifest.
Docs
- No docs needed.
Changes
This PR pin the exact version used by the merge-main-to-next workflow to match the same version we use in the repository. I should fix the failures.
Testing
Merge and wait once it's triggered again
Docs
N/A
Changes
This PR adds:
- zizmor to our CI to check for incorrect usage of our workflows. As for now, I tuned the tool to target high confidence and high severity
- I run the tool in our actions, and applied the suggestions
- removed an action from our preview release
- removed a possible template injection
There's still a warning
warning[secrets-inherit]: secrets unconditionally inherited by called workflow
--> .github/workflows/format.yml:12:11
|
12 | uses: withastro/automation/.github/workflows/format.yml@497c9268ad4267c842a8f6c4d830ad0182d6f6b3 # main
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this reusable workflow
...
15 | secrets: inherit
| ---------------- inherits all parent secrets
|
= note: audit confidence → High
However it's not easy fixable at the moment
Note
I plan to address even warnings, but for now I'll focus on high profile errors.
Testing
Green CI
Docs
Changes
Closes #16781
It hardens and fixes the double encoding algorithm.
Testing
Added a bunch of unit tests, and an integration test.
Docs
N/A
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.7
Patch Changes
-
#16821
9c76b12Thanks @astrobot-houston! - Fixes request body handling in the Node adapter whenreq.bodyis aBuffer,Uint8Array, orArrayBuffer. Previously, binary body data was incorrectly JSON-stringified (producing{"type":"Buffer","data":[...]}) instead of being passed through directly. This affected libraries likeserverless-httpthat setreq.bodyto aBuffer. -
#16785
de96360Thanks @astrobot-houston! - Fixesvite.build.minify,vite.build.sourcemap, andvite.build.rollupOptions.output(e.g.compact) being ignored for client-side builds. These top-level Vite build options are now properly forwarded to the client environment, with environment-specific overrides (vite.environments.client.build.*) taking priority when set. -
#16819
b5dd8f1Thanks @astrobot-houston! - Fixes custom elements in MDX files bypassing the renderer pipeline. Custom elements (tags containing hyphens like<my-element>) in.mdxfiles are now routed through registered renderers for SSR, matching the behavior of.astrofiles. If no renderer claims the element, it falls back to rendering as raw HTML. -
#16808
765896cThanks @ematipico! - Fixes dynamic routes returning 400 Bad Request when the URL contains a literal%character, such as paths built withencodeURIComponent('%?.pdf') -
#16804
90d2acaThanks @jp-knj! - Fixes a v6 regression whereastro:i18ncould not be imported from client<script>blocks.
@astrojs/cloudflare@13.5.4
Patch Changes
-
#16769
428cb1bThanks @astrobot-houston! - Forwards user-providedoptimizeDepssettings (exclude, include, esbuildOptions.loader) to SSR/prerender environments. Previously, top-levelvite.optimizeDepsin the Astro config was silently ignored for server environments because Vite 6 scopes it to client-only and the adapter'sconfigEnvironmenthook did not forward it. This caused packages with non-standard file types (e.g..datafiles) to fail during dev-mode dependency optimization with errors like "No loader is configured for '.data' files". -
Updated dependencies []:
- @astrojs/underscore-redirects@1.0.3
Adds guardrails to the triage skill so the agent bails out after 2 failed server starts instead of looping until timeout.
Prompted by this run where the agent confirmed the bug early but wasted ~10 minutes fighting a stale server process and ran out of time before writing its report.
Changes:
- SKILL.md: General "don't get stuck" rule at the top level
- reproduce.md: Server Management Rules subsection (bail after 2 failures, stop before restart, one repro run is enough, prefer build over dev/preview)
- diagnose.md: Brief server management reminder in the instrumentation step
Changes
It seems that emdash-cms/emdash#1116 was caused by #16708
This PR reverts it
Testing
Preview release
Docs
Changes
- Adds
5-legacyto thebranchesfilter on the preview release workflow so PRs targeting5-legacycan get preview releases via thepr previewlabel.
Testing
- No test changes.
Docs
- No docs needed.
Changes
- Re-enables all Svelte e2e tests that were disabled when the monorepo upgraded to Vite 8.
@sveltejs/vite-plugin-svelte@6.2.4now has experimental Vite 8 support, and all previously-skipped tests pass. - Uncomments
@astrojs/svelteintegration setup and Svelte component usage across 25 e2e fixture files (configs + pages), and removes.skipfrom 11 e2e test files. - The Cloudflare
svelte-rune-depstest still fails (500 in dev mode) so it remains skipped with an updated reason. Theasync-renderingtest is a separate Svelte upstream issue and was not touched.
Testing
- No new tests added — this PR re-enables ~20 existing test cases across
svelte-component,nested-in-svelte,nested-in-react,nested-in-vue,nested-in-preact,nested-in-solid,nested-recursive,client-only,csp-client-only,errors, andview-transitionse2e suites.
Docs
- No docs needed — test-only change.
Closes AST-87
Changes
This PR fixes this failure: https://github.com/withastro/astro/actions/runs/25864427348/job/76002567202#step:7:13
It's caused by conflicts=true, which leaves possible conflict markers in package.json. Then the pnpm action comes, and tries to use the version from the manifest, which is broken.
This PR pins the pnpm version to use in the GH action
Testing
Green CI. We will see if it works after the next release.
Docs
Changes
- Before creating a PR, the fix-verification agent now checks if one already exists for the
flue/fix-{issueNumber}branch. If a PR exists, it posts a comment linking to it instead of failing with a 422 from the GitHub API. - Adds a
findPullRequesthelper to.flue/lib/github.tsthat queries open PRs by head branch.
Testing
- No automated tests; this is CI workflow glue code. Verified the logic matches the GitHub Pulls API behavior.
Docs
- No docs needed — internal CI change only.
Changes
Update esbuild and tsx to the latest versions. This reduces the vite instances installed under node_modules/ and resolves the test failure in Examples astro check / astro check CI job.
Before:
We have 3 vite instances installed. astro and @example/with-vitest use different vite.
$ git status
On branch chore/merge-main-into-next
$ pnpm -r why vite | egrep ' astro@|vite@8|@example|Found'
vite@8.0.13 peer#4033 (3 variations)
│ └── astro@7.0.0-alpha.1 (devDependencies)
├── astro@7.0.0-alpha.1 (dependencies)
│ └── astro@7.0.0-alpha.1 (dependencies)
vite@8.0.13 peer#5d0f (3 variations)
vite@8.0.13 peer#96d1 (3 variations)
│ ├── @example/with-tailwindcss@0.0.1 (dependencies)
│ ├── @example/container-with-vitest@0.0.1 (dependencies)
│ └── @example/with-vitest@0.0.1 (dependencies)
Found 1 version, 3 instances of vite
After:
We have 2 vite instances installed. astro and @example/with-vitest share the same vite.
$ git status
On branch ocavue/chore/merge-main-into-next
$ pnpm -r why vite | egrep ' astro@|vite@8|@example|Found'
vite@8.0.13 peer#1781 (2 variations)
│ ├── @example/with-tailwindcss@0.0.1 (dependencies)
│ ├── @example/container-with-vitest@0.0.1 (dependencies)
│ ├── astro@7.0.0-alpha.1 (devDependencies)
│ └── @example/with-vitest@0.0.1 (dependencies)
├── astro@7.0.0-alpha.1 (dependencies)
│ ├── astro@7.0.0-alpha.1 (dependencies)
vite@8.0.13 peer#ade4 (2 variations)
Found 1 version, 2 instances of vite
Testing
Docs
Changes
- Fixes
vite.build.minify,vite.build.sourcemap, and rollupoutputoptions being ignored when configured through the top-levelvite.buildin astro.config.ts - Adds proper fallback chains so client environment config inherits from top-level Vite config, with environment-specific overrides taking highest priority
- Resolves regression from Vite Environment API migration (PR #14306) where these options were hardcoded instead of respecting user configuration
Testing
- Added 8 new unit tests in
packages/astro/test/units/build/vite-build-config.test.tscovering minify, sourcemap, and rollup output inheritance scenarios - Tests verify environment-specific config takes priority over top-level config, which takes priority over defaults
- Existing sourcemap integration tests continue to pass
Docs
- No docs update needed, as this restores documented behavior that was broken by the Environment API migration
Closes #16268
Changes
This PR improves the printed time stamp of the build
Testing
Added unit tests
Docs
N/A
Changes
- Fixes stale inline
<style>tags in server-rendered HTML after CSS file edits during dev. CSS changes now properly update both client-side HMR and server-side inline styles, eliminating the flash of old CSS (FOUC) on fresh page loads. - Adds invalidation of
virtual:astro:dev-css:*modules in both the SSR module graph and module runner evaluation cache when style files change during HMR. This ensures the next SSR render picks up fresh CSS while preserving the performance benefit of skipping full page reloads for CSS-only changes.
Testing
- Added
packages/astro/test/units/dev/hmr-css-invalidation.test.tswith 5 test cases covering CSS/SCSS invalidation, module graph behavior, non-style change passthrough, and edge cases. - Tests verify that dev-css virtual modules are properly invalidated when style files change and that non-style changes are unaffected.
Docs
- No docs update needed, this fixes dev-only behavior to match user expectations.
Closes #16780
Description
Fixes #2697.
When the mobile menu is open on narrow viewports, Tab navigation currently escapes the menu after the last focusable element (the theme switcher) and lands on the underlying page content.
This PR extends the existing StarlightMenuButton custom element with a keydown handler that wraps Tab / Shift+Tab focus between the first and last visible focusable elements inside the surrounding <nav> while the menu is expanded (body[data-mobile-menu-expanded]).
The trap is scoped to the same <nav> already used for the Escape listener and is gated on the existing data attribute, so there is no behavioural change while the menu is closed.
Test plan
- Added Playwright e2e coverage in
packages/starlight/__e2e__/mobile-menu.test.ts:- Tab from the last focusable wraps to the first.
- Shift+Tab from the first wraps to the last.
- Trap does not engage while the menu is closed.
- Escape still closes the menu and restores focus to the toggle (regression check).
- All existing unit tests (519) and e2e tests pass locally.
Changeset
Patch bump for @astrojs/starlight included.
Changes
- Adds
npmandnpxas allowed commands in the triage agent's flue sandbox. The agent usesnpx stackblitz-cloneto clone issue reproductions, butnpxwasn't a permitted command, causing the agent to waste time hunting for tools and ultimately fail (run).
Testing
- No test changes. This is a configuration-only change to the flue agent definition.
Docs
- No docs needed — internal CI tooling change.
Changes
- The HMR reload plugin now invalidates all recursively-invalidated modules in the SSR module runner's
evaluatedModulescache, not just the directly changed file.moduleGraph.invalidateModule()already walks importers and populates aninvalidatedModulesset, but the runner cache only cleared the leaf module. Barrel files (e.g.index.tsre-exporting components) stayed cached, soawait import("../components")returned stale exports even after a full page reload.
Fixes #16000
Testing
- New unit test in
hmr-reload.test.ts: "invalidates importers in the module graph for dynamic import chains" -- sets up a component with a barrel file importer and verifies the invalidation propagates through the mock module graph. - Updated mock
invalidateModuleto simulate Vite's recursive importer walk by adding importers to theseenset.
Docs
- No docs changes needed. This is a bug fix restoring behavior from Astro 5.
Changes
- The route cache (
callGetStaticPaths) now stores the module reference alongside cached static paths and compares identity on lookup. After HMR,modis a new object from a freshimport(), so the cache treats the old entry as stale and re-callsgetStaticPaths()with updated component references. This fixes the case where components passed as props viagetStaticPaths()served stale content in dev — even on manual refresh.
Fixes #16522
Testing
- New unit test in
getstaticpaths-cache.test.ts: "re-calls getStaticPaths when module identity changes (HMR)" — callscallGetStaticPathswith one module, confirms cache hit on same module, then passes a different module object and asserts the cache is bypassed and fresh props are returned.
Docs
- No docs changes needed. This is a bug fix restoring existing behavior from Astro 5.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.6
Patch Changes
-
#16774
8f77583Thanks @astrobot-houston! - Fixes markdown images with empty alt text () in content collections dropping thealtattribute entirely. Thealt=""attribute is now correctly preserved in the rendered HTML output, which is important for accessibility (indicating decorative images). -
#16776
3d10b5eThanks @matthewp! - Fixes HMR serving stale content when components are passed as props viagetStaticPaths() -
#16784
7453860Thanks @ematipico! - Improved the printing of the build time if it goes over the 60 seconds. -
#16665
3dbbceeThanks @Princesseuh! - Fixes remote SVG sources erroring withdangerouslyProcessSVGafter the v6.3 SVG-processing gate. The default Sharp service now resolves the output format from the source up-front when it can (URL extension,data:MIME, ESM metadata), and from the actual buffer at request time when it can't, so SVG sources pass through untouched without needing to setimage.dangerouslyProcessSVG: trueor an explicitformat="svg".The error message has also been updated to point at
format="svg"as the simpler workaround when an SVG source is encountered withoutdangerouslyProcessSVGenabled. -
#16777
1754b91Thanks @matthewp! - Fixes HMR serving stale content for dynamically imported components through barrel files -
#16730
068d924Thanks @harshagarwalnyu! - Fixes an issue where thefile()content loader did not generate a valid JSON Schema for collections whose JSON or YAML data is a top-level array instead of an object.
@astrojs/cloudflare@13.5.3
Patch Changes
-
#16801
d619277Thanks @ematipico! - Reverts a change to the esbuild dep-scan plugin that causedastro checkandastro buildto fail by making esbuild incorrectly bundlevirtual:modules (e.g. from expressive-code) -
Updated dependencies []:
- @astrojs/underscore-redirects@1.0.3
Changes
- Fixes markdown images with empty alt text (
) in content collections to renderalt=""instead of dropping the attribute entirely - Replaces JavaScript truthiness check with explicit
value != nullfiltering to preserve empty string attributes - Only affects content collections markdown path — pages-based markdown already handled this correctly
Testing
- Added test
'content collection images with empty alt preserve the alt attribute'inpackages/astro/test/core-image.test.tsto verify empty alt attributes are preserved - Verified all existing image, markdown, and content collection tests still pass
Docs
- No docs update needed, this fixes a regression to match existing expected behavior
Closes #16621
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.5
Patch Changes
-
#16771
07c8805Thanks @ematipico! - Fixespositionprop on<Image>and<Picture>components breaking Content Security Policy (CSP). -
#16593
50924ceThanks @yanthomasdev! - Improves error messages with more consistent and correct writing. -
#16757
5d661cdThanks @astrobot-houston! - Fixes dev server serving stale content when SSR-only modules change (e.g..astrofiles outside the project root in a monorepo, or dynamically imported components).Previously, the
astro:hmr-reloadplugin returned an empty array after detecting SSR-only module changes, which prevented Vite'supdateModulesfrom propagating the invalidation to the SSR module runner. The runner's evaluated module cache stayed stale, so subsequent requests continued returning old content.Now the plugin returns the SSR-only modules so Vite can process them through
updateModules, which properly invalidates the module runner's cache and ensures fresh content on the next request.
Summary
- fix a typo in the Romanian previous page label
- translate remaining English strings in
packages/starlight/translations/ro.json - add a changeset for the user-facing translation fix
Changes
- Uses the server to do
injectScriptentrypoint discover to prevent a race condition with the dep optimizer.
Closes #16766. Related: #16387.
Testing
- User confirmed the fix
- Reproduced locally against npm-installed astro using the reporter's reproduction. Before fix:
_metadata.jsoncontained 1 entry. After fix: 10 entries including all React deps. - No new test — the race condition requires packages installed from npm (not workspace links) and timing-dependent optimizer behavior.
Docs
- No docs changes needed.
Changes
- Forwards user-provided
optimizeDeps.exclude,optimizeDeps.include, andoptimizeDeps.esbuildOptions.loaderfrom the Astro config to SSR/prerender environments in the Cloudflare adapter - Fixes packages with non-standard file extensions (like
.data) failing during dev-mode dependency optimization - Resolves issue where top-level
vite.optimizeDepsconfiguration was silently ignored for server environments due to Vite 6's client-scoped design
Testing
- Added
packages/integrations/cloudflare/test/user-optimize-deps.test.tswith a test that imports a package with.datafiles to verify the exclude configuration works - Test uses a
fake-data-pkgfixture that would fail without the fix but succeeds whenoptimizeDeps.excludeis properly forwarded
Docs
- No docs update needed, this fix restores expected behavior where user Vite config applies to all environments.
Closes #16491
Root cause
The suggestion of Astro bot is included in #16124 . This fixing is clear the cache of disc. But it didn't solve the issue.
It seems the root cause is remaining cache of vite runner eveluatedModules cache.
Changes
Close #13149
Implemented invalidation for file paths and cache clearing in /packages/astro/src/content/vite-plugin-content-virtual-mod.ts.
Testing
Before Implementation
After Implementation

Description
This pull request, adds a simple new page template called blank that allows the end-user to fully customize what's rendered in the ContentPanel without having to worry about CSS hacks to hide things like the PageTitle which are present in the splash template. For more information see #3906
Additionally, it refactors a small section in the Page.astro that handles conditional rendering for pages with a hero image. Hopefully it improves visibility and maintainability in comparison to the previous version.
This pull request does not impact the default Starlight look.
Summary
Fixes #14013.
On case-insensitive filesystems (Windows, macOS), starting astro dev from a working directory whose case differs from the actual filesystem case (e.g. d:\dev\app vs D:\dev\app) causes styles to be stripped from Astro components.
Root Cause
normalizeFilename in vite-plugin-utils/index.ts uses commonAncestorPath to decide whether an absolute path lives inside the project root. commonAncestorPath performs a case-sensitive string comparison. When the CWD (root) and the Vite-resolved module path (filename) disagree on case, the check fails and the path is incorrectly rewritten — producing a path that misses the compile-metadata cache and causes the virtual style module to be generated with no styles.
Fix
A small helper isPathInRoot is introduced. It first attempts an exact (fast) match via commonAncestorPath. If that fails, it retries with both paths lower-cased, matching the semantics of case-insensitive filesystems. The change is limited to this one fallback; it does not alter any other behaviour and has no performance impact on correct (same-case) paths.
function isPathInRoot(filename: string, rootPath: string) {
return (
commonAncestorPath(filename, rootPath) ||
commonAncestorPath(filename.toLowerCase(), rootPath.toLowerCase())
);
}Tests
Added a unit test in test/units/vite-plugin-utils/normalize-filename.test.ts that directly reproduces the issue: normalizeFilename is called with a filename whose case differs from the root URL, and the function should return the original filename unchanged (i.e. not rewrite it onto the root).
This fix was contributed with AI assistance by SwarmFix (sulphur-swarm).
Summary
Fix syntax highlighting for <style> and <script> tags when the lang or type attribute is on a different line from the opening tag.
Root Cause
The TextMate grammar regex used [^>]*? to scan for lang/type attributes within style/script tags. In Oniguruma (VS Code's regex engine), [^>] does not reliably match across newlines in all configurations, causing the language detection to fail when attributes span multiple lines. The content then falls back to default CSS highlighting.
Changes
astro.tmLanguage.src.yaml: Changed[^>]*?→[\s\S]*?in all three language-detection patterns (JSON-LD, module/JS, and generic language)astro.tmLanguage.json: Same change in compiled grammar- Test fixture: Added
sass-multiline.astrocovering SCSS, LESS, SASS, and CSS with both single-line and multi-line attribute layouts
Testing
- Manually verified the regex matches using Node.js and Python
- Added test fixture covering all style language types with both single-line and multi-line
<style>tag layouts - Existing grammar snapshot tests should continue to pass (
pnpm test:grammar)
Related
Fixes #14657
Closes draft PR #15066 (supersedes with proper YAML source + JSON update)
Changes
There is a json schema file included in the wrangler npm package used to specify the structure of the cloudflare wrangler configurations.
This changes the astro add cloudflare command to include the schema reference when generating the wrangler.jsonc file.
- I included a changeset
Testing
Doesn't look like there are any existing tests for the astro add command.
Tested it locally instead.
Docs
Sufficient documentation exists for the add command and the CLI walks the user through it.
Changes
Assigns VS Code's registered dotenv language to .dev.vars env file used in the Cloudflare adapter.
Astro recommends using this file to store development-only environment variables when using the Cloudflare adapter, but VS Code displays it as plaintext rather than a dotenv file. This is the fix.
- I included a changeset with this change
Before / After
Testing
Tested using VS Code extension test runner.
Docs
Documentation exists for this file. Syntax highlighting will reassure new developers using Astro & Cloudflare workers that they are following documentation correctly.
Summary
- Adds a warning when a page partial's rendered component tree includes Astro component scripts or scoped styles that get stripped from the partial HTML output.
- Warning fires once per route component per
PagesHandlerlifetime (deduped), so dev users see it on first request and prerender users see it once during build. - Detects via
result._metadata.propagators.size > 0afterrenderPage, which is exactly the runtime set populated by components the compiler marked as propagating (i.e. components with scoped<style>or hoisted<script>blocks). - Adds 4 regression unit tests using the existing
SpyLogger+createBasicPipelinehelpers.
Fixes #11885
Tests run
astro-scripts test "test/units/**/*.test.ts" --strip-types --teardown ./test/units/teardown.ts— 2622 passing, 0 failingastro-scripts test "test/units/render/**/*.test.ts" --strip-types --teardown ./test/units/teardown.ts— 273 passingastro-scripts test "test/partials*.test.ts" --strip-types --parallel— 3 passing (existing fixtures)- New
test/units/render/partials-warnings.test.ts— 4 passing, covering:- warns when the tree includes propagating components
- does not warn when the partial has no stripped assets
- does not warn when the page is not a partial
- warns exactly once across repeated renders
biome checkandprettier -cclean on touched files.
Docs
No docs change strictly needed — the existing partials docs already describe the stripping behavior, and the warning links back to that section. Happy to add a note there if maintainers prefer.
Changes
- Handles CSS
@propertyrules inside Astro<style>blocks before delegating to VS Code's generic CSS grammar. - Prevents the tokenizer from staying in CSS context after
</style>, so following<style>and<script>blocks keep correct highlighting.
Testing
- Adds a grammar fixture covering
@propertydescriptors followed by additional style and script blocks. - Adds a snapshot assertion that the closing style tags and following script block keep Astro scopes.
Docs
- No docs update needed because this fixes editor syntax highlighting behavior only.
Changes
- Fixes HMR for SSR-only modules in monorepo setups where shared components live outside the Astro project root
- Changes
astro:hmr-reloadplugin to return SSR-only modules fromhotUpdateinstead of an empty array, allowing Vite'supdateModules()to properly invalidate the SSR module runner cache - Resolves regression from Astro 4/5 where external file changes required dev server restart to be visible
Testing
- Added comprehensive unit tests in
packages/astro/test/units/vite-plugin-hmr-reload/hmr-reload.test.tscovering the key scenarios:- SSR-only modules are returned (not
[]) — the critical test that fails without the fix - Non-SSR environments are skipped correctly
- Style-only modules continue to return
[]as expected - Modules that exist in both client and SSR environments are excluded
- SSR-only modules are returned (not
Docs
- No docs update needed, this fixes existing functionality to work as documented
Closes #16754
Changes
- Fixes
getSSREnvironment()in module loader to return the passedssrEnvironmentparameter instead of hardcodedviteServer.environments['ssr'] - Resolves missing styles from Markdoc/MDX custom components in
<head>when using Cloudflare adapter withprerenderEnvironment: 'node'and wrapper components - The bug occurred because component metadata crawling used the wrong environment's module graph (empty workerd vs populated prerender)
Testing
- Added
packages/astro/test/units/dev/module-loader.test.tswith 2 unit tests confirminggetSSREnvironment()returns the correct environment - Verified the fix resolves the issue in the reporter's reproduction case
Docs
- No docs update needed, this fixes a bug with no API changes
Closes #16013
Changes
Reverts the two-job build/publish split from #16710 back to a single job. The split caused artifact upload timeouts and workspace config mismatches after the pnpm v11 upgrade. Skipping the pnpm cache instead eliminates the cache poisoning vector without needing to shuttle artifacts between jobs.
Testing
CI-only workflow change, validated by triggering a preview release.
Docs
No docs needed — internal CI change.
Changes
The hardened preview-release workflow (#16710) combined with the pnpm v11 upgrade broke preview releases. This generates a synthetic minimal workspace for the publish artifact and uses pnpm dlx to run pkg-pr-new without triggering workspace resolution. Also excludes test/src/e2e directories from the artifact to prevent upload timeouts.
Testing
- Added smoke tests for the staging script (
scripts/stage-preview-publish.test.ts).
Docs
No docs needed — internal CI change.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated.
next is currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, run changeset pre exit on next.
Releases
astro@7.0.0-alpha.2
Major Changes
-
#16725
10229f7Thanks @ArmandPhilippot! - Removes deprecated APIs exported fromastro:transitions.In Astro 6.x, some helpers available in
astro:transitionsandastro:transitions/clientwere deprecated.In Astro 7.0, the following APIs can no longer be used in your project:
TRANSITION_BEFORE_PREPARATIONTRANSITION_AFTER_PREPARATIONTRANSITION_BEFORE_SWAPTRANSITION_AFTER_SWAPTRANSITION_PAGE_LOADisTransitionBeforePreparationEvent()isTransitionBeforeSwapEvent()createAnimationScope()
What should I do?
Remove any occurrence of
createAnimationScope():-import { createAnimationScope } from 'astro:transitions';Replace any occurrence of the other APIs using the lifecycle event names directly:
-import { - TRANSITION_AFTER_SWAP, - isTransitionBeforePreparationEvent, -} from 'astro:transitions/client'; -console.log(isTransitionBeforePreparationEvent(event)); +console.log(event.type === 'astro:before-preparation'); -console.log(TRANSITION_AFTER_SWAP); +console.log('astro:after-swap');
Learn more about all utilities available in the View Transitions Router API Reference.
Patch Changes
-
#16774
8f77583Thanks @astrobot-houston! - Fixes markdown images with empty alt text () in content collections dropping thealtattribute entirely. Thealt=""attribute is now correctly preserved in the rendered HTML output, which is important for accessibility (indicating decorative images). -
#16730
068d924Thanks @harshagarwalnyu! - Fixes an issue where thefile()content loader did not generate a valid JSON Schema for collections whose JSON or YAML data is a top-level array instead of an object.
@astrojs/svelte@9.0.0-alpha.2
Minor Changes
- #16549
9d9d516Thanks @ocavue! - Updates@sveltejs/vite-plugin-svelteto v7. No user action is necessary.
@astrojs/cloudflare@14.0.0-alpha.1
Patch Changes
Changes
The hardening split (#16710) combined with the pnpm v11 upgrade broke preview releases — the publish job's minimal artifact was missing patches/ and had unresolvable workspace:* deps. This generates synthetic workspace configs in the staging step and uses pnpm dlx instead of pnpm exec to skip workspace resolution entirely.
Testing
Note, this is me, the human. Preview releases are currently broken after we split up the job for security reasons (so installing code not have access to the id token).
I ran the script in this change and verified it does pack all of the things it's supposed to. I can't run a preview release to verify that works.
Also added some unit tests to the script itself.
Docs
No docs needed — internal CI change.
Changes
- Fixes the dev prerender/SSR handoff for Cloudflare
prerenderEnvironment: 'node'by delaying request body reads untilAstroServerApp.handleRequest()finishes route resolution inprerenderOnlymode. If the resolved route falls through to SSR, the POST body is still intact when the SSR handler receives it. - Adds a comment next to the prerender middleware gate explaining why the
matchAllRoutes()check is intentional: the final dev routing decision still needs the deeper route resolution inhandleRequest(), so future changes do not replace that gate withmatchRoute()again.
Testing
- Added a Cloudflare regression test in
packages/integrations/cloudflare/test/prerender-node-env.test.tsthat exercisesPOST /_actions/hellowith a prerendered catch-all route. - The same fixture continues to cover the surrounding
prerenderEnvironment: 'node'behavior for prerendered pages, SSR pages, and server islands.
Docs
- No docs update needed, because this fixes broken dev behavior rather than changing the supported API.
Closes #16746
Changes
Closes AST-44
This PR stabilise the logger feature
Testing
Green CI.
Docs
Changes
- Adds a sparse checkout step to the
publishjob in the preview release workflow sopnpm/action-setupcan read thepackageManagerfield frompackage.json. Thepublishjob previously had no checkout, so pnpm setup failed with "No pnpm version is specified."
Testing
- No test changes. This is a CI workflow fix — validation is the next preview release run succeeding.
Docs
- No docs needed — internal CI change only.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.4
Patch Changes
-
#16723
0f10bfeThanks @matthewp! - AddsfetchFileoption toexperimental.advancedRoutingto customize or disable the entrypoint fileexport default defineConfig({ experimental: { advancedRouting: { fetchFile: 'fetch.ts', }, }, });
-
#16723
0f10bfeThanks @matthewp! - Fixes Honocache()middleware to follow the standard wrapper pattern -
#16723
0f10bfeThanks @matthewp! - AddsApp.Providersinterface for typing custom context providers onAstroandctxdeclare namespace App { interface Providers { oauth: import('./lib/oauth').OAuthSession; } }
-
#16723
0f10bfeThanks @matthewp! - AddsFetchState.responseproperty, set automatically afterpages()ormiddleware()completesconst response = await middleware(state, (s) => pages(s)); console.log(state.response === response); // true
-
#16723
0f10bfeThanks @matthewp! - AddsFetchabletype export for typing the advanced routing entrypointimport type { Fetchable } from 'astro'; export default { async fetch(request) { return new Response('ok'); }, } satisfies Fetchable;
-
#16572
4a5a077Thanks @DORI2001! - Suppresses[WARN] Vite warning: unused imports from "@astrojs/internal-helpers/remote"during prerender builds. The package is now bundled alongsideastroin the prerender environment, matching how it is handled in the SSR environment. -
#16756
b6ee23dThanks @astrobot-houston! - Fixes styles from Markdoc/MDX custom components not being extracted to<head>in the dev server when using the Cloudflare adapter withprerenderEnvironment: 'node'and rendering content through a wrapper component. -
#16747
904d19aThanks @astrobot-houston! - Fixes Astro action requests failing inastro devwhen using the Cloudflare adapter withprerenderEnvironment: 'node'alongside a prerendered catch-all route such as[...page].astro.Actions and other SSR POST endpoints now continue to work in dev instead of returning an HTTP 500 error.
-
#16701
3495ce4Thanks @demaisj! - FixMapandSetinstances saved in a content collection being broken when retrieving entries. -
#16614
fca1c32Thanks @Eptagone! - Fixesentry.datatype inference when a live collection is configured without a schema. -
#16661
03b8f7fThanks @ocavue! - Updatestypescriptto v6. No changes are needed from users. -
#16681
c22770aThanks @dotnetCarpenter! - Fixes an issue where SVG images withwidth="0"orheight="0"incorrectly threw aNoImageMetadataerror instead of being treated as valid dimensions.
@astrojs/db@0.21.2
Patch Changes
@astrojs/cloudflare@13.5.2
Patch Changes
-
#16708
bb709ffThanks @fkatsuhiro! - Fixed a bug where a cascade of reloads would cause the page to crash during the first visit when building or developing with Cloudflare SSR in Astro v6 due to dependency loading issues. -
Updated dependencies []:
- @astrojs/underscore-redirects@1.0.3
@astrojs/netlify@7.0.10
Patch Changes
-
#16661
03b8f7fThanks @ocavue! - Updatestypescriptto v6. No changes are needed from users. -
Updated dependencies []:
- @astrojs/underscore-redirects@1.0.3
@astrojs/language-server@2.16.9
Patch Changes
-
#16661
03b8f7fThanks @ocavue! - Updatestypescriptto v6. No changes are needed from users. -
Updated dependencies [
03b8f7f]:- @astrojs/yaml2ts@0.2.4
@astrojs/ts-plugin@1.10.9
Patch Changes
-
#16661
03b8f7fThanks @ocavue! - Updatestypescriptto v6. No changes are needed from users. -
Updated dependencies [
03b8f7f]:- @astrojs/yaml2ts@0.2.4
astro-vscode@2.16.16
Patch Changes
- #16719
2b1df12Thanks @alexisintech! - fix MDX syntax highlighting for indented astro codeblocks
@astrojs/yaml2ts@0.2.4
Patch Changes
Changes
- Extracts the Vite
InlineConfigassembly frombuildEnvironments()into a newcreateViteBuildConfig()function invite-build-config.ts. The function takes settings, viteConfig, routes, plugins, builder, and isRollupInput, and returns the config object without executing any build. - The
buildAppcallback (which depends oninternals) is passed in as a parameter, keeping the function free of build-time state.
Testing
- New
test/units/build/vite-build-config.test.tswith 12 unit tests covering: user rollup output overrides (assetFileNames,chunkFileNames,entryFileNames) preserved in top-level, client, and prerender environments; Astro defaults applied when no override;build.assetsprefix flowing into rollup templates;base,envPrefix, andcssMinifybehavior. - Removed 5 integration tests + 3 fixtures replaced by unit tests:
custom-assets-name.test.ts+ fixture (userassetFileNamesfunction override)entry-file-names.test.ts+ fixture (cliententryFileNamesoverride)astro-assets-dir.test.ts+ fixture (build.assetsprefix)config-vite.test.tsfirst describe block (prerender rollup output override)astro-css-bundling.test.tscustom assetFileNames section (prerenderassetFileNamesoverride)
Docs
- No docs needed — internal refactor with no user-facing behavior change.
Changes
- Replaces the
astro-globalintegration test suite with unit tests usingcreateTestApp. CoversAstro.site,Astro.url,Astro.routePattern,Astro.isPrerendered, and middleware locals propagation. - Removes the integration test file, fixture directory, and all associated fixture files (21 files including a 7MB penguin image).
Testing
- New unit test file at
test/units/render/astro-global.test.tswith 10 tests covering the same Astro globals behavior. Runs in ~330ms vs seconds for the integration tests. - Dropped 4 tests that provided no meaningful coverage: 3 tested
import.meta.glob()(Vite behavior, not Astro globals) and 1 was accidentally asserting a 404 response.
Docs
- No docs needed — test-only change.
Changes
Replaces the flaky integration test added in d2e25c6, which fetched a remote image from kaleidoscopic-biscotti-6fe98c.netlify.app.
The replacement is a set of offline unit tests for propsToFilename that cover the same filename-sanitization logic without any network calls.
Testing
See changed tests
Docs
N/A
Changes
- Fixes a reflected XSS where slot names on hydrated (
client:*) components were interpolated raw intodata-astro-templateandastro-slot nameHTML attributes during SSR. An attacker could break out of the attribute context and inject arbitrary HTML when a slot name is derived from user input. AppliesescapeHTML()to the slot name key at both interpolation points incomponent.ts.
Testing
- Added
slot-name-escape.astrofixture page that uses a slot name containing"><img src=x onerror=alert(1)>on aclient:loadcomponent - Added test case in
astro-children.test.tsverifying no injected<img>element exists and the attribute value contains properly escaped entities
Docs
- No docs update needed — this is an internal security fix with no user-facing API change.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.3
Patch Changes
- #16737
bd84f33Thanks @matthewp! - Fixes a reflected XSS vulnerability where slot names on hydrated components were not HTML-escaped in SSR output
@astrojs/mdx@5.0.6
Patch Changes
- #16579
49e10e3Thanks @igor-koop! - Fixes an issue where thesmartypantsoption was ignored.
Changes
This PR addresses a flaky test shown in this run: https://github.com/withastro/astro/actions/runs/25853010563/job/75964157111?pr=16734
Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
Error: There should be 3 page loads (for page one & three), and an additional loads for the back navigation
expect(received).toEqual(expected) // deep equality
Expected: 3
Received: 2
206 | loads.length,
207 | 'There should be 3 page loads (for page one & three), and an additional loads for the back navigation',
> 208 | ).toEqual(3);
| ^
209 | });
210 |
211 | test('Declarative Shadow DOM elements are attached after transitions', async ({
at D:\a\astro\astro\packages\astro\e2e\view-transitions.test.ts:208:5
Error Context: test-results\e2e-view-transitions-View--20f1e--ClientRouter-w-back-button-Firefox-Stable-retry2\error-context.md
Particularly, I used expect.poll and expect.toPass to refactor some tests with a magic timeout.
// Bad pattern
doSomething()
await page.waitForTimeout(500); // <-- A magic timeout number whether is too big (too slow) on a fast machine or too small (too fast) on a slow machine
expect(getResult()).toBe(expectedResult)
// Good pattern
doSomething()
await page.poll(getResult).toBe(expectedResult)
// Good pattern
doSomething()
await expect(() => { expect(getResult()).toBe(expectedResult) }).toPass()Testing
CI should pass
Docs
N/A
Changes
Previous, if generateImagesForPath() fails, an uncaught error will be thrown. This is an issue because it doesn't break the build() function itself, causing flaky tests.
For example, in this job run, the await fixture.build() returns a resolved promise, but the build is not actually successful.
astro:test: ✖ astro:image (37126.090234ms)
astro:test: ℹ Error: Test hook "before" at test/core-image.test.ts:992:3 generated asynchronous activity after the test ended. This activity created the error "TypeError: fetch failed" and would have caused the test to fail, but instead triggered an unhandledRejection event.
Later, when we want to check out the built result, we hit errors:
astro:test: ✖ failing tests:
astro:test:
astro:test: test at test/core-image.test.ts:1020:3
astro:test: ✖ writes out allowed remote images (1.309057ms)
astro:test: Error: ENOENT: no such file or directory, open '/home/runner/work/astro/astro/packages/astro/test/fixtures/core-image-ssg/dist/core-image-build-ssg/_astro/sponsors_1Wl70z.webp'
astro:test: at async open (node:internal/fs/promises:640:25)
astro:test: at async Object.readFile (node:internal/fs/promises:1283:14)
astro:test: at async TestContext.<anonymous> (file:///home/runner/work/astro/astro/packages/astro/test/core-image.test.ts:1025:17)
astro:test: at async Test.run (node:internal/test_runner/test:1125:7)
astro:test: at async Suite.processPendingSubtests (node:internal/test_runner/test:787:7) {
astro:test: errno: -2,
astro:test: code: 'ENOENT',
astro:test: syscall: 'open',
astro:test: path: '/home/runner/work/astro/astro/packages/astro/test/fixtures/core-image-ssg/dist/core-image-build-ssg/_astro/sponsors_1Wl70z.webp'
astro:test: }
astro:test:
Testing
CI should pass
Docs
This change doesn't affect users, so no changeset is needed. The error message is silently improved.
If I have a 404 image in the repo, here is what I get before and after when running astro build:
Before:
$ ./node_modules/.bin/astro build
21:21:59 [content] Syncing content
21:21:59 [content] Synced content
21:21:59 [types] Generated 253ms
21:21:59 [build] output: "static"
21:21:59 [build] mode: "static"
21:21:59 [build] directory: /Users/ocavue/code/github/astro/packages/astro/test/fixtures/core-image-ssg/dist/
21:21:59 [build] Collecting build info...
21:21:59 [build] ✓ Completed in 259ms.
21:21:59 [build] Building static entrypoints...
21:21:59 [vite] ✓ built in 350ms
21:21:59 [vite] ✓ built in 6ms
21:21:59 [build] Rearranging server assets...
generating static routes
21:21:59 ├─ /remote/index.html (+8ms)
21:21:59 ├─ /srcset/index.html (+1ms)
21:21:59 ✓ Completed in 16ms.
generating optimized images
21:21:59 ▶ /_astro/image 1.ZqYaJ2Hs_Zkuiyp.webp (reused cache entry) (+9ms) (1/3)
21:21:59 ▶ /_astro/image 1.ZqYaJ2Hs_2gIxxN.webp (reused cache entry) (+0ms) (2/3)
21:22:00 ✓ Completed in 1.21s. <-- ⚠️⚠️⚠️ Notice this "Completed in 1.21s" message
file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/remote.js:10
throw new Error(
^
Error: Failed to load remote image https://astro.build/this_img_does_not_exist.png. The request did not return a 200 OK response. (received 404))
at loadRemoteImage (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/remote.js:10:11)
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
at async loadImage (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/generate.js:237:12)
at async generateImageInternal (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/generate.js:154:23)
at async generateImage (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/generate.js:69:28)
at async generateImagesForPath (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/generate.js:53:5)
at async file:///Users/ocavue/code/github/astro/node_modules/.pnpm/p-queue@9.1.0/node_modules/p-queue/dist/index.js:394:36
Node.js v24.11.1
$ echo $?
1 <-- ⚠️⚠️⚠️ `astro build` exits with code 1After:
$ ./node_modules/.bin/astro build
21:27:17 [content] Syncing content
21:27:17 [content] Synced content
21:27:17 [types] Generated 258ms
21:27:17 [build] output: "static"
21:27:17 [build] mode: "static"
21:27:17 [build] directory: /Users/ocavue/code/github/astro/packages/astro/test/fixtures/core-image-ssg/dist/
21:27:17 [build] Collecting build info...
21:27:17 [build] ✓ Completed in 263ms.
21:27:17 [build] Building static entrypoints...
21:27:17 [vite] ✓ built in 377ms
21:27:17 [vite] ✓ built in 5ms
21:27:17 [build] Rearranging server assets...
generating static routes
21:27:17 ├─ /remote/index.html (+8ms)
21:27:17 ├─ /srcset/index.html (+1ms)
21:27:17 ✓ Completed in 16ms.
generating optimized images
21:27:17 ▶ /_astro/image 1.ZqYaJ2Hs_Zkuiyp.webp (reused cache entry) (+10ms) (1/3)
21:27:17 ▶ /_astro/image 1.ZqYaJ2Hs_2gIxxN.webp (reused cache entry) (+0ms) (2/3)
21:27:18 [WARN] [build] Unable to generate optimized image for https://astro.build/this_image_does_not_exist.png: Error: Failed to load remote image https://astro.build/this_image_does_not_exist.png. The request did not return a 200 OK response. (received 404))
Error generating image for https://astro.build/this_image_does_not_exist.png: Error: Failed to load remote image https://astro.build/this_image_does_not_exist.png. The request did not return a 200 OK response. (received 404))
Stack trace:
at file:///Users/ocavue/code/github/astro/packages/astro/dist/core/build/generate.js:195:21
Caused by:
Failed to load remote image https://astro.build/this_image_does_not_exist.png. The request did not return a 200 OK response. (received 404))
at loadRemoteImage (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/remote.js:10:11)
at async loadImage (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/generate.js:237:12)
at async generateImage (file:///Users/ocavue/code/github/astro/packages/astro/dist/assets/build/generate.js:69:28)
at async file:///Users/ocavue/code/github/astro/node_modules/.pnpm/p-queue@9.1.0/node_modules/p-queue/dist/index.js:394:36
$ echo $?
1 <-- ⚠️⚠️⚠️ `astro build` still exits with code 1Notice that after this PR, you no longer see the Completed in 1.21s message in the shell because the build() function itself throws the error and never finishes. This should be a minor improvement for users.
Changes
file()loader JSON Schema generation now emits ananyOfschema instead of a plain record-only schema- Branch 1:
{ type: 'array', items: <itemSchema> }— validates top-level array JSON/YAML files (e.g.[{ name: 'red', color: '#f00' }]) - Branch 2:
{ type: 'object', additionalProperties: <itemSchema>, properties: { $schema: string } }— validates record-style files and preserves the$schemaproperty - Before: VS Code flagged top-level array files as invalid against the generated schema
- After: Both array and record shapes pass validation
- Fixes #16602
- Changeset included (
.changeset/tangy-chairs-smile.md)
Testing
Updated packages/astro/test/content-intellisense.test.ts — renamed and expanded the existing test to assert:
schema.anyOfexists with exactly 2 branches- Array branch:
type: 'array'withitems.propertiesmatching the collection item schema - Object branch:
type: 'object'withadditionalProperties.propertiesmatching the item schema and a$schemaproperty present
Run with:
pnpm test packages/astro/test/content-intellisense.test.tsDocs
No docs change needed — this is an internal schema generation improvement. The file() loader public API is unchanged; users who already use file() get correct IntelliSense automatically.
@astrojs/cloudflare/fetch
import { astro, FetchState } from 'astro/fetch';
import { cf } from '@astrojs/cloudflare/fetch';
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const state = new FetchState(request);
const asset = await cf(state, env, ctx);
if (asset) return asset;
return astro(state);
}
}@astrojs/cloudflare/hono
import { Hono } from 'hono';
import { actions, middleware, pages, i18n } from 'astro/hono';
import { cf } from '@astrojs/cloudflare/hono';
const app = new Hono<{ Bindings: Env }>();
app.use(cf());
app.use(actions());
app.use(middleware());
app.use(pages());
app.use(i18n());
export default app;Changes
- Adds
@astrojs/cloudflare/fetchexporting acf(state, env, ctx)function that applies Cloudflare-specific setup to aFetchState— session KV binding injection, static asset serving via ASSETS,locals.cfContext, client address fromcf-connecting-ip,waitUntil, and prerendered error page fetch. Returns aResponsefor asset hits,undefinedotherwise. - Adds
@astrojs/cloudflare/honoexporting acf()Hono middleware that does the same setup, readingenvandexecutionCtxfrom the Hono context automatically (no arguments needed). - Extracts shared helpers from the existing
handler.tsintocf-helpers.tsandcf.ts, then refactorshandler.tsto use them. Zero duplication between thehandle()entrypoint and the new exports.
Testing
- Adds
cf-helpers.test.tswith 18 unit tests coveringcreateLocals,getClientAddress,matchStaticAsset,fallbackToAssets, andcreateErrorPageFetch. Tests use mockEnvandExecutionContextobjects — no fixtures or build required. - All 207 existing Cloudflare adapter tests continue to pass after the
handler.tsrefactor.
Docs
Changes
- Reverts
npm ciback tonpm iin the VSCode extension publish step of the release workflow.npm cirequires apackage-lock.jsonwhich does not exist inpackages/language-tools/vscode/(and is globally gitignored). This was broken by #16711.
Testing
- No test changes. This is a CI workflow fix — validated by the next release that publishes the VSCode extension.
Docs
- No docs needed — internal CI change only.
Astro v7 PR
Changes
Removes the astro:transitions APIs deprecated in #14989 and scheduled to be removed in Astro v7.
Testing
Green CI.
Docs
Changeset added.
Docs PR: withastro/docs#13886
/cc @withastro/maintainers-docs for feedback!
Changes
- When the triage bot publishes a preview release, the issue now gets a
fix pending verificationlabel and the comment asks the reporter to confirm the fix works - A new
fix-verificationworkflow listens for comments on issues with that label. A flue agent (Sonnet) classifies whether the comment is a positive confirmation - If verified: removes the label, generates a PR title/body via the
astro-pr-writerskill, creates the PR from the existingflue/fix-{N}branch, and adds afix verifiedlabel to the PR
This closes the loop on the triage bot fix pipeline: reproduce → diagnose → fix → test → preview release → reporter verification → PR creation.
Testing
- No automated tests — this is CI/agent infrastructure. Will be validated by the next triage bot run that produces a fix with a preview release.
Docs
- No docs needed — internal CI automation, not user-facing.
Changes
Fetchabletype: Exported fromastroso users can typesrc/app.tswithsatisfies Fetchable.FetchState.response: Set byPagesHandlerandAstroMiddlewareafter rendering so callers can readstate.response.App.Providers: New extendable interface for typing custom context providers onctxandAstro.fetchFileconfig: Lets users rename the entrypoint away fromsrc/app.ts, or setnullto disable.- Hono
cache()wrapper: Now follows the standard middleware pattern like the other Hono wrappers.
Testing
Added 6 unit tests in test/units/fetch/index.test.ts:
state.responseis set afterpages()andmiddleware()- Context provider
provide/resolvelazy creation and caching finalizeAllruns finalize for resolved providers, skips unresolved ones
Docs
Added here: withastro/docs#13890
Changes
Fixes dynamic routes returning the string [object Object] under the following conditions:
- when used with Cloudflare Workers with the
nodejs_compatflag. The affected projects all use compatibility dates of2025-04-01or newer, not sure if older dates are affected. - only for large, complex projects where Rollup splits code in a way that triggers it. The exact level of complexity/size required is unknown. The fix should work for projects of all sizes though.
This was a very difficult issue to track down. Static routes were fine, and in the Worker logs I could see that the dynamic routes were being processed correctly (I see things like logs indicating success for loading external content and other expected internal logging from the build process) but the returned response was just the string [object Object] regardless of which page or what content was expected -- no error messages of any kind.
I did not open an issue for this because by the time I had enough detail to do so, Claude Code had provided a fix, so I'm just going straight to a PR.
See included Claude Code analysis below for full details.
Testing
I did not add tests because I'm not sure how to provide a sufficiently complicated test project -- this problem only surfaces in larger projects. I'm happy to assist adding tests if I can get some help.
I do have this fix running in 3 separate projects ranging in size and complexity from a stripped down copy of the Astro blog starter template for Workers (where the problem never exhibited but this fix does not cause any issues), up to a 1000-page hybrid site with upwards of 100 Astro components and a mix of dynamic and static routes (where the problem surfaced and the fix works).
Claude Code's explanation of the issue and proposed fix were arrived at after several rounds of analysis and refinements, and its explanation makes sense and matches what I've been experiencing. I specifically asked it to check that the changes do not affect anything for Deno or Node prerendering environments, only workerd.
Docs
I don't believe docs updates are needed because this fixes an issue with expected functionality with no changes needed by the user.
Claude's analysis, RCA, and proposed fix, which I've implemented in this PR
If you're interested, here's what my AI agent found
This analysis was done with Astro 6.2.x and Cloudflare 13.3.x, but I've confirmed the problem still exists in the current versions. I'm barred by work policy from installing packages newer than 7 days so I cannot test the latest versions directly, but the problem code still exists in the latest version, unchanged.This analysis was done by comparing two projects:
astro-blog-starter-templatewhich is a copy of the Astro starter for Workers, stripped of all but one dynamic and one static test pageweb-dev-collibra-astrowhich is our largest, most complicated project where the problem surfaced
This is its final summary, verbatim:
Fix Summary — Astro isNode false-positive in workerd
Symptom
In an Astro 6.2.x project deployed to Cloudflare Workers via @astrojs/cloudflare 13.3.x with nodejs_compat, any route with prerender: false returns an HTTP 200, content-type: text/html response whose body is the 15-byte string [object Object]. Static / prerendered routes are unaffected. Reproducible locally with astro build && astro preview.
Root cause
astro/dist/runtime/server/render/util.js defines:
const isNode = typeof process !== "undefined"
&& Object.prototype.toString.call(process) === "[object process]";astro/dist/runtime/server/render/page.js then chooses the response body shape:
if (isNode && !isDeno) {
body = await renderToAsyncIterable(...); // Node-only async iterable
} else {
body = await renderToReadableStream(...); // standard ReadableStream
}With nodejs_compat, workerd's process reports Object.prototype.toString.call(process) === "[object process]" until the @cloudflare/unenv-preset polyfill replaces it with a plain object. The output of renderToAsyncIterable is an object exposing Symbol.asyncIterator. Node's undici accepts async iterables as a Response body; workerd's Response does not, and falls back to String(body) → "[object Object]".
Why the bug surfaces in some projects but not others
The bug is a module-initialization-order race that only manifests once Rollup decides to code-split Astro's shared runtime.
Both the failing project and the working starter contain the same two pieces of code in the worker bundle:
// A — unenv polyfill assignment (replaces workerd's raw process)
globalThis.process = _process;
// B — Astro's isNode evaluation (reads process)
const isNode = typeof process !== "undefined"
&& Object.prototype.toString.call(process) === "[object process]";The order in which A and B run depends on whether Rollup keeps them in one chunk or splits them across two:
astro-blog-starter-template (2 pages, almost no Astro internals reused) — Rollup keeps everything in one chunk, worker-entry_DEEjL72T.mjs:
line 558: globalThis.process = _process; ← A runs first
line 5864: const isNode = ... ← B sees the unenv polyfill
Single module body, top-down execution → process is the plain-object unenv polyfill by the time isNode runs → isNode = false → renderToReadableStream → works.
web-dev-collibra-astro (many pages and components importing from astro/runtime/server/*) — Rollup factors Astro's shared runtime into its own chunk:
transition_DTdhAtUt.mjs:814 const isNode = ... ← B
worker-entry_2ArJYird.mjs:1282 globalThis.process = _process; ← A
worker-entry imports transition. ES Module spec executes imported module bodies before the importer's body, so B runs before A. isNode is evaluated against workerd's raw nodejs_compat process, latches to true, and renderToAsyncIterable is selected → broken response.
The threshold is whatever pushes Rollup over its code-splitting heuristic for shared dependencies. Any project with enough reuse of astro/runtime/server/* across pages or components will eventually trip it — it's not specific to Vue, custom integrations, or middleware.
I confirmed module init order is the differentiator by logging Object.prototype.toString.call(process) at module init vs. request time in the failing project: "[object process]" at init, "[object Object]" at request time — proving the polyfill assignment happens between the two, after isNode has already been frozen.
The fix
One line in packages/astro/src/runtime/server/render/util.ts:
const isNode = typeof process !== "undefined"
&& Object.prototype.toString.call(process) === "[object process]"
&& !(typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers");Workerd's navigator.userAgent === "Cloudflare-Workers" is documented, stable, and present from t=0 — so the check is immune to the module-init-order race that broke the original process-only detection.
Behavior matrix
| Environment | process toString |
navigator.userAgent |
isNode before |
isNode after |
Behavior change |
|---|---|---|---|---|---|
| Node.js | "[object process]" |
undefined / Node ua | true |
true |
None |
| Deno | "[object process]" (Node compat shim) |
"Deno/x.y.z" |
true |
true |
None — also gated by !isDeno in renderPage |
| Browser | undefined | various, never "Cloudflare-Workers" |
false |
false |
None |
Cloudflare Workers (workerd) without nodejs_compat |
undefined | "Cloudflare-Workers" |
false |
false |
None |
Cloudflare Workers (workerd) with nodejs_compat, polyfill already installed before isNode eval |
"[object Object]" |
"Cloudflare-Workers" |
false |
false |
None (incidentally-correct case — the starter) |
Cloudflare Workers (workerd) with nodejs_compat, polyfill not yet installed |
"[object process]" |
"Cloudflare-Workers" |
true (wrong) |
false (correct) |
Bug fixed |
The change only flips behavior in the row where the original heuristic is wrong.
Cross-checks against the prerender flow
@astrojs/cloudflare exposes prerenderEnvironment: "node" | "workerd" (default "workerd").
prerenderEnvironment: "node": prerender runs in real Node. Nonavigatorglobal, originalisNode = truepreserved. Same as before. ✓prerenderEnvironment: "workerd"(default): prerender runs in workerd. New check correctly returnsfalse. TherenderToReadableStreampath produces aReadableStreambody, which Astro's prerender pipeline consumes the same way it consumes an async iterable. ✓
Verification performed
- Reproduced locally on
web-dev-collibra-astrowithastro build+astro preview—curl /dev/dynamicreturns[object Object](15 bytes). - Confirmed the path by logging
isNode,streaming, andbody.constructor.nameinside the bundledrenderPage—isNode=true, body constructor isObject(the async iterable), notReadableStream. - Confirmed module-init-order theory by logging
Object.prototype.toString.call(process)both at module init ("[object process]") and at request time ("[object Object]") — proves the polyfill assignment happens between the two. - Confirmed
astro-blog-starter-templateevaluatesisNode = falsebecause the unenv polyfill assignment (line 558) precedes theisNodeevaluation (line 5864) in the same chunk. - Applied the patch via
build-scripts/patch-astro-renderer.mjs; re-ran preview; dynamic route returns<!DOCTYPE html>...<div>TEST</div>, static route unchanged.
Details
Changes
Bug reported: #16667
FIXES #16667
The Astro extension was causing MDX highlighting errors, but only when the code snippets were indented:
BEFORE FIX:
AFTER FIX:
Testing
pnpm installpnpm -C packages/language-tools/vscode build- In Cursor: Run and Debug → Launch Client
- Opened an MDX file and tested an indented astro codeblock
Docs
No docs necessary
Changes
- Updates pnpm to v11
- Removes minimum release age exceptions that are no longer required
- Cleans up deps in a couple of fixtures that were accidentally using npm versions of monorepo packages
- Explicitly disallows postinstall scripts for all dependencies that have them
- Enables pnpm’s
trustPolicywhich prevents installing versions of packages that have reduced their publishing provenance. This required applying some limited exceptions to continue installing our currently installed packages.
Some semi-user-facing changes:
- Updated deps in the Netlify adapter
- Dropped support for older versions of VS Code so we can test with Node ≥22 which is pnpm 11’s minimum required version
Testing
Existing tests should pass
Docs
n/a — monorepo hardening only
Description
- Companion to #3902
- Tells Renovate to wait for 3 days before applying updates to match our pnpm policy
- In practice not super important as we mainly use Renovate for GitHub Actions, which don’t involve pnpm, but still good to keep these in sync
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| pnpm/action-setup | action | patch | v6.0.7 → v6.0.8 |
| pnpm/action-setup | action | patch | v6.0.5 → v6.0.8 |
| withastro/automation | action | digest | 3754a74 → 53c85fa |
Release Notes
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Description
This PR updates to pnpm 11 and configures a 3-day minimum release age for dependencies to mirror the main astro monorepo: https://github.com/withastro/astro/blob/3abcc86c092ca2a3455fc0ffc72b6b7ca300f1a2/pnpm-workspace.yaml#L32
This helps defend a little against supply chain attacks, but also avoids us installing deps that then cause issues in the astro monorepo CI because they’re newer than their config allows.
Summary
- Adds
permissions: {}at the top level of every workflow that was missing it - Adds explicit job-level
permissionsto jobs that previously inherited the default token permissions - This ensures no workflow accidentally gets broad
GITHUB_TOKENaccess
Affected workflows: ci.yml, scripts.yml, check.yml, semgrep.yml, congrats.yml, continuous_benchmark.yml, issue-triage.yml, merge-fix.yml, merge-main-to-next.yml.
Part of supply chain hardening in response to the TanStack/Mini Shai-Hulud compromise.
What''s wrong?
When a developer adds a script-src or style-src entry to security.csp.directives, Astro rejects it with a generic Zod "Did not match" error that gives no actionable guidance.
Closes #16663
What changed?
Replaced the bare z.custom() validator in packages/astro/src/core/csp/config.ts with a z.string().superRefine() that emits two distinct, actionable errors:
- Managed directive (
script-src,style-src): tells the user which directive is managed by Astro and points them to the correct config key (security.csp.scriptDirectiveorsecurity.csp.styleDirective). - Unknown directive: lists all allowed directives so the user knows exactly what is valid.
Before / After
Before
[ConfigError] Did not match union
After (script-src example)
[ConfigError] The `script-src` directive is managed by Astro and cannot be set in `security.csp.directives`. Use `security.csp.scriptDirective` instead.
Checklist
- Added changeset (patch for
astro) - No behaviour change for valid configs — only the error path is affected
- Follows existing
superRefine+z.ZodIssueCode.custompattern used in the codebase
Summary
- Removes pnpm store cache (
cache: "pnpm") from the release workflow - Removes Turbo remote cache (
TURBO_TOKEN/TURBO_TEAM) - Adds
--forceto the build step to skip any local Turbo cache - Adds
--frozen-lockfileto ensure the lockfile is the source of truth
The release workflow now does a fully clean install and build from scratch every time. This eliminates cache poisoning as an attack vector for the publish pipeline.
Part of supply chain hardening in response to the TanStack/Mini Shai-Hulud compromise.
Summary
Hardens the preview-release workflow against the attack vector used in the TanStack/Mini Shai-Hulud compromise.
Two changes:
- Block fork PRs from preview releases. Adds a
head.repo.full_name == github.repositorycheck so only branches withinwithastro/astrocan trigger preview builds. This prevents a malicious fork from running code in a privileged context. - Split into two jobs to isolate OIDC token. The
buildjob runspnpm installandpnpm buildbut has noid-token: write. Thepublishjob hasid-token: writebut only downloads build artifacts and runspkg-pr-new— it never executes user code. This ensures the OIDC token is never available during code execution, even for non-fork PRs.
The build job uploads only the minimal files needed for publishing (root workspace files + affected package directories), not the entire workspace.
Description
This PR contains various improvements to the E2E tests mostly focusing on CI performance.
Here is a table summarizing some of the most impactful changes comparing the before, before average, and after times on my personal machine and CI:
| Task | Host | Before1 | Before average | After |
|---|---|---|---|---|
| E2E tests | Personal Mac | 43.6s | N/A | 28.9s |
| E2E tests | CI - Linux | 2m 27s | 2m 34s | 1m 29s |
| E2E tests | CI - Windows | 5m 9s | 5m 11s | 3m 11s |
| A11y tests | Personal Mac | 1m 1s | N/A | 41s |
| A11y tests | CI - Linux | 2m 39s | 2m 43s | 1m 47s |
You can find below a review were I try to comment on the reasoning behind most of the changes.
Footnotes
-
For CI times, the "before" times are from merging to
mainthe recent autogenerated sidebar groups refactor. ↩
Summary
- Pins
actions/labelerfrom mutable@v4tag to commit SHAac9175f8a1f3625fd0d4fb234536d26811351594(v4.3.0) - This was the only action in the repo not pinned to SHA
- This workflow runs on
pull_request_targetwith write permissions, so a tag hijack would affect every PR including forks
Part of supply chain hardening in response to the TanStack/Mini Shai-Hulud compromise.
Changes
Close #16248
Root cause
Dependencies imported within .astro file frontmatter are not detected during Vite's initial Dependency Optimization phase. This leads to several issues.
Solution
I have added an onResolve handler to the astro-frontmatter-scan plugin to ensure proper dependency discovery
Testing
Before Implemented
After Implemented
Docs
Summary
Fixes #16705
Fixes remoteBindings: false being ignored during astro build. The Cloudflare prerenderer's internal Vite preview server now receives the user's adapter options, so remote-flagged bindings (e.g. a D1 database with remote: true in wrangler.toml) are emulated locally during build, matching the existing astro dev behavior.
Changes
The fix is accomplished by forwarding cloudflareOptions (which include remoteBindings setting) to createCloudflarePrerenderer()
Description
Fixes the theme select dropdown having illegible colors in dark mode on Windows Firefox.
On Windows, Firefox renders native <select> dropdown options with white backgrounds in dark mode, making the text nearly invisible. The hovered/checked option text color is also white-on-white.
Before
The theme select in dark mode on Windows Firefox shows white option backgrounds with poor contrast, making it hard to read available options.
After
Explicit dark-theme color rules are applied to the select and option elements, using the existing Starlight design tokens (--sl-color-gray-6, --sl-color-text, --sl-color-accent-high, --sl-color-text-invert).
Changes
- Added dark-mode-specific styles for
select,option, andoption:checkedinSelect.astro - Added hover state styles for options in dark mode
- Uses existing CSS custom properties for consistency with the rest of the theme
Fixes #3426
Fixes #14013.
On case-insensitive filesystems (Windows, macOS), starting astro dev from a cwd whose case differs from the actual filesystem case (e.g. d:\dev\app vs. D:\dev\app) made the project root and Vite-resolved module ids disagree on case. normalizeFilename then failed its commonAncestorPath check on the absolute id, treated it as a server-relative URL, and concatenated it onto the root — producing a bogus path that missed the compile metadata cache and stripped scoped styles.
The lookup now falls back to a case-insensitive ancestor check, so absolute paths inside the project are recognized regardless of case and the cache hit succeeds.
Changes
- Content entry styles (
?astroPropagatedAssets) are now collected at render time instead of being baked into the module at transform time. The old approach captured a snapshot of the Vite module graph during the transform hook — if the graph was incomplete at that moment, styles were permanently missing. A newdev-style-collector.tsregistry (shared viaglobalThisSymbol.forto bridge the Vite plugin and SSR runner module caches) lets theContentcomponent inruntime.tscallgetStylesForURLfresh on each render. - Adds
templateDepthguards tohead/maybe-headinstructions incommon.tsso thatmaybeRenderHead()never fires inside an inert<template>element in layouts (e.g. theme-provider icon sprites).
Closes #16181
Testing
test/content-collections-dev-css.test.ts— dev server integration test verifying scoped styles from.astrocomponents in MDX content entries are present across multiple collections and with component indirection.test/units/render/inert-head-guard.test.ts— verifiestemplateDepthguard suppresses head rendering inside templates.test/units/render/head-in-template.test.ts— simulates Layout → ThemeProvider →<template>→ Icon pipeline, verifying styles land in<head>.
Docs
- No docs update needed — bug fix restoring expected behavior.
The updateImageReferencesInData function uses Traverse.map which uses a custom cloning algorithm and as such does not copy correctly instances of native objects such as Map and Set rendering them unusable.
Instead Travserse.forEach can be used after cloning the data object beforehand with the native structuredClone function.
Closes #16682
Changes
- Use
Travserse.forEachinstead ofTraverse.mapinupdateImageReferencesInData - Clone data before updating image references
- Update getEntry to not mutate data in store when updating image references
Testing
- 2 new tests added to cover Map/Set regressions
Docs
- no docs changes, this is a bug fix
fixes: #16688
Changes
There was an issue where passing the entry property, returned by the getLiveEntry function imported from astro:content, to the render function caused a type error.
This pull request adds tests and a changeset to the fix originally authored by Houston bot.
The
render()function type inpackages/astro/templates/content/types.d.tsonly has an overload for regular content collection entries (DataEntryMap[C][string]), but no overload forLiveDataEntry. When a project uses onlylive.config.ts,DataEntryMapis empty, sorender()acceptsnever. The live collections RFC and the existing test fixture both showrender(entry)being used with live entries — the type overload was simply missed when live collections were added in PR #13685.
I found a potential fix for this issue. The fix adds a
render()overload forLiveDataEntry<LiveLoaderDataType<C>>to the type template. It's a 4-line change that brings the type declarations in line with the runtime behavior. All existing tests (live loaders, content collections, type inference, render) continue to pass.
Testing
Added a test using the astro check command to verify that no type errors occur.
No changes were made to the test fixtures themselves. The error was originally occurring at the following location.
| import { getLiveEntry, render } from "astro:content"; | |
| const { entry } = await getLiveEntry("liveStuff", "123"); | |
| if (!entry) { | |
| return new Response(null, { status: 404 }); | |
| } | |
| const { Content } = await render(entry); |
src/pages/index.astro:10:34 - error ts(2345): Argument of type 'LiveDataEntry<{ ... }>' is not assignable to parameter of type 'never'.
10 const { Content } = await render(entry);
~~~~~Docs
N/A, bug fix
Changes
- When the triage bot produces a high-confidence fix (fix verified + unit test created), it now publishes a preview release via pkg.pr.new so reporters can immediately test the fix with
npm i https://pkg.pr.new/astro@<sha>. - The fix skill now generates a changeset file (
.changeset/<slug>.md) for every successful fix, making the branch PR-ready with proper changelog entries. - Preview URLs are included in the triage bot issue comment under a "Try this fix" section, giving reporters a one-liner install command.
Testing
- No test changes — this modifies CI agent infrastructure (skill instructions, Flue orchestrator, GHA workflow).
Docs
- No docs needed — this is internal CI tooling, not user-facing.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| pnpm/action-setup | action | patch | v6.0.6 → v6.0.7 |
Release Notes
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Changes
- What does this change?
- Be short and concise. Bullet points can help!
- Before/after screenshots can help as well.
- Don't forget a changeset! Run
pnpm changeset. - See https://contribute.docs.astro.build/docs-for-code-changes/changesets/ for more info on writing changesets.
Testing
Docs
Changes
This PR removes a bunch of tests and assertions that are already checked against unit tests, so there's so reason to use a dev server for that.
The few that were still needed, they are checked against the build because it's the same behaviour
Testing
Green CI
Docs
Changes
- Adds
ASTRO_VITE_ENVIRONMENT_NAMES.astrotoisAstroServerEnvironment()inpackages/astro/src/environments.ts. The internal'astro'environment is a Node.js fallback used when the'ssr'environment is not runnable (e.g. Cloudflare adapter with workerd). Without this, Vite plugins like the script loader served actual client-side script content in this environment, causingHTMLElement is not definedcrashes on HMR reload.
Closes #16626
Testing
- New unit test
packages/astro/test/units/environments.test.tscoveringisAstroServerEnvironmentandisAstroClientEnvironmentfor all four environment names (ssr,prerender,astro,client).
Docs
- No docs update needed — this is an internal bug fix with no API change.
Changes
This PR targets the integration tests that user the dev server. The vast majority of the logic is already tested in a unit test environment, so they were removed.
some functions that were used in the dev server as middleware, they are extracted as a unit test function and tested.
Testing
Removed some fixtures. Green CI.
Docs
Changes
I encountered the following flaky test:
https://github.com/withastro/astro/actions/runs/25663263442/job/75329416110#step:8:7300
Error: EPERM: operation not permitted, rename 'D:\a\astro\astro\packages\astro\test\fixtures\astro-assets-prefix\node_modules\.astro\data-store.json.tmp' -> 'D:\a\astro\astro\packages\astro\test\fixtures\astro-assets-prefix\node_modules\.astro\data-store.json'
I investigated this error. Two test files test/asset-query-params.test.ts and test/astro-assets-prefix.test.ts are using the same fixture ./fixtures/astro-assets-prefix/. Although these two tests have different outDir as added in #16382, they still share the same cache directory ./fixtures/astro-assets-prefix/node_module/.astro/. This seems to be the reason for the flaky test above.
In this PR, I provide a unique cacheDir for every fixture that is used in multiple test files.
15 fixtures are affected:
Fixture (test/fixtures/…) |
Test files |
|---|---|
astro pages |
test/astro-pages.test.tstest/static-build-page-dist-url.test.tstest/static-build-vite-plugins.test.ts |
astro-assets |
test/asset-query-params.test.tstest/astro-assets.test.ts |
astro-assets-prefix |
test/asset-query-params.test.tstest/astro-assets-prefix.test.ts |
astro-basic |
test/astro-basic.test.tstest/astro-sync.test.tstest/config-mode.test.ts |
astro-dev-headers |
test/astro-dev-headers.test.tstest/chrome-devtools-workspace.test.ts |
css-order-layout |
test/css-order-layout.test.tstest/css-order.test.ts |
dev-render |
test/config-format.test.tstest/dev-base.test.tstest/dev-render-chunk.test.tstest/dev-render-components.test.tstest/integration-route-setup-hook.test.ts |
endpoint-routing |
test/endpoint-response.test.tstest/endpoint-routing.test.tstest/endpoint-runtime.test.ts |
fonts |
test/asset-query-params.test.tstest/fonts.test.ts |
middleware space |
test/featuresSupport.test.tstest/middleware.test.ts |
multiple-jsx-renderers |
test/asset-query-params.test.tstest/multiple-jsx-renderers.test.ts |
server-islands/hybrid |
test/csp-server-islands.test.tstest/server-islands.test.ts |
server-islands/ssr |
test/csp-server-islands.test.tstest/server-islands.test.ts |
ssr-assets |
test/ssr-assets.test.tstest/units/app/logger.test.ts |
ssr-request |
test/ssr-adapter-build-config.test.tstest/ssr-request.test.ts |
Testing
CI should pass.
Docs
N/A
Changes
- Applied
@typescript-eslint/no-floating-promisesrule to all tests; - Fixed all issues to pass ESLint. Check my code comments / PR comments for details.
- This PR is the first step. I eventually want to apply the rule to all code, including package sources.
Testing
CI should pass
Docs
N/A
Awesome work by @OfirHaf and @fkatsuhiro! Fixing bug #16633 🎉
This PR is a curated combination of #16674 and #16676.
What does this fix?
Closes #16633
SVG elements with explicit width="0" or height="0" attributes threw NoImageMetadata: Could not process image metadata instead of being imported normally. This is a valid and common pattern for SVG filter containers hidden from screen readers:
<svg width="0" height="0" aria-hidden="true">
<defs>...</defs>
</svg>Changes
issus: #16633
Root cause
Two falsy-zero checks along the metadata extraction path:
vendor/image-size/types/svg.ts — the calculate() function:
// Before: 0 is falsy, so an SVG with width="0" height="0" fell through
if (attrs.width && attrs.height) { ... }
// After: explicit null check allows 0 as a valid dimension
if (attrs.width != null && attrs.height != null) { ... }assets/utils/metadata.ts — post-probe validation:
// Before: !0 === true, triggering the error for zero dimensions
if (!result.height || !result.width || !result.type) { ... }
// After: only null/undefined signals a missing value
if (result.height == null || result.width == null || !result.type) { ... }Changes
packages/astro/src/assets/utils/vendor/image-size/types/svg.ts: replace truthiness check with null check incalculate()packages/astro/src/assets/utils/metadata.ts: replace truthiness check with null check inimageMetadata()
Both changes are minimal and surgical — they only affect the specific falsy-zero edge case while leaving all other behavior unchanged. SVGs with missing dimensions (null/undefined) still throw the appropriate error.
Testing
Befor implement testing
After implement testing

Changes
Added instructions for linking the correct folder when using pnpm link for installing your local copy of astro. It took me a while to figure out which folder actually needed to be linked. It is not enough to link to the root folder using pnpm 11.0.9.
Testing
No code changed.
Docs
This is only for contributors.
Changes
Fixes a regression introduced in #16366, where Astro.cache was undefined when experimental.cache was not configured.
The previous documented behaviour is for Astro.cache to always be defined as a no-op shim: cache.set() warns once, cache.invalidate() throws and cache.enabled can be used to gate. This allows library and user code can call cache methods without conditional checks. The cache provider registration was being gated at the call site on experimental.cache being configured, which meant the disabled shim branch inside the provider was unreachable and the Astro.cache getter was never attached to the context.
Testing
Added a regression test
Docs
Bug fix
Summary
createStaticHandler in @astrojs/node looks up per-route static headers (e.g. Content-Security-Policy) using header.pathname.includes(baselessPathname). The headers map is keyed by exact prerendered route pathnames written by core/build/generate.ts (routeToHeaders.set(pathname, ...)), and the comment immediately above the lookup states that headers are "keyed by base-less route paths" — so the intent is keyed lookup, not substring matching.
Substring matching causes the wrong headers to be applied whenever one prerendered route's path is a substring of another's:
- Two prerendered routes
/usersand/users/profile, each with a different CSP. /users/profileis inserted intorouteToHeadersfirst → it ends up first in the JSON array.- A request for
/usersmatches the route correctly viaapp.match, butheadersMap.find(h => h.pathname.includes("/users"))returns the/users/profileentry. - The wrong CSP is sent on the
/usersresponse.
The fix is a one-line change to use === instead of .includes().
Changes
packages/integrations/node/src/serve-static.ts: matchheader.pathnameby equality.- Changeset:
@astrojs/nodepatch.
Test plan
-
pnpm -C packages/integrations/node testpasses. - Manual: build a project with two prerendered routes where one path is a prefix of the other, both emitting different CSPs via the static-headers feature; confirm each route receives its own CSP under
@astrojs/nodestandalone.
Changes
This PR adjusts the reference zod support to allow numbers that are transformed to strings.
This has been an issue with collection-A markdown content that references collection-B entries with 1234 in the frontmatter as the yaml is parsed to numbers.
Testing
Added a small unit test that tests the case.
Docs
/cc @withastro/maintainers-docs I am not if documentation for this is needed.
Description
Opposite of:
but the goal is the same: to silence the Postinstall Scripts Unapproved notification when pnpm install.
References:
| Package | URL | Quote |
|---|---|---|
| esbuild | evanw/esbuild#4085 (comment) | The install script still optimizes the command by replacing the JavaScript shim by the actual binary executable, so that running the esbuild command directly doesn't have to run node first just to shell out to the executable. So it's not necessary for correctness (and you're welcome to disable the install script if you'd like to and if it still works for you) but it might result in esbuild being slower than it would have been otherwise. |
| sharp | https://sharp.pixelplumbing.com/install/ | When using |
Changesets are NOT required.
Footnotes
-
equivalent to
allowBuilds=falsein pnpm v10.26+. ↩
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated.
next is currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, run changeset pre exit on next.
Releases
astro@7.0.0-alpha.1
Patch Changes
- #16603
deaaf3fThanks @alexanderniebuhr! - Removes the warning that Astro does not support vite v8, since Astro v7 does support vite v8
Description
- Fixes #3890 (review) & #3890 (comment)
Tweak a comment: "non-Firefox" → "Chromium-based"
Safari has already supported the syntax :lang(zh, ja).
The position of Opera is the culprit.
This change is so trivial that we should ship it with other changes.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| pnpm/action-setup | action | patch | v6.0.5 → v6.0.6 |
| withastro/automation | action | digest | c8a2e8b → 3754a74 |
Release Notes
pnpm/action-setup (pnpm/action-setup)
v6.0.6
What's Changed
Full Changelog: pnpm/action-setup@v6.0.5...v6.0.6
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Changes
- Adds a new "Write a Unit Test" step to the triage bot's fix skill, so it produces a regression test alongside every fix attempt.
- Creates
reference/unit-testing.mddocumenting unit test conventions, file placement, and the shared test utilities/mocks available in the repo. Referenced fromAGENTS.mdso any agent (not just triage) can use it. - Adds a Unit Test line to the triage bot's GitHub comment template.
Testing
- No test changes — this PR only modifies agent instructions and reference docs.
Docs
- No user-facing docs needed.
reference/unit-testing.mdis agent-facing only.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@astrojs/starlight@0.39.2
Patch Changes
Description
- Fixes #3872 (comment)
:lang(zh, ja) used in #3872 shamefully has not been supported Chromium and Safari. As a result, text-autospace has been enabled only in Firefox. Replaced it with the combination of :lang(zh) and :lang(ja).
Screenshot in Chrome (Pay attention to the slight presence of whitespace around "Astro" and "Starlight"):
Before (https://starlight.astro.build/ja/getting-started/):
After:

Changes
Since the creation of astro:assets, there has been many lingering issues regarding default output formats. The way the pipeline has to work for SSG / SSR, perf reasons etc, means that we typically don't know the format of the image for real if its remote, which is mostly fine except for one case: SVGs. Since we don't want to convert SVGs to WebP by default, and also the reverse, we often need to know what we're working with to set the default format.
Previously, this wasn't that much of an issue, because imo, it's kinda edge casey and being remote images meant that anyway it's up to the user to authorize. However, now in 6.3 with dangerouslyProcessSVG, it's actually problematic to not really know what we're dealing it, because it could mean a SVG go through when it shouldn't, but also the reverse!
This PR tries to fix this through a few methods:
- Do a best effort guess at the format early on, allowing ourselves to fail sometimes
- ... If we failed and we're not converting, and we're in SSG, use the service's way of inferring formats to craft the proper file type, or let the service handle it (as it should, since images could anyway come from a random request!)
- Harden the service against these problems in various ways
Testing
Updated and added tests for certain of the newly covered cases
Docs
N/A
Changes
Updates the attribute escaping logic to use named entities (& and ") instead of numeric entities (& and ").
Currently, <title> tags render named entities, while other meta tags render numeric entities. Although both formats are semantically equivalent, this results in inconsistent raw HTML output.
This change aligns the escaping behavior across all head and meta tags so they consistently render named entities.
Closes #16657
Testing
Updates the test cases that rendered & and " to instead render & and ".
Docs
N/A
Changes
- Fixes #16201 — the Cloudflare adapter silently replaced a user-defined
image.servicewith@astrojs/cloudflare/image-service-workerdwheneverimageServicewas unset (default'cloudflare-binding') or set to'compile'/'cloudflare-binding'/'cloudflare'/'passthrough'. Custom services (e.g., a third-party CDN service) are now preserved across all modes. - Added a guard at the top of
setImageConfigthat detects a non-defaultimage.service(entrypoint ≠astro/assets/services/sharp) and returns the config untouched, mirroring the precedent in the existing fallbackdefault:branch and the explicit'custom'mode. - Emits a single
logger.infoline when this preservation kicks in, telling the user the override was skipped and pointing them atimageService: 'custom'to silence the notice. - Added a
.changeset(@astrojs/cloudflarepatch).
Testing
- New unit test:
packages/integrations/cloudflare/test/image-config.test.ts(10 cases) covering preservation acrossundefined,'passthrough','cloudflare','cloudflare-binding','compile', and the compound{ build: 'compile' }/{ build: 'compile', runtime: 'cloudflare-binding' }configs. Also asserts the existing override of default sharp in'cloudflare-binding'and'compile'is preserved (no regression) and that explicit'custom'continues to work. - Existing image-related fixture tests run clean:
external-image-service.test.ts(2/2),binding-image-service.test.ts(7/7),compile-image-service.test.ts(5/5). pnpm run buildsucceeds;tsc -bclean.
Docs
No user-facing docs changes needed. Behavior aligns with what the existing docs already imply (a custom image.service should be respected); this PR brings the Cloudflare adapter into line. Worth a brief note in the Cloudflare adapter README that custom services are preserved alongside imageService modes — happy to follow up in withastro/docs if maintainers prefer.
Closes #16201
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.2
Patch Changes
-
#16675
11d4592Thanks @ascorbic! - Fixes a regression whereAstro.cachewasundefinedwhenexperimental.cachewas not configured.The previous documented behavior is for
Astro.cacheto always be defined as a no-op shim:cache.set()warns once,cache.invalidate()throws andcache.enabledcan be used to gate. This allows library and user code can call cache methods without conditional checks. The cache provider registration was being gated at the call site onexperimental.cachebeing configured, which meant the disabled shim branch inside the provider was unreachable and theAstro.cachegetter was never attached to the context. -
#16691
0f0a4ceThanks @matthewp! - FixesHTMLElement is not definederror during HMR when using components with client-side scripts (e.g. Starlight<Tabs>) and the Cloudflare adapter -
#16562
07529ecThanks @matthewp! - Fixes non-prerendered routes failing when a dynamic prerendered route exists in the same project withprerenderEnvironment: 'node' -
#16638
272185bThanks @ematipico! - Fixes a bug where the Astro compiler wasn't freed at the end of the build. After the fix, the memory used by the compiler is now correctly freed at the end of the build. -
#16544
d365c97Thanks @matthewp! - TightensisRemotePath()to reject control characters after a leading slash and fixes the dev image endpoint origin check -
#16685
889e748Thanks @farrosfr! - Improve validation messages forsecurity.csp.directiveswhenscript-srcorstyle-srcare incorrectly placed in thedirectivesarray. -
#16605
772f13aThanks @rururux! - FixesassetsPrefixnot being available onbuildfromastro:config/server. -
#16556
f38dec7Thanks @matthewp! - Rejects double-encoded URL paths with a 400 response instead of silently falling back to partial decoding -
#16659
38bcb25Thanks @jsparkdev! - Fixes&characters appearing as raw entity strings (e.g.&) in<meta>tags when viewed in link previews or raw HTML. -
Updated dependencies [
d365c97,9256345]:- @astrojs/internal-helpers@0.9.1
- @astrojs/markdown-remark@7.1.2
@astrojs/prism@4.0.2
Patch Changes
- #15723
9256345Thanks @rururux! - Fixes an issue where the<Prism />component failed to work in Cloudflare Workers.
@astrojs/cloudflare@13.5.1
Patch Changes
-
#16707
2ff3f8fThanks @helio-cf! - FixesremoteBindings: falsebeing ignored duringastro build. The Cloudflare prerenderer's internal Vite preview server now receives the user's adapter options, so remote-flagged bindings (e.g. a D1 database withremote: trueinwrangler.toml) are emulated locally during build, matching the existingastro devbehavior. -
#16652
98c32ccThanks @greatjourney589! - Fixes user-declared KV namespace bindings being duplicated in the generateddist/server/wrangler.json, which caused wrangler validation to fail with " assigned to multiple KV Namespace bindings." The Astro Cloudflare config customizer now returns only the auto-injectedSESSIONbinding and lets@cloudflare/vite-pluginmerge it with the user's wrangler config, instead of pre-merging the user's bindings into the output. -
#16272
4f9521eThanks @barry3406! - Fixes.astrofiles failing withNo matching export in "html:..." for import "default"when default-imported from a.tsfile -
#15723
9256345Thanks @rururux! - Fixes an issue where the<Prism />component failed to work in Cloudflare Workers. -
Updated dependencies [
d365c97]:- @astrojs/internal-helpers@0.9.1
- @astrojs/underscore-redirects@1.0.3
@astrojs/markdoc@1.0.5
Patch Changes
-
#15723
9256345Thanks @rururux! - Updates internal type usage from@astrojs/prism. -
Updated dependencies [
d365c97,9256345,9256345]:- @astrojs/internal-helpers@0.9.1
- @astrojs/markdown-remark@7.1.2
- @astrojs/prism@4.0.2
@astrojs/mdx@5.0.5
Patch Changes
- Updated dependencies [
9256345]:- @astrojs/markdown-remark@7.1.2
@astrojs/netlify@7.0.9
Patch Changes
-
#16716
04fdbb2Thanks @delucis! - Updates internal dependencies -
Updated dependencies [
d365c97]:- @astrojs/internal-helpers@0.9.1
- @astrojs/underscore-redirects@1.0.3
@astrojs/node@10.1.1
Patch Changes
- Updated dependencies [
d365c97]:- @astrojs/internal-helpers@0.9.1
@astrojs/preact@5.1.3
Patch Changes
- Updated dependencies [
d365c97]:- @astrojs/internal-helpers@0.9.1
@astrojs/react@5.0.5
Patch Changes
- Updated dependencies [
d365c97]:- @astrojs/internal-helpers@0.9.1
@astrojs/svelte@8.1.1
Patch Changes
@astrojs/vercel@10.0.7
Patch Changes
- Updated dependencies [
d365c97]:- @astrojs/internal-helpers@0.9.1
@astrojs/internal-helpers@0.9.1
Patch Changes
- #16544
d365c97Thanks @matthewp! - TightensisRemotePath()to reject control characters after a leading slash and fixes the dev image endpoint origin check
@astrojs/ts-plugin@1.10.8
Patch Changes
astro-vscode@2.16.15
Patch Changes
@astrojs/markdown-remark@7.1.2
Patch Changes
Closes #16590
Changes
- Fixes a regression introduced in #16555 where user-declared KV namespace bindings (e.g.
RATE_LIMIT,CACHE) appear twice in the generateddist/server/wrangler.json, causing wrangler to fail with"<binding> assigned to multiple KV Namespace bindings.". - The Astro Cloudflare config customizer now returns only the auto-injected
SESSIONbinding fromkv_namespaces, instead of pre-merging the user's existing bindings into the output. @cloudflare/vite-pluginalready merges the customizer's output with the user's wrangler config, so echoing the user's bindings caused the duplication.- Removed the now-unused
withSessionKVBinding()helper. - Added a changeset (
@astrojs/cloudflarepatch).
Affected versions reported in the issue: @astrojs/cloudflare@13.3.0–13.3.1 + astro@6.2.x. Last working combination was astro@6.1.9 + @astrojs/cloudflare@13.2.1, which matches the pre-#16555 behavior this PR restores.
Testing
- Updated two existing assertions in
packages/integrations/cloudflare/test/wrangler.test.tsthat locked in the buggy merged-output shape. - Added a dedicated regression test
does not include user-declared KV bindings in the output (regression #16590)covering the multi-binding scenario from the issue (RATE_LIMIT+CACHEdeclared by the user) and asserting the customizer returns only[{ binding: 'SESSION' }]. - All 25 tests in
wrangler.test.tspass locally.
Docs
No documentation changes needed. The fix restores documented and previously-shipped behavior — the public API and configuration surface (sessionKVBindingName, automatic SESSION binding provisioning) are unchanged.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.1
Patch Changes
Changes
- Restores the
FREDKBOT_GITHUB_TOKENenv var mapping that was accidentally dropped in the flue 0.3 upgrade (#16441). Without it,postGitHubCommentfails at the end of every triage run.
Testing
- No test changes. Verified by inspecting the failing run.
Docs
- No docs needed — CI-only change.
Description
Adds generic clock, desktop, mobile phone, app window, database, server, git branch, notes, question mark, analytics, and padlock icons. Also adds a new logo icon for SolidJS. The mobile icon has a bit of a funny name — mobile-android — but I kept it to match the source library’s name.
Which icons to include built-in is always a bit subjective, but I went through our icon library and tried to add a handful that might meet some more common needs on docs sites given I was already adding a couple based on user requests.
For the SolidJS logo, I asked on their Discord server to confirm which style they preferred for a monochrome icon (as it deviates from their more common blue gradient style).
Changes
After #16402 migrated every test under packages/astro/test to TypeScript, two scripts in packages/astro/package.json were left pointing at non-existent .test.js globs:
"test:match": "astro-scripts test \"test/**/*.test.js\" --match",
"test:cli": "astro-scripts test \"test/**/cli.test.js\"",Because astro-scripts test wraps the matched files in a temp module before invoking node:test (see scripts/cmd/test.js:90-102), an empty match still produces a passing run — node:test counts the empty wrapper as a single passing "test" — so the breakage was silent. pnpm test:cli reports tests 1, pass 1, suites 0 and exits in ~100ms without ever executing the real CLI tests in test/cli.test.ts.
This PR:
- Updates the globs to
.test.tsso they match real files (test/cli.test.ts,test/units/**/*.test.ts,test/*.test.ts). - Adds
--strip-typessonode:testcan load the TypeScript files, matching the existingtest:unitandtest:integrationscripts on the surrounding lines. - Restores
test:matchas the documented "run a subset of tests by name pattern" command referenced inCONTRIBUTING.mdlines 130-132.
Testing
Verified locally before/after the change:
Before — pnpm test:cli (phantom pass, no real tests run):
> astro-scripts test "test/**/cli.test.js"
✔ /workspaces/astro/packages/astro/node_modules/.astro/test.mjs (90ms)
ℹ tests 1 ℹ suites 0 ℹ pass 1
After — pnpm test:cli --match "astro --version" (real cli.test.ts test):
> astro-scripts test "test/**/cli.test.ts" --strip-types --match 'astro --version'
▶ astro cli
✔ astro --version (338ms)
✔ astro cli (339ms)
ℹ tests 1 ℹ suites 1 ℹ pass 1
Note the suite count flips from 0 → 1, confirming cli.test.ts is actually loaded and the describe('astro cli') suite runs.
Docs
No docs changes needed — CONTRIBUTING.md:130-132 already documents pnpm test:match "<pattern>"; this PR makes that documented behaviour work again.
No changeset added — internal-only package.json script change, no published-package surface affected.
Changes
- Renames
packages/astro/e2e/astro-island-hydration-error.test.jsto.test.tsand adds minimal type annotations to align with the e2e test convention established in #16402. - This file was introduced in #16412 (after #16402 had already migrated every other e2e test to TypeScript), so it landed using the prior
.jsconvention. It is the only remaining.test.jsfile inpackages/astro/e2e/. - As a side effect, the file is now matched by
playwright.config.js(which usestestMatch: 'e2e/*.test.ts'), so its two regression tests forastro:hydration-errorrecovery (added in #16412) now actually execute in CI. They were silently skipped before this rename.
Testing
pnpm exec tsc --noEmit -p tsconfig.test.jsonpasses against the converted file.- The diff is a near-mechanical port of the existing test (12 insertions / 6 deletions), preserving identical test semantics.
gitrecognises the change as a rename (83% similarity). - The
Window.__hydrationErrorEventstyping follows the samedeclare global { interface Window {...} }pattern already used bye2e/view-transitions.test.ts.
Docs
No docs changes required — internal test refactor with no public-API surface.
No changeset added — internal-only test file rename, no published-package behaviour changes.
Changes
- The generic image endpoint (
assets/endpoint/generic.ts) self-fetches local images from the same origin. #16519 added anisRemoteAllowedcheck on the response URL, but that check rejects local URLs (e.g.http://host/_astro/image.png) since they aren't inimage.domainsorimage.remotePatterns. This caused local images on non-prerendered pages to 404. - Extracted the fetch logic into
loadImage.tswith anisRemoteflag that gates theisRemoteAllowedcheck. Local images skip it — they're already protected by the same-origin guard in the caller.
Fixes #16644
Testing
- Added
test/units/assets/endpoint-load-image.test.tswith 4 cases: local image succeeds, unauthorized remote rejected, allowed remote succeeds, fetch failure handled. The local image test fails without the fix.
Docs
- No docs needed — this is a regression fix restoring prior behavior.
Changes
A flaky test was detected in astro-assets-prefix.test.ts in CI.
https://github.com/withastro/astro/actions/runs/25502409719/job/74839082587
The root cause is likely that tests are now running in parallel, and both astro-assets-prefix.test.ts and astro-assets-prefix-multi-cdn.test.ts share the same fixture while also calling fixture.clean() in their after() hooks, which deletes the cache folder.
When the test fails, the log shows "The collection "blog" does not exist or is empty. Please check your content config file for errors." and as shown here, the getDataStoreFile function references the .astro folder, which is exactly what clean() removes.
astro/packages/astro/src/content/content-layer.ts
Lines 468 to 470 in 45e50e4
| export function getDataStoreFile(settings: AstroSettings, isDev: boolean) { | |
| return new URL(DATA_STORE_FILE, isDev ? settings.dotAstroDir : settings.config.cacheDir); | |
| } |
astro/packages/astro/test/test-utils.ts
Lines 270 to 284 in 45e50e4
| clean: async () => { | |
| await fs.promises.rm(config.outDir, { | |
| maxRetries: 10, | |
| recursive: true, | |
| force: true, | |
| }); | |
| const astroCache = new URL('./node_modules/.astro', config.root); | |
| if (fs.existsSync(astroCache)) { | |
| await fs.promises.rm(astroCache, { | |
| maxRetries: 10, | |
| recursive: true, | |
| force: true, | |
| }); | |
| } | |
| }, |
To fix this, I merged the two test files into one.
An alternative would have been to give each test its own dedicated fixture, but since the two test cases are similar in nature, I went with the merge approach instead.
Testing
Green CI
Docs
N/A
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@astrojs/starlight@0.39.1
Patch Changes
-
#3885
010eed1Thanks @ArmandPhilippot! - Fixes the version mentioned in an error message related to autogenerated sidebar groups support. -
#3887
b3c6990Thanks @delucis! - Adds 13 new icons:clock,desktop,mobile-android,window,database,server,code-branch,notes,question,question-circle,analytics,padlock, andsolidjs.
Description
The Starlight version mentioned in the error related to autogenerated sidebar groups support is wrong:
Support for autogenerated sidebar groups was removed in Starlight v0.38.0.
#3618 has been released earlier in 0.39.0, not 0.38.0.
I added a changeset because we need a new version for such a small change. 😅
Changes
Migrate a js test file to ts.
Testing
CI should pass.
Docs
N/A
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| changesets/action | action | minor | v1.7.0 → v1.8.0 |
Release Notes
changesets/action (changesets/action)
v1.8.0
Minor Changes
- #258
f5dbf72Thanks @tom-sherman! - Support draft version PR modes with a newprDraftinput. Usecreateto create new version PRs as drafts, oralwaysto also convert existing version PRs back to draft when updating them.
Patch Changes
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Changes
A follow-up to #16471 and #16472
svelte2tsx@0.7.55 supports typescript v6, so we can also update the typescript peer dependency range in @astrojs/svelte.
Testing
CI should pass
Docs
A changeset file is added because we need to release a new version for @astrojs/svelte.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@astrojs/cloudflare@13.5.0
Minor Changes
- #16639
4d72482Thanks @ematipico! - The adapter now depends on Astro 6.3.0.
@astrojs/node@10.1.0
Minor Changes
- #16639
4d72482Thanks @ematipico! - The adapter now depends on Astro 6.3.0.
Changes
The compiler was never torn down at the end of the build. This PR fixes it.
Testing
Manually tested.
Docs
N/A
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@astrojs/starlight@0.39.0
Minor Changes
-
#3618
dcf6d09Thanks @HiDeoo! -⚠️ BREAKING CHANGE: This release changes how autogenerated links work in Starlight’s sidebar configuration.If you have sidebar groups using the
autogeneratekey, you must now wrap that configuration in anitemsarray:{ label: 'My group', - autogenerate: { directory: 'some-dir' }, + items: [{ autogenerate: { directory: 'some-dir' } }], }This change unlocks the possibility to mix autogenerated links and other links in a single group, for example:
{ label: 'Mixed group', items: [ 'example-page', { autogenerate: { directory: 'examples' } }, { label: 'More examples', link: 'https://example.com' }, ], }
This release also updates the shape of autogenerated sidebar entries in route data. Autogenerated links and groups in
Astro.locals.starlightRoute.sidebarnow include anautogenerateobject with the configureddirectoryvalue:{ type: 'link', label: 'Example', href: '/examples/example/', isCurrent: false, autogenerate: { directory: 'examples' } }
-
#3618
dcf6d09Thanks @HiDeoo! -⚠️ BREAKING CHANGE: This release changes the default collapsed state of autogenerated sidebar subgroups.Autogenerated subgroups no longer inherit the
collapsedvalue from their parent group. They are now expanded by default unless explicitly configured withautogenerate.collapsed.If your sidebar configuration relies on a collapsed parent group to also collapse its autogenerated subgroups, update your configuration to set
autogenerate.collapsedtotrue:{ label: 'Reference', collapsed: true, items: [ - { autogenerate: { directory: 'reference' } }, + { autogenerate: { directory: 'reference', collapsed: true } }, ], } -
#3845
4d755f5Thanks @delucis! - Adds a<link rel="alternate" hreflang="x-default" href="...">tag pointing to the default locale in multilingual sites. Thex-defaultalternate is used as a signal of which language to fall back to if no other is available. Learn more in Google’s SEO localization docs. -
#3862
ec70630Thanks @itrew! - Makes spacing of items in nested lists more consistent -
#3872
417a66cThanks @tats-u! - Enables the CSS propertytext-autospacein Chinese and Japanese documents.If you would prefer to disable autospacing in Chinese and Japanese pages, you can add the following custom CSS to your site:
[lang]:where(:lang(zh, ja)) { text-autospace: initial; }
-
#3797
9764ebdThanks @delucis! - Avoids the risk of layout shift when users expand and collapse sidebar groupsThis release can introduce additional padding to the site sidebar on certain devices to reserve space for scrollbars. You may wish to inspect your site sidebar visually when upgrading.
If you would prefer to keep the previous styling, you can add the following custom CSS to your site:
.sidebar-pane { scrollbar-gutter: auto; }
-
#3858
6672c35Thanks @delucis! - Updatesi18next, used for Starlight’s localization APIs, from v23 to v26There should not be any user-facing changes from this update
Description
Adds flush() and close() methods to AstroIntegrationLogger, mirroring AstroLogger. Both delegate to the underlying destination's optional flush / close hooks.
AstroLogger already exposes flush() and close() (added in #16404). AstroIntegrationLogger did not, so any code path that placed an AstroIntegrationLogger where an AstroLogger was expected — e.g. App.#prepareResponse calling this.logger.flush() — would crash with TypeError: this.logger.flush is not a function. The two logger classes now have a symmetric public surface.
Closes #16622.
Changes
packages/astro/src/core/logger/core.ts: addflush()andclose()toAstroIntegrationLogger, identical shape to the methods onAstroLogger.packages/astro/test/units/logger/logger.test.ts: coverAstroIntegrationLogger.flush()/.close()(delegation, no-throw, fork inheritance).- changeset:
patch.
Testing
pnpm --filter astro build:ci
pnpm --filter astro exec astro-scripts test test/units/logger/logger.test.ts --strip-types --teardown ./test/units/teardown.ts
# 10 pass, 0 fail
Docs
No public API docs change required — AstroIntegrationLogger.flush() / .close() mirror the existing AstroLogger methods (no behaviour change for destinations that don't define flush/close).
Changes
Fixes #16612
Testing
Should pass! We unfortunately cannot test this in the monorepo because TypeScript is of course always available
Docs
N/A
Changes
This PR improves integration tests:
- remove large-array, as we already use that in our benchmarks
- moves some fixtures at higher level so that they aren't built multiple times
- removes the custom rollup thing because it's already tested elsewhere
Testing
Green CI
Docs
N/A
Changes
Some tests in core-image.test.ts have been flaky in Astro's CI.
https://github.com/withastro/astro/actions/runs/25425893932/job/74579340641?pr=16579
https://github.com/withastro/astro/actions/runs/25407064617/job/74520525804?pr=16614
After investigating, I found that the removeDir function, which is used to clean up the cache directory before tests, was not being awaited despite returning a Promise.
This PR adds the missing await to address the potential race condition.
Testing
Green CI
Docs
N/A
Changes
One of TypeScript's biggest strengths is static analysis. In practice, we rely on it in three places:
- In editors (e.g. vscode / TypeScript language service)
- During local and CI builds via
tsc - During local and CI linting via
eslintwith type-aware linting
All three ultimately depend on TypeScript project configuration through tsconfig.
Before #16241, Astro used three different tsconfig setups for these workflows:
- Editors used an effectively empty root
tsconfig.json, so the language service fell back to its default behavior. tscused per-package configs that only includedsrc/, excluding files likebin/andtest/.eslintused a separatetsconfig.eslint.json.
This inconsistency created a poor developer experience. For example, vscode could report type errors while CI remained green, making editor diagnostics unreliable.
This PR completes the TypeScript project references work introduced in previous PRs. After this PR, all three workflows share the same source of truth: the root tsconfig.json.
The root tsconfig.json now includes all src/, test/, scripts, and configuration files (e.g. prettier.config.mjs). It doesn't include SFC files (.astro, .svelte, etc.) and test fixtures, though.
Also, note that if a JS/TS file is included in eslint but not in the root tsconfig.json, eslint will show the following errors. This is beneficial because it ensures that we have all files added to tsconfig.json, thus these files are covered by tsc.
/astro/packages/astro/e2e/astro-island-hydration-error.test.js
0:0 error Parsing error: /astro/packages/astro/e2e/astro-island-hydration-error.test.js was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject
Testing
CI should pass
Docs
N/A
Changes
Migrate some tests from JS to TS.
These tests come from an old pull request, which was merged into main after the TypeScript migration was completed.
Testing
N/A
Docs
N/A
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| actions/labeler | action | minor | v6.0.1 → v6.1.0 |
Release Notes
actions/labeler (actions/labeler)
v6.1.0
Enhancements
- Add changed-files-labels-limit and max-files-changed configuration options to cap the number of labels added by @bluca in #923
Bug Fixes
- Improve Labeler Action documentation and permission error handling by @chiranjib-swain in #897
- Preserve manually added labels during workflow runs and refine label synchronization logic by @chiranjib-swain in #917
Dependency Updates
- Upgrade brace-expansion from 1.1.11 to 1.1.12 and document breaking changes in v6 by @dependabot in #877
- Upgrade minimatch from 10.0.1 to 10.2.3 by @dependabot in #926
- Upgrade dependencies (@actions/core, @actions/github, js-yaml, minimatch, @typescript-eslint) by @Copilot in #934
New Contributors
- @chiranjib-swain made their first contribution in #897
- @bluca made their first contribution in #923
- @Copilot made their first contribution in #934
Full Changelog: actions/labeler@v6...v6.1.0
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Changes
- Fixes
ModuleLoader.getSSREnvironment()to return thessrEnvironmentparameter the loader was constructed with, instead of always looking upviteServer.environments.ssr. - With adapters that register a separate
prerenderVite dev environment (notably@astrojs/cloudflarewithprerenderEnvironment: 'node'),createViteLoaderbuilds a dedicated loader per environment. Every other method already uses the constructor parameter - onlygetSSREnvironment()ignored it, so the prerender handler's loader returned the unrelatedssrenvironment andgetComponentMetadata()walked the wrong module graph in dev. With Cloudflare's default workerdssr, that env is also non-runnable, making theas RunnableDevEnvironmentcast unsafe. - Removes the now-unused
ASTRO_VITE_ENVIRONMENT_NAMESimport. - Changeset:
astro: patch(.changeset/fix-module-loader-prerender-env.md). - Closes #16436. Follow-up to #16292, which fixed the visible style-into-
<template>symptom inastro:head-metadatabut did not touch this separate correctness bug in the per-environment module loader.
Testing
tsc --noEmitonpackages/astropasses cleanly.- Existing regression test
packages/astro/test/head-propagation-prerender-env.test.jsstill passes (1/1) - confirms no regression on theastro:head-metadatapath that #16292 fixed. - No new test added:
loader.getSSREnvironment()has a single caller (getComponentMetadata), and when only thessrenv existsssrEnvironment === viteServer.environments.ssr, so the new behaviour is a strict superset of the old correct behaviour. Happy to extend thehead-propagation-prerender-envfixture with a prerendered route to exercise the prerender handler's loader directly if maintainers prefer.
Docs
No docs change needed - this is a dev-server-internal correctness fix with no user-facing API or behaviour change. Users on adapters with a separate prerender env (e.g. @astrojs/cloudflare with prerenderEnvironment: 'node') will simply see correct head metadata for prerendered routes in dev.
Changes
The Houston bot for the triage workflow is currently not working.
https://github.com/withastro/astro/actions/runs/25424727887/job/74575139340
The log shows the following:
pnpm exec flue run issue-triage \
--target node \
--session-id "issue-triage-$ISSUE_NUMBER" \
--payload "{\"issueNumber\": $ISSUE_NUMBER}"
Unknown argument: --session-id
Looking into the flue repository, I couldn't find the --session-id argument anywhere.
Instead, I found that --id is defined as the correct argument.
https://github.com/withastro/flue/blob/808b34ee154b94025d5cb2b45288d88b7766ae0f/packages/cli/bin/flue.ts#L95-L108
I also confirmed that commit 8388ed1 in the flue repository changed --session-id to --id.
Based on this, I concluded that --id should be used instead of --session-id, and updated it accordingly.
Testing
I don't know how to do it 😔
Docs
N/A
Changes
Right now, types for entry.data field returned by getLiveEntry() are not working.
---
import { getLiveEntry } from "astro:content";
const { entry } = await getLiveEntry("example", "hello-world");
const data = entry?.data; // type `any` instead of defined type from loader.
---The cause is that the utility type ExtractDataType used here is missing, so entry.data is always of type any.
This pr adds the missing utility type to the file.
Testing
Tested by manually adding the missing utility type to the generated .astro/content.d.ts in my projects and verifing the expected types to work.
Docs
It adds missing typing.
Fixes the smoke test failure caused by astro-expressive-code@0.42.0 being too recently published to pass the 3-day minimumReleaseAge gate. Since this is a Starlight dependency used by our docs smoke test, it should always be installable regardless of release age.
Another PR updating depedencies. Expressive Code is the main motivator as the latest release updates to Shiki v4 (thanks again @ocavue!) so Starlight will share this with Astro instead of having v3 and v4 in the tree.
Prod dependencies
- Updates
astro-expressive-codein Starlight (0.41.7 => 0.42.0)
Dev depdendencies
- Updates
astroacross monorepo (6.1.9 => 6.2.2) - Updates
@astrojs/check(0.9.8 => 0.9.9) - Updates
pnpm(10.33.2 => 10.33.3) - Updates linters
eslint(10.2.1 => 10.3.0)globals(17.5.0 => 17.6.0)typescript-eslint(8.59.0 => 8.59.2)
Haven’t bothered with a changeset as we already have an unreleased “update deps” patch changeset in the pipeline. Should see no user-facing change in any case.
Changes
This PR adds the missing * line from a code sample in JSDoc, which basically removed the whitespace between the imports and the code.
This doesn't need a changeset, AFAIK
Testing
Docs
That's all it is.
Changes
Fixes a regression from #16277 where the generated wrangler.json sets
assets.directory to include the base prefix (e.g. "../client/blog"
instead of "../client").
Cloudflare's asset binding resolves the full request URL path (including
the base) against the directory. With the prefixed path, requests would
resolve to a double-nested location (e.g. client/blog/blog/file.js),
causing 404s for all static assets.
Related: #16276
Testing
Added a test to with-base.test.ts verifying the assets directory in
the generated wrangler.json points to the un-prefixed client root.
Docs
fixes: #16604
Changes
As reported in the issue, the migration guide and deprecation comments recommended using build.assetsPrefix from astro:config/server as a replacement for the deprecated import.meta.env.ASSETS_PREFIX, but astro:config/server was not actually exporting build.assetsPrefix.
astro/packages/astro/client.d.ts
Lines 11 to 15 in 3740b24
| /** | |
| * The prefix for Astro-generated asset links if the build.assetsPrefix config option is set. This can be used to create asset links not handled by Astro. | |
| * @deprecated This will be removed in a future major version of Astro. Use `build.assetsPrefix` from `astro:config/server` instead. | |
| */ | |
| readonly ASSETS_PREFIX: string | Record<string, string>; |
This PR fixes the implementation so that build.assetsPrefix is correctly exported from astro:config/server.
Testing
Added tests to verify that build.assetsPrefix is properly exported and that its value can be read correctly.
Docs
The "Upgrade to Astro v6" page documents this under "Deprecated: import.meta.env.ASSETS_PREFIX" and recommends using build.assetsPrefix instead, but the "Imports from astro:config/server" reference page does not yet list build.assetsPrefix.
A follow-up PR to update the docs will be submitted separately.
Fixes #16491
Context: In Vite 6, top-level vite.optimizeDeps only applies to the client environment. The Cloudflare adapter owns SSR/prerender dep optimization via its configEnvironment hook, so user-configured optimizeDeps.exclude and optimizeDeps.esbuildOptions.loader were silently ignored for server environments.
Changes
- Fix: capture the user's
config.vite.optimizeDepsinastro:config:setupand merge it into the adapter'sconfigEnvironmentreturn value for server environments. Added integration test: (test/custom-loader.test.ts) with a fixture containing a fake package that imports a.datafile, reproduces the esbuild "No loader is configured for .data files" error when the fix is absent.- Extended
test/ssr-deps.test.tswith a dynamically-created fake-data-pkg that imports a .data file, reproduces the esbuild "No loader is configured for .data files" error when the fix is absent.
Testing
- Run
NODE_OPTIONS="--experimental-strip-types --no-warnings"pnpm test in packages/integrations/cloudflare. - If the new test fails locally with a miniflare compatibility date error, that is a pre-existing local env issue (same as
ssr-deps.test.ts) CI is the source of truth
Docs
Fix doesn't introduce any new config options or change any public API. No docs update needed.
Changes
- Moves conflict resolution and changeset cleanup into the
merge-main-to-nextaction so the branch is clean and buildable before CI runs. AI only runs when there are actual conflicts; clean merges skip it entirely. - Strips the
merge-fixaction down to just test fixing — no more conflict stripping, no--no-frozen-lockfile. Uses--frozen-lockfilesince the lockfile is already correct. - Adds critical rules to the
fix-testsskill based on analysis of a 1-hour timeout: never runpnpm install, time-box investigation to 5 min, diff before deep-reading source, batch bash commands.
Testing
- No test changes — these are CI workflow and skill instruction changes.
Docs
- No docs needed — internal CI automation only.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
astro@6.3.0
Minor Changes
-
#16366
d69f858Thanks @matthewp! - Adds a newexperimental.advancedRoutingoption that lets you take full control of Astro's request handling pipeline by creating asrc/app.tsfile in your project.Today, Astro handles every incoming request through a fixed internal pipeline: trailing slash normalization, redirects, actions, middleware, page rendering, i18n, and so on. That pipeline works great for most sites, but as projects grow you often want to run your own logic between those steps — an auth check before rendering, a rate limiter before actions, custom logging around the whole stack. Advanced routing gives you that control.
When enabled, Astro looks for a
src/app.tsfile in your project. If it finds one, that file becomes the entrypoint for all server-rendered requests. You compose the pipeline yourself using the handlers Astro provides, and you can slot your own logic anywhere in the chain.Enabling advanced routing
// astro.config.mjs import { defineConfig } from 'astro/config'; export default defineConfig({ experimental: { advancedRouting: true, }, });
Two ways to build your pipeline
Astro ships two entrypoints for advanced routing:
astro/fetchandastro/hono.astro/fetchis a low-level, framework-free API built on the Web Fetch standard. You create aFetchStatefrom the incoming request, then call handler functions in sequence. Each handler takes the state, does its work, and returns aResponse(orundefinedto pass through). This is the core primitive that everything else is built on:// src/app.ts import { FetchState, trailingSlash, redirects, actions, middleware, pages, i18n, } from 'astro/fetch'; export default { async fetch(request: Request) { const state = new FetchState(request); // Early exits — these return a Response only when they apply. const slash = trailingSlash(state); if (slash) return slash; const redirect = redirects(state); if (redirect) return redirect; const action = await actions(state); if (action) return action; // Middleware wraps page rendering; i18n post-processes the response. const response = await middleware(state, () => pages(state)); return i18n(state, response); }, };
astro/honowraps the same handlers as Hono middleware, so you can mix Astro's pipeline with Hono's ecosystem of middleware (logger, CORS, JWT, rate limiting, etc.) using theapp.use()pattern you already know:// src/app.ts import { Hono } from 'hono'; import { getCookie } from 'hono/cookie'; import { logger } from 'hono/logger'; import { actions, middleware, pages, i18n } from 'astro/hono'; const app = new Hono(); app.use(logger()); // Auth gate — only runs for /dashboard routes. app.use('/dashboard/*', async (c, next) => { const session = getCookie(c, 'session'); if (!session) return c.redirect('/login'); return next(); }); app.use(actions()); app.use(middleware()); app.use(pages()); app.use(i18n()); export default app;
Both approaches give you the same power — pick whichever fits your project. If you don't need a framework,
astro/fetchkeeps things minimal. If you want a rich middleware ecosystem,astro/honogets you there with one import.For more information on enabling and using this feature in your project, see the experimental advanced routing docs. To give feedback, or to keep up with its development, see the advanced routing RFC for more information and discussion.
-
#16366
d69f858Thanks @matthewp! - Adds aconsume()instance method toAstroCookies. This method marks the cookies as consumed and returns theSet-Cookieheader values. After consumption, any subsequentset()calls will log a warning, since the headers have already been sent.Previously this was only available as a static method
AstroCookies.consume(cookies). The static method is now deprecated but kept for backward compatibility with existing adapters. -
#16412
ba2d2e3Thanks @0xbejaxer! - Add retry and error event handling forastro-islandhydration import failures to reduce unrecoverable hydration errors on transient network failures. -
#16582
885cd31Thanks @Princesseuh! - Adds a newimage.dangerouslyProcessSVGflag to optionally enable processing SVG inputs. For security reasons, Astro will no longer rasterizes SVG image sources by default in its default image service and endpoint.Set
image.dangerouslyProcessSVG: trueto opt back into processing SVG inputs.// astro.config.mjs import { defineConfig } from 'astro/config'; export default defineConfig({ // ... image: { dangerouslyProcessSVG: true, }, });
Note that this is a breaking change for users who were previously relying on Astro's default image service to rasterize SVG inputs, but it is a necessary change to improve security and prevent potential vulnerabilities.
-
#16519
1b1c218Thanks @louisescher! - Adds support for redirecting URLs in remote image optimization.Previously, when a remote image URL meant to be optimized by Astro led to a redirect, Astro would fail silently and ignore the redirect. Now, Astro tracks up to 10 redirects for these images. If any of the redirects are not covered by a pattern in
image.remotePatternsor a domain inimage.domains, Astro will fail with a helpful error message.In the following example, the first image would be loaded successfully, while the second would lead to Astro throwing an error:
export default defineConfig({ image: { domains: ['example.com', 'cdn.example.com'], }, });
{ /* Redirects to https://cdn.example.com/assets/image.png: */ } <Image src="https://example.com/assets/image.png" width="1920" height="1080" alt="An example image." />; { /* Redirects to https://malicious.com/image.png: */ } <Image src="https://example.com/bad-image.png" width="1920" height="1080" alt="An example image." />;
In cases where all redirects to HTTPS hosts should be trusted, the following configuration for
image.remotePatternscan be used:export default defineConfig({ image: { remotePatterns: [ { protocol: 'https', }, ], }, });
Patch Changes
-
#16592
9c6efc5Thanks @matthewp! - Escapes interpolated values in the dev server redirect HTML template, consistent with how the 404 template already handles them -
#16585
78f305eThanks @web-dev0521! - Fixesz.array(z.boolean())in form actions incorrectly coercing the string"false"totrue. Boolean array elements now use the same'true'/'false'string comparison as singlez.boolean()fields, so submitting["false", "true", "false"]correctly parses as[false, true, false]. -
#16567
12a03f2Thanks @matthewp! - Fixes deleted content collection entries persisting ingetCollection()results during dev -
#16595
ce9b25cThanks @web-dev0521! - FixespushDirectivein the CSP runtime duplicating the new directive once per existing non-matching directive. CallinginsertDirective()(or otherwise pushing a directive whose name is not yet in the list) now appends it exactly once, and a directive that merges with a later existing entry no longer leaves an unmerged copy behind. -
#16600
94e4b7cThanks @web-dev0521! - FixesAstro.preferredLocalereturning the wrong value wheni18n.localesmixes object-form entries ({ path, codes }) with string entries that normalize to the same locale. The first matching code in the configuredlocalesorder is now selected, matching the documented behavior. -
#16591
cce20f7Thanks @matthewp! - Uses a consistent generic error message in the image endpoint across all adapters -
#16629
f54be80Thanks @g-taki! - Fixes a bug where SSR responses inastro devcould crash withTypeError: this.logger.flush is not a function. -
#16589
3740b24Thanks @ArmandPhilippot! - Fixes an outdated code snippet in the documentation for session storage configuration. -
Updated dependencies [
354e231]:- @astrojs/telemetry@3.3.2
@astrojs/cloudflare@13.4.0
Minor Changes
-
#16519
1b1c218Thanks @louisescher! - Adds support for redirecting URLs in remote image optimization.Previously, when a remote image URL meant to be optimized by Astro led to a redirect, Astro would fail silently and ignore the redirect. Now, Astro tracks up to 10 redirects for these images. If any of the redirects are not covered by a pattern in
image.remotePatternsor a domain inimage.domains, Astro will fail with a helpful error message.In the following example, the first image would be loaded successfully, while the second would lead to Astro throwing an error:
export default defineConfig({ image: { domains: ['example.com', 'cdn.example.com'], }, });
{ /* Redirects to https://cdn.example.com/assets/image.png: */ } <Image src="https://example.com/assets/image.png" width="1920" height="1080" alt="An example image." />; { /* Redirects to https://malicious.com/image.png: */ } <Image src="https://example.com/bad-image.png" width="1920" height="1080" alt="An example image." />;
In cases where all redirects to HTTPS hosts should be trusted, the following configuration for
image.remotePatternscan be used:export default defineConfig({ image: { remotePatterns: [ { protocol: 'https', }, ], }, });
Patch Changes
- Updated dependencies []:
- @astrojs/underscore-redirects@1.0.3
@astrojs/language-server@2.16.8
Patch Changes
- #16627
5778cb7Thanks @Princesseuh! - Fixes unintended dependency on thetypescriptpackage being available to the language server
@astrojs/telemetry@3.3.2
Patch Changes
- #16260
354e231Thanks @gameroman! - Refactors internal config logic to remove thedlvdependency in favor of native logic
Changes
- Fix
pushDirectivein the CSP runtime duplicating the new directive once per existing non-matching directive. - Root cause: the
elsebranch inside the loop pushednewDirectiveon every non-match instead of once after the loop. Replaced thededuplicatedflag with amatchedflag and moved thenewDirectivepush outside the loop, guarded by!matched. - Closes #16594.
Before: pushDirective(["img-src 'self'", "style-src 'self'"], "script-src 'self'") → ["img-src 'self'", "script-src 'self'", "style-src 'self'", "script-src 'self'"] (duplicated + interleaved)
After: → ["img-src 'self'", "style-src 'self'", "script-src 'self'"]
Also fixes Mode 2: a directive that merges with a later existing entry no longer leaves an unmerged copy in front of the merged result.
Don't forget a changeset! Run
pnpm changeset. — Added:.changeset/fix-csp-push-directive-duplicate.md
Testing
- Added a
regression: issue #16594suite inpackages/astro/test/units/csp/runtime.test.tscovering:- Mode 1 — non-matching new directive appended exactly once.
- Mode 2 — merge with a later match, no unmerged copy left behind.
- Empty input list edge case.
- Many non-matches: directive-name uniqueness asserted via a
Mapcount. - Table-driven sweep across "non-match", "match in middle", "match at end" shapes, asserting both length and
Set-based name uniqueness.
- All 11 tests in
csp/runtime.test.tspass locally (node --test test/units/csp/runtime.test.ts).
Docs
No docs change required — pushDirective is internal CSP runtime; public insertDirective() semantics are unchanged (this restores them to the documented behavior).
Changes
This PR does an overhaul of all error messages to make them more consistent with each other, namely:
- fixing missing full stop at the end of messages.
- adding missing backticks to code excerpts.
- adding () to differentiate functions from simple properties/objects.
- improving error titles/messages with more context.
Testing
I am not sure if adding backticks in a few places could break rendering, we'll be sure once we get the automated PR on docs.
Docs
That's all it is.
Changes
- The redirect template in
3xx.tsinterpolated URL values directly into HTML without escaping. The sibling4xx.tstemplate already escapes its values usinghtml-escaper. This applies the same pattern to the redirect template for consistency.
Testing
- No new tests. The change adds
escape()calls around existing interpolations — the same approach4xx.tsalready uses.
Docs
- No docs update needed.
Changes
- The Node image endpoint (
node.ts) returns a generic'Internal Server Error'string on failure, but the generic adapter endpoint (generic.ts) was interpolating the error object into the response. This alignsgeneric.tswithnode.tsso all adapters use the same consistent error response.
Testing
- No new tests. Single-line string replacement in an error path — existing image endpoint tests cover the surrounding behavior.
Docs
- No docs update needed.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| withastro/automation | action | digest | e27ec6d → c8a2e8b |
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Description
- Closes #3871
Especially in Chinese and Japanese, the CSS property text-autospace: normal is preferred for better looking like books by automatically inserting spaces with the moderate width.
- https://developer.chrome.com/blog/css-i18n-features#inter-script_spacing_text-autospace
- https://developer.mozilla.org/docs/Web/CSS/text-autospace
- https://webkit.org/blog/16574/webkit-features-in-safari-18-4/#:~:text=Text%20auto%20space
- vuejs/vitepress#4996
- web-infra-dev/rspress#2920
- https://github.com/web-infra-dev/rspress/releases/tag/v2.0.0-rc.3
Without this, some users and AIs try to insert such spaces manually as workarounds but they are too wide and prevent keywords from searched.
Japanese:
Before:
After:
Note: the current Chinese content of Starlight translation is not affected because it took the workarounds like manually inserting spaces.
The other languages are not affected.
Changes
- During
astro build,createTempViteServer(inpackages/astro/src/core/sync/index.ts) creates a Vite dev server purely to resolve virtual modules for type generation. The server never handles HTTP requests, but every plugin'sconfigureServerhook still fires — including adapter plugins that start heavy runtimes (notably@astrojs/cloudflare→@cloudflare/vite-plugin→ miniflare/workerd, ~1–3.5 s of overhead). - Additionally, top-level
optimizeDeps: { noDiscovery: true }only applies to theclientenvironment under Vite 7's Environment API, so adapters'configEnvironmenthooks re-injected theiroptimizeDeps.includelists intossr/astro/prerenderand forced unnecessary pre-bundling. - Two changes in
createTempViteServer:- Per-environment
optimizeDeps: { noDiscovery: true, include: [] }for all four environments. - Inline
astro:sync:strip-server-hooksplugin that removesconfigureServerfrom non-astro:plugins inconfigResolved.
- Per-environment
- Net effect on a representative project:
Types Generateddrops from ~3.6 s to ~125 ms; total build from ~13 s to ~9.2 s. - Closes #16332.
- Changeset added (
patchforastro).
This is the fix astrobot-houston suggested on the issue, applied verbatim.
Testing
pnpm exec astro-scripts test test/astro-sync.test.ts --strip-types— all 9 existing sync tests pass.- Verified end-to-end against the MRE from #16332 (https://github.com/adamchal/astro-sync-perf): with
@astrojs/cloudflareon the adapter,Types Generateddrops from ~2.3 s to ~100 ms and miniflare no longer starts during sync. - I have been running this same patch (applied as a
postinstallmonkey-patch) on a production Astro/Cloudflare site for the past few weeks with no regressions in dev or build.
Docs
No user-facing API changes — this is a build-time perf fix. Adapter authors who relied on configureServer firing during sync would notice, but that path was unintentional and has no documented contract.
Changes
- Fix
z.array(z.boolean())in form actions incorrectly coercing the string"false"totrue. handleFormDataGetAllwas usingentries.map(Boolean), butFormDatavalues are always strings andBoolean("false") === true. Replaced with the same'true'/'false'comparison already used for singlez.boolean()fields inhandleFormDataGet.- Closes #16584.
Before: ["false", "true", "false"] → [true, true, true]
After: ["false", "true", "false"] → [false, true, false]
Testing
- Added a new
boolean arrayssuite inpackages/astro/test/units/actions/form-data-to-object.test.ts:should preserve "false" string values in boolean arrays— direct regression test for the reported case.should coerce mixed boolean array values correctly— table-driven coverage across all-true / all-false / mixed inputs.
- All 25 tests in
formDataToObjectpass locally (node --test test/units/actions/form-data-to-object.test.ts).
Docs
No docs change required — this is a behavior fix bringing array-element parsing in line with the already-documented single-boolean coercion behavior.
fixes #13297
Changes
In the current implementation, enabling prefetch settings causes links to be collected via document.getElementsByTagName('a') on the initial page load.
While this approach works for most cases, it fails to cover certain scenarios, such as the one reported in issue #13297.
The root cause of issue #13297 is that when a component with the server:defer attribute uses top-level await, its links are rendered after the Promise resolves, meaning they are not captured by the initial document.getElementsByTagName('a') call.
More broadly, the same issue occurs whenever links are dynamically added to the DOM after the initial page load.
To address this, this pull request adds the observeDynamicLinks option, which uses a MutationObserver to watch for dynamically added link elements.
This ensures that links added after the initial render are properly captured.
This feature is opt-in.
Why MutationObserver?
Investigation process
- First, I checked the code around
server:deferto see if it used any event notifications. - It did not, but I found that the prefetch code [uses the
astro:page-loadevent].
astro/packages/astro/src/prefetch/index.ts
Line 316 in 7711e47
document.addEventListener('astro:page-load', () => { - When I looked into whether
astro:page-loadcould be used here, I found that the constant defining this event name is marked as@deprecated, so I decided to look for a different approach.
astro/packages/astro/src/transitions/events.ts
Lines 12 to 13 in 7711e47
/** @deprecated This will be removed in Astro 7 */ export const TRANSITION_PAGE_LOAD = 'astro:page-load'; - I also considered that the root cause of issue #13297 is not specific to
server:defer, the same problem can occur in other situations where links are added to the DOM dynamically. For this reason, I wanted an approach that resolves the issue within the prefetch code itself, rather than adding prefetch-dependent logic to theserver:deferside. - As a result, I concluded that
MutationObserveris the best approach, as it is well-suited for observing DOM changes and allows the fix to be fully self-contained within the prefetch implementation.
Testing
Added tests to verify that the observeDynamicLinks option works correctly in the following scenarios:
- Links dynamically added via
server:defer, as reported in issue #13297 - Links dynamically added by a toggle component (
ToggleButton.jsx)
Docs
/cc @withastro/maintainers-docs for feedback!
Changes
SVGs are always troublesome in the end and wanting to actually process them is so uncommon that it's best to leave it to the user.
I decided to put it on a global image option so that other services could also use it, but I'd be okay with it being a service config as well, it's ok
Testing
Added a test
Docs
WIP
This PR contains the following updates:
| Package | Change | Age | Confidence |
|---|---|---|---|
| @markdoc/markdoc (source) | ^0.5.4 → ^0.5.7 |
||
| devalue | ^5.6.3 → ^5.8.0 |
||
| preact (source) | ^10.28.4 → ^10.29.1 |
||
| vite (source) | ^7.3.2 → ^7.3.3 |
Release Notes
markdoc/markdoc (@markdoc/markdoc)
v0.5.7
What's Changed
- Make table-syntax validation error location more precise by @yue-stripe in #605
- the
validate()method now returns more location/line info than before - Build output now includes ES2022 syntax. The esbuild version used to build markdoc was upgraded from 0.13 to 0.25, which raises the effective output target from ~ES2020 to ES2022+. If your build pipeline transpiles @markdoc/markdoc, you may need to ensure your toolchain supports ES2022 features such as static class blocks.
- the
Full Changelog: markdoc/markdoc@0.5.6...0.5.7
v0.5.6
What's Changed
- Add validation for invalid Markdoc table syntax by @yue-stripe in #603
Markdoc.validatewill now raise errors for invalid syntax within{% table %}tags.
Full Changelog: markdoc/markdoc@0.5.5...0.5.6
v0.5.5
Added support for providing a list of conditional tags in addition to if. (['if'] is set by default). No breaking changes.
sveltejs/devalue (devalue)
v5.8.0
Minor Changes
c5115b0: feat: addstringifyAsyncfor async serialization
v5.7.1
Patch Changes
8becc7c: fix: handle regexes consistently in uneval's value and reference formats
v5.7.0
Minor Changes
df2e284: feat: use native alternatives to encode/decode base64498656e: feat: addDataViewsupporta210130: feat: whitelistFloat16Arraydf2e284: feat: simplify TypedArray slices
Patch Changes
preactjs/preact (preact)
v10.29.1
Fixes
- Create a unique event-clock for each Preact instance on a page. (#5068, thanks @JoviDeCroock)
- Fix incorrect DOM order with conditional ContextProvider and inner keys (#5067, thanks @JoviDeCroock)
Maintenance
- fix: Remove postinstall script for playwright setup (#5063, thanks @rschristian)
- chore: speed up tests by using playwright instead of webdriverio (#5060, thanks @marvinhagemeister)
- chore: migrate remaining .js -> .jsx files (#5059, thanks @marvinhagemeister)
- chore: rename
.test.js->.test.jsxwhen JSX is used (#5058, thanks @marvinhagemeister) - Migrate from biome to oxfmt (#5033, thanks @JoviDeCroock)
Configuration
📅 Schedule: (UTC)
- Branch creation
- Between 12:00 AM and 03:59 AM, only on Monday (
* 0-3 * * 1)
- Between 12:00 AM and 03:59 AM, only on Monday (
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Changes
Minor adjustment to @astrojs/mdx so smartypants object options are passed to remark-smartypants, matching Astro's markdown behavior since v6.1.
Testing
Added a regression test that verifies smartypants object options (e.g. dashes: "oldschool") are applied in MDX. The test fails without this change (e.g., for dashes: "oldschool" an em-dash is expected while the default configuration produces an en-dash) and passes with it.
Changes
Close #16273
Fixed issue where custome elements in MDX bypass the renderer pipline. By detecting hyphens in tag names within the JSX runtime, custom elements are now correctly routed to the renderer for SSR, ensuring parity with .astro files.
Testing
Before fixing bug
Test result after implement all test.
After fixing bug
Docs
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| pnpm/action-setup | action | patch | v6.0.4 → v6.0.5 |
Release Notes
pnpm/action-setup (pnpm/action-setup)
v6.0.5
What's Changed
Full Changelog: pnpm/action-setup@v6.0.4...v6.0.5
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Changes
Fixes prerendered pages returning 404 when build.format: 'file' is set in the Node standalone adapter.
When build.format is 'file', pages are emitted as flat .html files (about.html) instead of about/index.html. The send library's extensions option tells it to try appending .html before giving up, so a request to /about resolves correctly on disk instead of falling through to the SSR handler as a 404.
One-line change in serve-static.ts — app.manifest.buildFormat is already available in scope (same as app.manifest.trailingSlash used just above).
Testing
Added two cases to the existing prerender.test.ts under a new "build.format: 'file'" describe block:
- prerendered page is served correctly via its clean URL
- SSR route still works alongside it
Docs
No user-facing behavior change beyond bug fix — no docs update needed.
Fixes #16570.
Changes
Fixes the Vite build warning reported in #15957:
[WARN] Plugin vite:reporter: Unused imports from "@astrojs/internal-helpers/remote":
– "matchHostname", "matchPathname", "matchPort", "matchProtocol"
plugin-internals.ts sets noExternal: ['astro'] for the prerender environment, which bundles astro but leaves @astrojs/internal-helpers as an external. Rollup then sees that those four helpers are imported by the bundled astro code but not consumed inside the bundle boundary, which triggers the unused-import warning.
Adding @astrojs/internal-helpers to noExternal alongside astro co-bundles it in the prerender environment, eliminating the warning. This is a first-party workspace package and the same pattern is already used elsewhere.
Testing
The warning is emitted by Vite/Rollup during the build phase and is not directly observable in the integration test suite without capturing stderr. Verified manually by running a project build with output: 'hybrid' before and after the change — the warning disappears with the fix applied.
Docs
No user-facing behavior change — no docs update needed.
Fixes #15957.
Fixes #16692
Changes
- Automatically injects
Cache-Control: public, max-age=31536000, immutablefor hashed Astro assets (/_astro/*) into Cloudflare's_headersfile at build time, so browsers cache assets across deploys without extra user configuration. - Skips injection when
build.assetsPrefixis set (assets are served from a different origin). - Skips injection when the user's existing
_headersalready has aCache-Controlrule whose URL pattern matches the assets path — Cloudflare merges duplicate matching rules' headers with a comma, which would produce contradictory cache directives. - Respects
baseconfig: the injected pattern is prefixed accordingly (e.g./blog/_astro/*). - Injection block is prepended before any existing user-defined headers so rule ordering in the file is predictable.
- Atomic
_headerswrite (write to.tmp, then rename) to avoid leaving the file truncated on a mid-write crash.
Testing
- Added unit tests for the new
headersFileHasCacheControlForPathutility inpackages/integrations/cloudflare/test/headers.test.ts, covering: empty files, exact patterns, global splat (/*),! Cache-Controldetach, non-matching rules, placeholder patterns (:name), host-prefixed patterns, comments/blank lines, base-prefixed paths, and case-insensitivity. - Extended the existing
with-baseintegration test to assert the injected block is present with the correct base prefix, that user-defined headers are preserved, and that the injected block is placed before user headers.
Docs
No documentation changes needed — this is an internal build-time improvement for Cloudflare deployments. The behavior is automatic and the skip conditions (logged via logger.info) are observable in build output. No new configuration options were added.
Other Info
Live preview of the fix: https://with-fix-astro-assets-cf-repro.ma2153.workers.dev/
Changes
- Invalidates the
astro:data-layer-contentvirtual module in the SSR module runner'sevaluatedModulescache when the data store changes, not just in the server-side module graph. The existinginvalidateModulecall only clears the server'stransformResult, but a subsequenttransformRequest(triggered during module resolution) re-populates it before the runner'sfetchModulecheck, causing a false cache hit that returns stale data.
Closes #16561
Testing
- No new tests added. Manually verified with a content collection setup: deleting entries correctly updates
getCollection()results on the next page load in dev.
Docs
- No docs update needed; this is a bug fix with no API change.
Changes
- Fixes #16564 —
data-astro-prefetch="tap"silently failing when the user clicks a nested child element (e.g.<span>,<img>,<svg>, Astro<Image />) inside an anchor e.targetontouchstart/mousedownis the deepest element under the pointer, not necessarily the<a>— useclosest('a')to walk up to the anchor before checking the strategy- Particularly impactful on slow connections / data-saver mode, where all strategies collapse to
tap— meaning zero prefetching was happening for any link with nested content for the users who would benefit most
Testing
- Added e2e fixture link
<a data-astro-prefetch="tap"><span>tap nested</span></a>and a corresponding page - Added e2e test in
prefetch.test.tsthat clicks the inner<span>and asserts the prefetch request fires
Docs
No docs change needed — this is a bug fix restoring already-documented behavior, not a new feature or API change.
Closes #16563
Summary
Calling session.delete(key) as the first mutation in a request (no prior get, set, has, keys, etc.) did not write back to session storage. The session stayed dirty in memory, but [PERSIST_SYMBOL]() skipped the save path because #data was still undefined, so the backing store kept the old value and the next request could still read the “deleted” key.
Root cause
set()initializes the in-memory map withthis.#data ??= new Map();delete()only didthis.#data?.delete(key), so#datacould remainundefined.- Persistence gated saves on
if (this.#dirty && this.#data), so delete-only flows never calledsetItemand#toDeletewas never applied to storage.
Fix
- Initialize the map in
delete()the same way as inset():this.#data ??= new Map()before removing the key, so the persist path runs and#ensureData()can merge, apply deletions, and serialize to the driver.
Testing
- Added a unit test that pre-seeds backing storage, calls only
delete()then persist, and asserts a newAstroSessionwith the same storage/session id no longer returns the deleted key. - Adjusted an existing persistence test so the follow-up session’s storage
getreturns parsed JSON (matching real unstorage behavior) aftersetItemwrites a devalue JSON string.
Docs
No documentation change required; behavior now matches what the session API already promises.
Contributor checklist (see CONTRIBUTING.md)
-
pnpm exec changesetfor theastropackage (user-facing bugfix) - Tests:
pnpm -C packages/astro exec astro-scripts test "test/units/sessions/astro-session.test.ts"
Fixes #16553
Changes
- Fixes non-prerendered routes (e.g. SSR endpoints using
cloudflare:workers) failing when a dynamic prerendered route like[page].astroexists in the same project withprerenderEnvironment: 'node'. - The dev prerender middleware was using
matchAllRoutes(), which returns every route matching a pathname. A request to/ssrwould match bothssr.astro(non-prerendered) and[page].astro(prerendered), and since one match was prerendered, the request was incorrectly routed to the Node handler. Replaced withmatchRoute(), which returns only the highest-priority match.
Testing
- Added a dynamic prerendered route (
[page].astrowithgetStaticPaths) to the existingprerender-node-envfixture. The existing "renders SSR page through workerd" test now exercises this scenario.
Docs
- No docs needed.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| withastro/automation | action | digest | 497c926 → e27ec6d |
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
Changes
- Updates
./test-utils.jsimports to./test-utils.tsin three.test.jsfiles that were missed when #16492 renamedtest-utils.js→test-utils.ts.
Testing
- No new tests. This fixes the existing
test:integration:jssuite which fails immediately withERR_MODULE_NOT_FOUND.
Docs
- No docs needed — test-only change.
Changes
Simplify db's tsconfig by removing tsconfig.virtual.json.
Its only purpose is to prevent incorrect auto-imports from editor hints and doesn’t affect the runtime output, so it should be relatively safe to remove it.
This file makes it tricky for the TypeScript project references refactor.
Testing
Green CI
Docs
N/A
Changes
- Double-encoded URL paths like
/api/%2561dmin/userscould bypass middleware auth checks because the normalization fallback leftctx.url.pathnamehalf-decoded. - Adds
MultiLevelEncodingErroras a distinct error type so callers can distinguish it from generic decode failures. #createNormalizedUrlnow re-throwsMultiLevelEncodingErrorinstead of falling back. Other decode errors (truly malformed URLs) still fall back gracefully.BaseApp.render()catchesMultiLevelEncodingErrorand returns400 Bad Request.
Testing
- Added
test/units/app/double-encoding-bypass.test.ts— 7 tests using a catch-all/api/[...path]endpoint behind auth middleware. Covers the direct path (401), single-encoded (401), double-encoded (400), multiple encoded segments (400), public routes (200), and non-protected API routes (200).
Docs
- No docs update needed — no user-facing API changes.
Changes
- Fixes a bug in
@astrojs/cloudflarewhere custom KV namespace bindings (e.g.MY_KV,CACHE) were silently removed when Astro's session functionality was enabled. - The root cause was in
packages/integrations/cloudflare/src/wrangler.ts: when injecting theSESSIONKV binding, the code returned a fresh single-item array instead of merging with the user's existingkv_namespaces. - Extracted a
withSessionKVBindinghelper that copies existing namespaces and appends the SESSION binding, preserving all user-defined bindings. - Fix applies to both the top-level config and the
previewsconfig (same code path). - Updated the existing test that was asserting the broken behavior, and added a new test covering the
previewscase.
Closes #16554
Testing
- Updated
packages/integrations/cloudflare/test/wrangler.test.ts: fixed the assertion in "adds SESSION binding when other KV bindings exist but not the session one" to verifyOTHER_KVis preserved alongsideSESSION. - Added a new test "preserves existing previews KV bindings when adding SESSION binding" covering the same scenario in the
previewsconfig. - No integration test needed: the bug and fix are fully exercised by the unit test suite for
cloudflareConfigCustomizer.
Docs
No docs change needed. This is a bug fix restoring behavior that users already rely on — their existing kv_namespaces entries in wrangler.toml now survive when sessions are enabled.
Changes
- Fixes a regex bug where
returnwas incorrectly rewritten inside string literals, template literals, and comments during esbuild's dep-scan / frontmatter error-check phase - Replaces the two-pass
\breturn\bregex with a single-pass state-machine regex that skips over all string/comment tokens before rewriting barereturnstatements - Fixes the primary report case:
gen.return(value)→ was producinggen.throw (value)(syntax error); now preserved correctly - Extracts a shared
replaceTopLevelReturns()helper intoutils.tsto deduplicate logic between the cloudflare esbuild plugin andcompile.ts
Affected files:
packages/integrations/cloudflare/src/esbuild-plugin-astro-frontmatter.tspackages/astro/src/vite-plugin-astro/utils.tspackages/astro/src/vite-plugin-astro/compile.ts
Closes #16551
Testing
The fix is a pure regex change with no new dependencies. Verified manually against the cases from the issue:
| Input | Before | After |
|---|---|---|
gen.return(val) |
gen.throw (val) ← syntax error |
unchanged ✓ |
"return value" |
"throw value" |
unchanged ✓ |
// return foo |
// throw foo |
unchanged ✓ |
`return ${x}` |
`throw ${x}` |
unchanged ✓ |
return foo |
return foo |
throw foo ✓ |
return; |
return; |
throw 0; ✓ |
No new tests added — the two affected code paths are internal-only (esbuild dep scan and compile-error enhancement), exercised by the existing integration test suite.
Docs
No user-facing behavior change — this only affects internal dep scanning and error reporting during compilation. No docs update needed.
Changes
fixes #16524.
with vite.css.transformer: 'lightningcss', scoped styles using a nested & selector inside :where(...) silently produce css where the scope attribute lands on the matched child instead of the intended parent. tailwind v4's space-x-*, space-y-*, and divide-* all expand to this shape and tailwind v4 ships with lightningcss in the loop, so any astro 6 + tailwind v4 project using these utilities in scoped <style> blocks targeting children from another component gets broken spacing in production while the css rule still looks present in the bundle.
what's happening: lightningcss runs inside vite's preprocessCSS() and lowers nesting before @astrojs/compiler injects scope attributes. by the time the compiler sees the css, .parent is buried inside :where(...) and isn't a top-level compound anymore, so the injector prepends [data-astro-cid-X] as a new leading compound — which constrains the wrong element.
fix in packages/astro/src/core/compile/style.ts: when the user has css.transformer: 'lightningcss', call preprocessCSS() with a shallow-cloned vite config whose css.lightningcss.exclude ORs in Features.Nesting, so lightningcss skips its nesting-lowering pass for this preprocess call. vite's final pipeline still lowers nesting for the bundle so output stays compatible with the user's targets. clone is non-mutating per call so it's safe under parallel .astro compiles. lightningcss is resolved from the user's project root via createRequire — same instance vite uses, no new dep added to packages/astro. falls back to the original config if lightningcss can't be resolved.
i think a better fix would live in @astrojs/compiler itself, teach the scope injector to recognize :where(<simple-compound>, ...) as a leading-compound wrapper and attach the cid to the inner compound instead of prepending a new one. similar in spirit to withastro/compiler#1153 but extended to cover the :where(...) case, and it'd cover any future css transform that produces a similarly shaped selector not just lightningcss.
Testing
added a regression fixture under packages/astro/test/:
lightningcss-scoped-nesting.test.ts— compiles the reporter's exact shape withvite.css.transformer: 'lightningcss'and asserts the scope id binds to.parent, not as a leading compound on the matched child.- fixture under
packages/astro/test/fixtures/lightningcss-scoped-nesting/— single page using the reporter's:where(& > :not(:last-child))selector inside a scoped<style>block.
reporter's reproduction repo: https://github.com/rklos/astro-css-bug-repro
Docs
no docs changes. silent regression in css output; behavior after the fix matches what the docs already describe for scoped styles + nested selectors.
Changes
upgrade/package.json has "build": "astro-scripts build \"src/index.ts\" --bundle && tsc". The output dist/index.js should be emitted by astro-scripts, while tsc should only emit .d.ts files.
In #16493, tsc emits both .js and .d.ts files, which overrides the bundled dist/index.js file that was just emitted by astro-scripts.
This PR updates upgrade/tsconfig.json to ensure that tsc won't emit any .js files.
Testing
Before this PR:
$ pnpm -w build
$ tree ./packages/upgrade/dist/
./packages/upgrade/dist/
├── actions
│ ├── context.d.ts
│ ├── context.js
│ ├── help.d.ts
│ ├── help.js
│ ├── install.d.ts
│ ├── install.js
│ ├── verify.d.ts
│ └── verify.js
├── index.d.ts
├── index.js
├── messages.d.ts
├── messages.js
├── shell.d.ts
└── shell.js
2 directories, 14 filesAfter this PR:
$ pnpm -w build
$ tree ./packages/upgrade/dist/
./packages/upgrade/dist/
├── actions
│ ├── context.d.ts
│ ├── help.d.ts
│ ├── install.d.ts
│ └── verify.d.ts
├── index.d.ts
├── index.js
├── messages.d.ts
└── shell.d.ts
2 directories, 8 filesAfter this PR, only one bundled dist/index.js file is emitted in dist/.
Docs
The previous and afterward outputs can both work, so there is no behavior change. No docs are needed.
Last fetched: | Scheduled refresh: Every Saturday
See Customizing GitHub Activity Pages to configure your own
Inspired by prs.atinux.com