Astro
Upgrade to Astro v6
Upgrade to Astro v6
z.string().min(5, { error: "Too short." });
Also, if you use [`.default()` with transforms](https://zod.dev/v4/changelog#default-updates), you may need to update your schemas. In Zod 4, default values must match the output type (after transforms), not the input type. The default value short-circuits parsing when the input is `undefined`:
```ts title="src/content.config.ts" del={5-6} ins={7-8}
const blog = defineCollection({
schema: z.object({
// Zod 3: default matched input type (string)
views: z.string().transform(Number).default("0"),
// Zod 4: default must match output type (number)
views: z.string().transform(Number).default(0),
})
});
For the old behavior where defaults are parsed, use the new .prefault() method.
These are only some of the many changes upgrading from Zod 3 to Zod 4. If you encounter any issues with your Zod schemas after upgrading to Astro 6, please consult the Zod 4 changelog for complete upgrade guidance.
Additionally, a community codemod, which can potentially automate some of these changes when migrating from Zod 3 to Zod 4, is also available.
You can ensure you're the same version of Zod that Astro uses internally by importing Zod from astro/zod.
astro/zod module.
Shiki 4.0
Astro v6.0 upgrades to Shiki v4.0 for syntax highlighting.
What should I do?
If you are using Shiki-specific APIs, check the Shiki migration guide for their breaking changes and upgrade your project as needed.
Official Astro integrations
All of Astro's official server adapters have also updated to a new major version to accompany the upgrade to Vite v7.0 with Vite's Environment API as the development server and production bundler.
In particular, Astro's Cloudflare adapter has undergone significant changes, and breaking changes to your existing Cloudflare setup are expected.
What should I do?
If you are using an Astro adapter for on-demand rendering or other platform-specific features, please check your specific adapter's changelog for upgrade guidance:
@astrojs/cloudflareCHANGELOG@astrojs/netlifyCHANGELOG@astrojs/nodeCHANGELOG@astrojs/vercelCHANGELOG
Legacy
The following features are now considered legacy features. They should function normally but are no longer recommended and are in maintenance mode. They will see no future improvements and documentation will not be updated. These features will eventually be deprecated, and then removed entirely.
Legacy: content collections backwards compatibility
In Astro 5.x, projects could delay upgrading to the new Content Layer API introduced for content collections because of some existing automatic backwards compatibility that was not previously behind a flag. This meant that it was possible to upgrade from Astro 4 to Astro 5 without updating your content collections, even if you had not enabled the legacy.collections flag. Projects would continue to build, and no errors or warnings would be displayed.
Astro v6.0 removes this automatic legacy content collections support, along with the legacy.collections flag. All content collections must now use the Content Layer API introduced in Astro v5.0 that powers all content collections.
What should I do?
If you experience content collections errors after updating to v6, check your project for any removed legacy features that may need updating to the Content Layer API.
If you are unable to update immediately, you can enable the legacy.collectionsBackwardsCompat flag as a temporary migration helper:
This flag preserves some legacy v4 content collections features:
- Supports the legacy configuration file
src/content/config.ts - Supports
type: 'content'andtype: 'data'without loaders - Preserves legacy entry API:
entry.slugandentry.render() - Uses path-based entry IDs instead of slug-based IDs
This is a temporary migration helper. Migrate your collections to the Content Layer API as soon as possible, then disable this flag.
Deprecated
The following deprecated features are no longer supported and are no longer documented. Please update your project accordingly.
Some deprecated features may temporarily continue to function until they are completely removed. Others may silently have no effect, or throw an error prompting you to update your code.
Deprecated: Astro in getStaticPaths()
In Astro 5.x, it was possible to access an Astro object inside getStaticPaths(). However, despite being typed the same as the Astro object accessible in the frontmatter, this object only had site and generator properties. This could lead to confusion about which Astro object properties were available inside getStaticPaths().
Astro 6.0 deprecates this object for getStaticPaths() to avoid confusion and improves error handling when attempting to access Astro values that are unavailable. Using Astro.site or Astro.generator within getStaticPaths() will now log a deprecation warning, and accessing any other property will throw a specific error with a helpful message. In a future major version, this object will be removed entirely, and accessing Astro.site or Astro.generator will also throw an error.
What should I do?
Update your getStaticPaths() function if you were attempting to access any Astro properties inside its scope. Remove Astro.generator entirely, and replace all occurrences of Astro.site with import.meta.env.SITE:
---
return getPages(Astro.site);
return getPages(import.meta.env.SITE);
}
---
import.meta.env.SITE that are accessible when using getStaticPaths() to dynamically generate static routes.
Deprecated: import.meta.env.ASSETS_PREFIX
In Astro 5.x, it was possible to access build.assetsPrefix in your Astro config via the built-in environment variable import.meta.env.ASSETS_PREFIX. However, Astro v5.7.0 introduced the astro:config virtual module to expose a non-exhaustive, serializable, type-safe version of the Astro configuration which included access to build.assetsPrefix directly. This became the preferred way to access the prefix for Astro-generated asset links when set, although the environment variable still existed.
Astro 6.0 deprecates this variable in favor of build.assetsPrefix from the astro:config/server module.
What should I do?
Replace any occurrences of import.meta.env.ASSETS_PREFIX with the build.assetsPrefix import from astro:config/server. This is a drop-in replacement to provide the existing value, and no other changes to your code should be necessary:
Update any occurrences of the other deprecated exports:
console.log(isTransitionBeforePreparationEvent(event));
console.log(event.type === 'astro:before-preparation');
console.log(TRANSITION_AFTER_SWAP);
console.log('astro:after-swap');
Deprecated: session driver string signature
In Astro 5.x, any unstorage provider name or a custom entrypoint could be provided to define a session driver, and options were also provided directly to the session configuration. However, we felt that this API was limited and inconsistent with other parts of the Astro config.
Astro 6.0 deprecates the driver string signature and options in favor of a new object shape.
What should I do?
Update your session config to use the newly exported sessionDrivers:
const handler = async (req, res) => {
const response = await app.render(req);
await NodeApp.writeResponse(response, res);
};
return { handler };
}
const app = createApp();
const response = await app.render(request);
await writeResponse(response, res);
}
astro/app/node module.
Deprecated: loadManifest() and loadApp() from astro/app/node (Adapter API)
In Astro 5.x, the astro/app/node exposed loadManifest() and loadApp() utilities to allow loading the SSR manifest or a NodeApp instance from a URL instance. However, these were not documented and are no longer recommended usage with the v6 Adapter API.
Astro 6.0 deprecates both functions.
What should I do?
If you have built an adapter, remove loadManifest() and replace loadApp() by createApp():
const manifest = await loadManifest(new URL(import.meta.url));
const app1 = new NodeApp(loadManifest);
const app2 = await loadApp(new URL(import.meta.url));
const app = createApp();
astro/app/entrypoint module.
Deprecated: createExports() and start() (Adapter API)
In Astro 5.x, adapters had to provide the exports required by the host in their server entrypoint using a createExports() function before passing them to setAdapter() as an exports list.
Astro 6.0 introduces a simpler yet more powerful way of making server entrypoints. This relies on passing a new option entrypointResolution: "auto" to setAdapter().
However, for backwards compatibility with existing adapters, the default value of entrypointResolution ("explicit") mimics Astro 5.x API behavior. This means that your adapters can continue to function until you can fully migrate your adapter to the auto value, as shown below.
Note that entrypointResolution: "explicit" (maintaining v5 API behavior) is considered deprecated usage, but the option has been provided so that no immediate change to your adapter is required and to allow adapter authors time to update. This option will be removed in a future major version in favor of all adapters using entrypointResolution: "auto".
What should I do?
If you are an adapter author with a public repository and include the astro-adapter keyword in your package.json, the Astro core team will attempt to make a PR to your repository directly to help you migrate your code if you have not yet followed the steps below.
If you are seeing warnings because you are using a community adapter that is not yet updated, please reach out to the adapter author directly to let them know. It is ultimately their responsibility to update their adapters. You can also let the Astro core team know in the #integrations channel of our Discord and we will attempt to help the adapter author upgrade.
If you have built an adapter, follow these steps to remove the legacy v5 behavior:
Update your
setAdapter(): setentrypointResolution: "auto", removeexportsandargssetAdapter({ // ... entrypointResolution: 'auto', exports: ['handler'], args: { assets: config.build.assets } })Update your server entrypoint to provide any required exports without
createExports():import { App } from 'astro/app'; export function createExports(manifest) { const app = new App(manifest); const handler = (event, context) => { // ... }; return { handler }; } import { createApp } from 'astro/app/entrypoint'; const app = createApp(); export const handler = (event, context) => { // ... }If your adapter provides a
start()function, update your server entrypoint to call the code directly:import { App } from 'astro/app'; export function start(manifest) { const app = new App(manifest); addEventListener('fetch', event => { // ... }); } import { createApp } from 'astro/app/entrypoint'; const app = createApp(); addEventListener('fetch', event => { // ... });If you were relying on
args, create a virtual module to pass the build time configuration and import them from the virtual module instead:export function createExports(manifest, { assets }) { // ... } import { assets } from 'virtual:@example/my-adapter:config';
Removed
The following features have now been entirely removed from the code base and can no longer be used. Some of these features may have continued to work in your project even after deprecation. Others may have silently had no effect.
Projects now containing these removed features will be unable to build, and there will no longer be any supporting documentation prompting you to remove these features.
Removed: legacy content collections
In Astro 5.x, it was still possible to use the original Content Collections API first introduced in Astro v2.0, either through a legacy configuration flag or via built-in backwards compatibility. These methods allowed you to upgrade to Astro v5 even if you were not yet ready or able to update your existing content collections to those powered by the new Content Layer API.
Astro v6.0 removes this previously deprecated Content Collections API support entirely, including the legacy.collections flag and some existing backwards compatibility that was not previously behind a flag. All content collections must now use the Content Layer API introduced in Astro v5.0 that powers all content collections. No backwards compatibility support is available.
What should I do?
If you had previously enabled the legacy flag, you must remove it.
a collection that defines a collection type (`type: 'content'` or `type: 'data'`) / ([`ContentCollectionInvalidTypeError`](/en/reference/errors/content-collection-invalid-type/))
There are no longer different types of collections. This must be deleted from your collection definition.// src/content.config.ts
const blog = defineCollection({
// For content layer you no longer define a `type`
type: 'content',
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/data/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
}),
});
legacy collection querying methods `getDataEntryById()` and `getEntryBySlug()` / ([`GetEntryDeprecationError`](/en/reference/errors/get-entry-deprecation-error/))
Replace both methods with [`getEntry()`](/en/reference/modules/astro-content/#getentry).legacy collection querying and rendering methods that depend on a `slug` property / ([`ContentSchemaContainsSlugError`](/en/reference/errors/content-schema-contains-slug-error/))
Previously, the `id` was based on the filename, and there was a `slug` property that could be used in a URL. Now the [`CollectionEntry`](/en/reference/modules/astro-content/#collectionentry) `id` is a slug. If you need access to the filename (previously available as the `id`), use the `filePath` property. Replace instances of `slug` with `id`:---
return posts.map((post) => ({
params: { slug: post.slug },
params: { slug: post.id },
props: post,
}));
}
---
content rendered using `entry.render()`
Collection entries no longer have a `render()` method. Instead, import the `render()` function from `astro:content` and use `render(entry)`:---
const post = await getEntry('pages', 'homepage');
const { Content, headings } = await post.render();
const { Content, headings } = await render(post);
---
<Content />
Removed: <ViewTransitions /> component
In Astro 5.0, the <ViewTransitions /> component was renamed to <ClientRouter /> to clarify the role of the component. The new name makes it more clear that the features you get from Astro's <ClientRouter /> routing component are slightly different from the native CSS-based MPA router. However, a deprecated version of the <ViewTransitions /> component still existed and may have functioned in Astro 5.x.
Astro 6.0 removes the <ViewTransitions /> component entirely and it can no longer be used in your project. Update to the <ClientRouter /> component to continue to use these features.
What should I do?
Replace all occurrences of the ViewTransitions import and component with ClientRouter:
<html>
<head>
...
<ViewTransitions />
<ClientRouter />
</head>
</html>
Removed: emitESMImage()
In Astro 5.6.2, the emitESMImage() function was deprecated in favor of emitImageMetadata(), which removes two deprecated arguments that were not meant to be exposed for public use: _watchMode and experimentalSvgEnabled.
Astro 6.0 removes emitESMImage() entirely. Update to emitImageMetadata() to keep your current behavior.
What should I do?
Replace all occurrences of the emitESMImage() with emitImageMetadata() and remove unused arguments:
const imageId = '/images/photo.jpg';
const result = await emitESMImage(imageId, false, false);
const result = await emitImageMetadata(imageId);
emitImageMetadata().
Removed: Astro.glob()
In Astro 5.0, Astro.glob() was deprecated in favor of using getCollection() to query your collections, and import.meta.glob() to query other source files in your project.
Astro 6.0 removes Astro.glob() entirely. Update to import.meta.glob() to keep your current behavior.
What should I do?
Replace all use of Astro.glob() with import.meta.glob(). Note that import.meta.glob() no longer returns a Promise, so you may have to update your code accordingly. You should not require any updates to your glob patterns.
---
const posts = await Astro.glob('./posts/*.md');
const posts = Object.values(import.meta.glob('./posts/*.md', { eager: true }));
---
{posts.map((post) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
Where appropriate, consider using content collections to organize your content, which has its own newer, more performant querying functions.
You may also wish to consider using glob packages from NPM, such as fast-glob.
import.meta.glob.
Removed: exposed astro:actions internals
In Astro 5.x, some internals were exported from astro:actions that were not meant to be exposed for public use.
Astro 6.0 removes the following functions, classes and types as exports from the astro:actions virtual module. These can no longer be imported in your project files:
ACTION_ERROR_CODESActionInputErrorappendForwardSlashastroCalledServerErrorcallSafelydeserializeActionResultformDataToObjectgetActionQueryStringserializeActionResulttype Actionstype ActionAccepttype AstroActionContexttype SerializedActionResult
What should I do?
Replace all imports of serializeActionResult() and deserializeActionResult() with getActionContext(). These two methods are now available through getActionContext():
// ...
});
Remove any occurrences of the other removed exports:
Removed: Percent-Encoding in routes
In Astro 5.x, it was possible to include a percent-encoded percent sign (%25) in filenames.
Astro 6.0 removes support for the characters %25 in filenames for security reasons. This restriction prevents encoding-based security bypasses where %25 decodes to %, potentially leading to ambiguous or invalid encoding sequences.
What should I do?
If you have route files with %25 in the filename, rename them to use a different character:
src/pages/test%25file.astro
src/pages/test-file.astro
Removed: astro:ssr-manifest virtual module (Integration API)
In Astro 5.x, the deprecated astro:ssr-manifest virtual module could still be used to access configuration values.
Astro 6.0 removes the astro:ssr-manifest virtual module entirely. It is no longer used by integrations or internally by Astro. The manifest is now passed directly through integration hooks and adapter APIs rather than through a virtual module. For build-specific manifest data, use the astro:build:ssr integration hook, which receives the manifest as a parameter.
What should I do?
If your integration or code imports from astro:ssr-manifest, use astro:config/server instead to access configuration values:
// Use srcDir, outDir, root, etc. for configuration values
astro:config virtual module.
Removed: RouteData.generate() (Adapter API)
In Astro 5.x, routes could be generated using the generate() method on RouteData.
Astro 6.0 removes RouteData.generate() because route generation is now handled internally by Astro.
What should I do?
Remove any calls to route.generate() in your code. This method is no longer needed:
const generated = route.generate(params);
Removed: routes on astro:build:done hook (Integration API)
In Astro 5.0, accessing routes on the astro:build:done hook was deprecated.
Astro 6.0 removes the routes array passed to this hook entirely. Instead, the astro:routes:resolved hook should be used.
What should I do?
Remove any instance of routes passed to astro:build:done and replace it with the new astro:routes:resolved hook. Access distURL on the newly exposed assets map:
const integration = () => {
let routes
return {
name: 'my-integration',
hooks: {
'astro:routes:resolved': (params) => {
routes = params.routes
},
'astro:build:done': ({
routes
assets
}) => {
for (const route of routes) {
const distURL = assets.get(route.pattern)
if (distURL) {
Object.assign(route, { distURL })
}
}
console.log(routes)
}
}
}
}
astro:routes:resolved hook for building integrations.
Removed: entryPoints on astro:build:ssr hook (Integration API)
In Astro 5.0, functionPerRoute was deprecated. That meant that entryPoints on the astro:build:ssr hook was always empty.
Astro 6.0 removes the entryPoints map passed to this hook entirely.
What should I do?
Remove any instance of entryPoints passed to astro:build:ssr:
const integration = () => {
return {
name: 'my-integration',
hooks: {
'astro:build:ssr': (params) => {
someLogic(params.entryPoints)
},
}
}
}
Removed: old app.render() signature (Adapter API)
In Astro 4.0, the app.render() signature that allowed passing routeData and locals as optional arguments was deprecated in favor of a single optional renderOptions argument.
Astro 6.0 removes this signature entirely. Attempting to pass these separate arguments will now cause an error in your project.
What should I do?
Review your app.render() calls and pass routeData and locals as properties of an object instead of as multiple independent arguments:
app.render(request, routeData, locals)
app.render(request, { routeData, locals })
Removed: app.setManifestData() (Adapter API)
In Astro 5.0, the app.setManifestData() method was available on App and NodeApp, but is no longer used nor needed.
Astro 6.0 removes this method entirely.
What should I do?
Remove any call to app.setManifestData(). If you need to update the manifest, create a new App instance.
Removed: handleForms prop for the <ClientRouter /> component
In Astro 4.0, the handleForms prop of the <ClientRouter /> component was deprecated, as it was no longer necessary to opt in to handling submit events for form elements. This functionality has been built in by default and the property, if still included in your project, silently had no impact on form submission.
Astro 6.0 removes this prop entirely and it now must be removed to avoid errors in your project.
What should I do?
Remove the handleForms property from your <ClientRouter /> component if it exists. It has provided no additional functionality, and so removing it should not change any behavior in your project:
---
---
<html>
<head>
<ClientRouter handleForms />
</head>
<body>
<!-- stuff here -->
</body>
</html>
Removed: prefetch() with option
In Astro 4.8.4, the with option of the programmatic prefetch() function was deprecated in favor of a more sensible default behavior that no longer required specifying the priority of prefetching for each page.
Astro 6.0 removes this option entirely and it is no longer possible to configure the priority of prefetching by passing the with option. Attempting to do so will now cause errors.
By default, Astro's prefetching now uses an automatic approach that will always try to use <link rel="prefetch> if supported, or will fall back to fetch().
What should I do?
Review your prefetch() calls and remove the with option if it still exists:
prefetch('/about', { with: 'fetch' });
prefetch('/about');
Removed: rewrite() from Actions context
In Astro 5.5.6, the ActionAPIContext.rewrite() method was deprecated because custom endpoints should be used instead of rewrites.
Astro 6.0 removes the rewrite() method from ActionAPIContext entirely and it may no longer be used.
What should I do?
Review your Actions handlers and remove any call to rewrite():
}
</style>
<style>
body {
background: yellow;
background: red;
}
</style>
<script>
console.log("hello")
console.log("world")
</script>
<script>
console.log("world!")
console.log("hello!")
</script>
script and style tags.
Changed: how responsive image styles are emitted
In Astro 5.x, images were computed at runtime and the fit and pos responsive image styles were injected in a style attribute. This did not allow compatibility with Astro's Content Security Policy (CSP) for many reasons.
Astro 6 generates image styles inside a virtual module at build time based on project configuration, resulting in a hash class and data-* attributes to apply responsive styling to your images.
What should I do?
Visually inspect your images to ensure that they are rendering as expected. This is an implementation detail that should not affect the expected use of responsive images.
However, if you were relying on the inline styles previously generated for your images:
<img style="--fit: <value>; --pos: <value>" >
then you will need to update your project code to account for the new data-* attributes instead:
<img class="__a_HaSh350" data-astro-fit="value" data-astro-pos="value" >
Breaking Changes
The following changes are considered breaking changes in Astro v6.0. Breaking changes may or may not provide temporary backwards compatibility. If you were using these features, you may have to update your code as recommended in each entry.
Changed: endpoints with a file extension cannot be accessed with a trailing slash
In Astro v5.0, custom endpoints whose URL ended in a file extension (e.g. /src/pages/sitemap.xml.ts ) could be accessed with a trailing slash (/sitemap.xml/) or without (/sitemap.xml), regardless of the value configured for build.trailingSlash.
In Astro v6.0, these endpoints can only be accessed without a trailing slash. This is true regardless of your build.trailingSlash configuration.
What should I do?
Review your links to your custom endpoints that include a file extension in the URL and remove any trailing slashes:
<a href="/sitemap.xml/">Sitemap</a>
<a href="/sitemap.xml">Sitemap</a>
Changed: import.meta.env values are always inlined
In Astro 5.13, the experimental.staticImportMetaEnv flag was introduced to update the behavior when accessing import.meta.env directly to align with Vite's handling of environment variables and ensures that import.meta.env values are always inlined.
In Astro 5.x, non-public environment variables were replaced by a reference to process.env. Additionally, Astro could also convert the value type of your environment variables used through import.meta.env, which could prevent access to some values such as the strings "true" (which was converted to a boolean value), and "1" (which was converted to a number).
Astro 6 removes this experimental flag and makes this the new default behavior in Astro: import.meta.env values are always inlined and never coerced.
What should I do?
If you were previously using this experimental feature, you must remove this experimental flag from your configuration as it no longer exists.
If you were relying on coercion, you may need to update your project code to apply it manually:
const enabled: boolean = import.meta.env.ENABLED;
const enabled: boolean = import.meta.env.ENABLED === "true";
If you were relying on the transformation into process.env, you may need to update your project code to apply it manually:
const enabled: boolean = import.meta.env.DB_PASSWORD;
const enabled: boolean = process.env.DB_PASSWORD;
You may also need to update types:
interface ImportMetaEnv {
readonly PUBLIC_POKEAPI: string;
readonly DB_PASSWORD: string;
readonly ENABLED: boolean;
readonly ENABLED: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
namespace NodeJS {
interface ProcessEnv {
DB_PASSWORD: string;
}
}
If you need more control over environment variables in Astro, we recommend you use astro:env.
astro:env.
Changed: Cropping by default in default image service
In Astro 5.0, the default image service would only apply cropping when the fit option was provided.
Astro 6.0 applies cropping by default without requiring setting the fit option.
What should I do?
No changes are needed to your existing cropped images as the fit property is still valid. However, if you were previously setting fit to contain (its default value) in order to crop your images, you may now remove this option and still achieve the same cropping behavior by specifying width and height alone:
---
---
<Image src={myImage} width={400} height={300} fit="contain" />
<Image src={myImage} width={400} height={300} />
Changed: Never upscale images in default image service
In Astro 5.x, the default image service would upscale images when the requested dimensions were larger than the source image.
Astro 6.0 removes this behavior: the default image service never upscales images.
What should I do?
Review your images and update dimensions as needed. If you do need to upscale images, you may consider upscaling the images manually or using a custom image service that supports upscaling.
Changed: SVG rasterization
In Astro v5.x, Astro's default Sharp image service was unable to convert SVG files to raster files (e.g. PNG, WebP). This meant that the <Image /> component would ignore any value set for format when optimizing and transforming SVG files.
Astro 6.0 now supports SVG rasterization. This is subject to many limitations, for instance, SVGs with embedded fonts might not be converted properly. However, when the format property is set, the image service will now attempt to convert SVG images.
What should I do?
If you were previously relying on the fact that the image service would automatically skip converting SVGs, you must now check the format of your images beforehand to avoid converting SVGs to raster images:
<Image src={imageThatMightBeAnSvg} format="avif" alt="example" />
<Image
src={imageThatMightBeAnSvg}
format={imageThatMightBeAnSvg.format === "svg" ? "svg" : "avif"}
alt="example"
/>
format image property.
Changed: getImage() throws when called on the client
In Astro 5.x, calling getImage() from astro:assets on the client would silently fail or produce incorrect results.
Astro 6.0 throws a runtime error when getImage() is called on the client.
What should I do?
Call getImage() on the server and pass the resulting src to the client instead:
---
const optimizedBackground = await getImage({ src: myBackground, format: "avif" });
---
<div id="background" data-src={optimizedBackground.src}></div>
<script>
const src = document.getElementById("background").dataset.src;
// use src client-side as needed
</script>
getImage() for a full example.
Changed: Markdown heading ID generation
In Astro 5.x, an additional default processing step to Markdown stripped trailing hyphens from the end of IDs for section headings ending in special characters. This provided a cleaner id value, but could lead to incompatibilities rendering your Markdown across platforms.
In Astro 5.5, the experimental.headingIdCompat flag was introduced to allow you to make the IDs generated by Astro for Markdown headings compatible with common platforms like GitHub and npm, using the popular github-slugger package.
Astro 6.0 removes this experimental flag and makes this the new default behavior in Astro: trailing hyphens from the end of IDs for headings ending in special characters are no longer removed.
What should I do?
If you have manual links to headings, you may need to update the anchor link value with a new trailing hyphen. For example, the following Markdown heading:
## `<Picture />`
will now generate the following HTML with a trailing hyphen in the heading id:
<h2 id="picture-"><code><Picture /></code></h2>
and must now be linked to as:
See [the Picture component](/en/guides/images/#picture-) for more details.
If you were previously using the experimental feature to enforce trailing hyphens, you must remove this experimental flag from your configuration as it no longer exists.
If you were previously using the rehypeHeadingIds plugin directly to enforce compatibility, remove the headingIdCompat option as it no longer exists:
If you want to keep the old ID generation for backward compatibility reasons, you can create a custom rehype plugin that will generate headings IDs like Astro 5.x. This will allow you to continue to use your existing anchor links without adding trailing hyphens.
Create a custom rehype plugin to strip trailing hyphens
Install required dependencies:
```sh npm i github-slugger hast-util-heading-rank unist-util-visit hast-util-to-string ``` ```sh pnpm add github-slugger hast-util-heading-rank unist-util-visit hast-util-to-string ``` ```sh yarn add github-slugger hast-util-heading-rank unist-util-visit hast-util-to-string ``` Create a custom rehype plugin that will generate headings IDs like Astro v5:
import GithubSlugger from 'github-slugger'; import { headingRank } from 'hast-util-heading-rank'; import { visit } from 'unist-util-visit'; import { toString } from 'hast-util-to-string'; const slugs = new GithubSlugger(); export function rehypeSlug() { /** * @param {import('hast').Root} tree */ return (tree) => { slugs.reset(); visit(tree, 'element', (node) => { if (headingRank(node) && !node.properties.id) { let slug = slugs.slug(toString(node)); // Strip trailing hyphens like in Astro v5 and below: if (slug.endsWith('-')) slug = slug.slice(0, -1); node.properties.id = slug; } }); }; }Add the custom plugin to your Markdown configuration in
astro.config.mjs:import { defineConfig } from 'astro/config'; import { rehypeSlug } from './plugins/rehype-slug'; export default defineConfig({ markdown: { rehypePlugins: [rehypeSlug], }, });
Changed: getStaticPaths() cannot return params of type number
In Astro 5.x, getStaticPaths() could return params of type number, which would always be stringified by Astro. However, that could be confusing because it conflicted with Astro.params types.
Astro 6.0 removes this behavior: getStaticPaths() must now return string or undefined params values.
What should I do?
Review your dynamic routes using getStaticPaths() and convert any number params to strings:
---
Changed: Rollup output file name config path (Vite config)
In Astro 5.x, custom Rollup output file name options for client assets could be configured at vite.build.rollupOptions.output.
Astro 6.0 scopes client build output configuration to Vite's client environment. If you customize entryFileNames, chunkFileNames, or assetFileNames for client assets, use vite.environments.client.build.rollupOptions.output.
What should I do?
Move your config from vite.build.rollupOptions.output to vite.environments.client.build.rollupOptions.output:
Changed: Integration hooks and HMR access patterns (Integration API)
In Astro 5.x, Astro relied on certain patterns for integration hooks and HMR access that were incompatible with or could be improved by integrating Vite's Environment API.
Astro 6.0 uses Vite's new Environment API for build configuration and dev server interactions. This primarily enables dev mode in runtimes like workerd, but means that some integration hooks and HMR access patterns have changed.
What should I do?
For integrations using astro:build:setup:
The hook is now called once with all environments configured (ssr, client, prerender), instead of being called separately for each build target. Remove the target parameter and use vite.environments to configure specific environments:
{
hooks: {
'astro:build:setup': ({ target, vite }) => {
if (target === 'client') {
vite.build.minify = false;
}
}
'astro:build:setup': ({ vite }) => {
vite.environments.client.build.minify = false;
}
}
}
For dev toolbar and integration code accessing HMR:
Replace server.hot.send() with server.environments.client.hot.send():
server.hot.send(event)
server.environments.client.hot.send(event)
Changed: SSRManifest interface structure (Adapter API)
In Astro 5.x, path properties of the SSRManifest interface like srcDir, outDir, cacheDir, publicDir, buildClientDir, and buildServerDir were URL strings.
Astro 6.0 changes the form of these path properties to URL objects instead of URL strings. With this change, several new properties are now available on the manifest, and others have been updated or removed.
What should I do?
If you were treating these path properties as strings, you will now need to handle the URL object. For example, you will now need to access the href property of the URL object:
// To retrieve the same format (e.g., "file:///path/to/src"), make the following change:
const srcPath = manifest.srcDir;
const srcPath = manifest.srcDir.href;
If you were accessing the hrefRoot property, you will need to remove it, as it is no longer available on the manifest.
Update any use of serverIslandMappings and sessionDriver. These are now async methods:
const mappings = manifest.serverIslandMappings;
const driver = manifest.sessionDriver;
const mappings = await manifest.serverIslandMappings?.();
const driver = await manifest.sessionDriver?.();
Changed: schema types are inferred instead of generated (Content Loader API)
In Astro 5.x, the types for content collections were generated using zod-to-ts when provided by a content loader and not defined by a user-provided schema.
Astro 6.0 removes this behavior: types are no longer generated using zod-to-ts. Instead, types are inferred.
What should I do?
If you are providing a schema in a content loader, you must use the TypeScript' satisfies operator:
import type { Loader } from 'astro/loaders'
function myLoader(): Loader {
function myLoader() {
return {
name: 'my-loader',
load: async (context) => {
// ...
},
schema: z.object({/* ... */})
}
} satisfies Loader
}
Known Issues
Please check Astro's issues on GitHub for any reported issues, or to file an issue yourself.