diff --git a/astro.sidebar.ts b/astro.sidebar.ts index ed4990f336ba0..c2870189bb783 100644 --- a/astro.sidebar.ts +++ b/astro.sidebar.ts @@ -90,6 +90,7 @@ export const sidebar = [ group('guides.upgrade.major', { collapsed: true, items: [ + 'guides/upgrade-to/v6', 'guides/upgrade-to/v5', 'guides/upgrade-to/v4', 'guides/upgrade-to/v3', @@ -145,15 +146,9 @@ export const sidebar = [ 'reference/experimental-flags', 'reference/experimental-flags/csp', 'reference/experimental-flags/fonts', - 'reference/experimental-flags/live-content-collections', 'reference/experimental-flags/client-prerender', 'reference/experimental-flags/content-intellisense', - 'reference/experimental-flags/preserve-scripts-order', - 'reference/experimental-flags/heading-id-compat', - 'reference/experimental-flags/static-import-meta-env', 'reference/experimental-flags/chrome-devtools-workspace', - 'reference/experimental-flags/fail-on-prerender-conflict', - 'reference/experimental-flags/svg-optimization', ], }), 'reference/legacy-flags', diff --git a/public/_headers b/public/_headers index f56759cc14415..015d6749500c7 100644 --- a/public/_headers +++ b/public/_headers @@ -2,3 +2,7 @@ cache-control: max-age=31536000 cache-control: immutable cache-control: public + +# TODO: Remove this before merging to main +/* + x-robots-tag: no-index diff --git a/src/content/docs/de/tutorial/6-islands/4.mdx b/src/content/docs/de/tutorial/6-islands/4.mdx index cd7c4fc0c4767..7c60d35154716 100644 --- a/src/content/docs/de/tutorial/6-islands/4.mdx +++ b/src/content/docs/de/tutorial/6-islands/4.mdx @@ -119,7 +119,8 @@ Aktualisiere Astro und alle Integrationen auf die neuesten Versionen, indem du i ```ts title="src/content.config.ts" import { glob } from "astro/loaders"; - import { z, defineCollection } from "astro:content"; + import { defineCollection } from "astro:content"; + import { z } from "astro/zod"; const blog = defineCollection({ loader: glob({ pattern: '**/[^_]*.md', base: "./src/blog" }), @@ -148,7 +149,7 @@ Aktualisiere Astro und alle Integrationen auf die neuesten Versionen, indem du i 1. Erstelle eine neue Seiten­datei `src/pages/posts/[...slug].astro`. Markdown- und MDX-Dateien werden innerhalb einer Sammlung nicht mehr automatisch zu Seiten. Du musst daher eine Seite erstellen, die für die Ausgabe jedes einzelnen Blogbeitrags verantwortlich ist. -2. Füge den folgenden Code ein, um deine Sammlung [abzufragen](/de/guides/content-collections/#querying-collections) und für jede generierte Seite die `slug` und den Seiteninhalt bereitzustellen: +2. Füge den folgenden Code ein, um deine Sammlung [abzufragen](/de/guides/content-collections/#querying-build-time-collections) und für jede generierte Seite die `slug` und den Seiteninhalt bereitzustellen: ```astro title="src/pages/posts/[...slug].astro" --- diff --git a/src/content/docs/en/getting-started.mdx b/src/content/docs/en/getting-started.mdx index ec9c01835d6d2..9b70bb453fd03 100644 --- a/src/content/docs/en/getting-started.mdx +++ b/src/content/docs/en/getting-started.mdx @@ -5,6 +5,9 @@ i18nReady: true tableOfContents: false editUrl: false next: false +banner: + content: | + Astro v6 is here! Learn how to upgrade your site hero: title: Astro Docs tagline: Guides, resources, and API references to help you build with Astro. diff --git a/src/content/docs/en/guides/actions.mdx b/src/content/docs/en/guides/actions.mdx index cfe4e8622fdab..1b930e30e2cf1 100644 --- a/src/content/docs/en/guides/actions.mdx +++ b/src/content/docs/en/guides/actions.mdx @@ -329,14 +329,14 @@ export const server = { // Matches when the `type` field has the value `create` type: z.literal('create'), name: z.string(), - email: z.string().email(), + email: z.email(), }), z.object({ // Matches when the `type` field has the value `update` type: z.literal('update'), id: z.number(), name: z.string(), - email: z.string().email(), + email: z.email(), }), ]), async handler(input) { @@ -374,7 +374,7 @@ The following example shows a validated newsletter registration form that accept ``` -2. Define a `newsletter` action to handle the submitted form. Validate the `email` field using the `z.string().email()` validator, and the `terms` checkbox using `z.boolean()`: +2. Define a `newsletter` action to handle the submitted form. Validate the `email` field using the `z.email()` validator, and the `terms` checkbox using `z.boolean()`: ```ts title="src/actions/index.ts" ins={5-12} import { defineAction } from 'astro:actions'; @@ -384,7 +384,7 @@ The following example shows a validated newsletter registration form that accept newsletter: defineAction({ accept: 'form', input: z.object({ - email: z.string().email(), + email: z.email(), terms: z.boolean(), }), handler: async ({ email, terms }) => { /* ... */ }, diff --git a/src/content/docs/en/guides/cms/keystatic.mdx b/src/content/docs/en/guides/cms/keystatic.mdx index d8fdc1e12276a..6e94b455e57d4 100644 --- a/src/content/docs/en/guides/cms/keystatic.mdx +++ b/src/content/docs/en/guides/cms/keystatic.mdx @@ -183,7 +183,7 @@ Visit `http://127.0.0.1:4321/keystatic` in the browser to see the Keystatic Admi ## Rendering Keystatic content -Use Astro's Content Collections API to [query and display your posts and collections](/en/guides/content-collections/#querying-collections), just as you would in any Astro project. +[Query and display your posts and collections](/en/guides/content-collections/#querying-build-time-collections), just as you would in any Astro project. ### Displaying a collection list diff --git a/src/content/docs/en/guides/content-collections.mdx b/src/content/docs/en/guides/content-collections.mdx index d85093421e967..462b9ab8cf3ac 100644 --- a/src/content/docs/en/guides/content-collections.mdx +++ b/src/content/docs/en/guides/content-collections.mdx @@ -3,6 +3,9 @@ title: Content collections description: >- Manage your content with type safety. i18nReady: true +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 3 --- import { FileTree, CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; import Since from '~/components/Since.astro' @@ -12,18 +15,13 @@ import ReadMore from "~/components/ReadMore.astro"

-**Content collections** are the best way to manage sets of content in any Astro project. Collections help to organize and query your documents, enable Intellisense and type checking in your editor, and provide automatic TypeScript type-safety for all of your content. -Astro v5.0 introduced the Content Layer API for defining and querying content collections. This performant, scalable API provides built-in content loaders for your local collections. For remote content, you can use third-party and community-built loaders or create your own custom loader and pull in your data from any source. +**Content collections** are the best way to manage sets of content in any Astro project: blog posts, product descriptions, character profiles, recipes, or any structured content. Collections help to organize and query your documents, enable Intellisense and type checking in your editor, and provide automatic TypeScript type-safety for all of your content. -:::note -Projects may continue using the legacy Content Collections API introduced in Astro v2.0. However, we encourage you to [update any existing collections](/en/guides/upgrade-to/v5/#legacy-v20-content-collections-api) when you are able. -::: +Astro provides performant, scalable APIs to load, query, and render content from anywhere: stored locally in your project, hosted remotely, or fetched live from frequently-updating sources. ## What are Content Collections? -You can define a **collection** from a set of data that is structurally similar. This can be a directory of blog posts, a JSON file of product items, or any data that represents multiple items of the same shape. - -Collections stored locally in your project or on your filesystem can have entries of Markdown, MDX, Markdoc, YAML, TOML, or JSON files: +A content collection is a set of related, structurally identical data. This data can be stored in one or several files locally (e.g. a folder of individual Markdown files of blog posts, a single JSON file of product descriptions) or fetched from remote sources such as a database, CMS, or API endpoint. Each member of the collection is called an entry. - src/ @@ -35,32 +33,103 @@ Collections stored locally in your project or on your filesystem can have entrie - authors.json a single file containing all collection entries -With an appropriate collection loader, you can fetch remote data from any external source, such as a CMS, database, or headless payment system. +Collections are defined by the location and shape of its entries and provide a convenient way to query and render your content and associated metadata. You can create a collection any time you have a group of related data or content, stored in the same location, that shares a common structure. + +[Two types of content collections](#types-of-collections) are available to allow you to work with data fetched either at build time or at request time. Both build-time collections and live updating collections use: + +- A required `loader` to retrieve your content and metadata from wherever it is stored and make it available to your project through content-focused APIs. +- An optional collection `schema` that allows you to define the expected shape of each entry for type safety, autocomplete, and validation in your editor. + +Collections stored locally in your project or on your filesystem can use one of Astro's [provided build-time loaders](#build-time-collection-loaders) to fetch data from Markdown, MDX, Markdoc, YAML, TOML, or JSON files. Point Astro to the location of your content, define your data shape, and you're good to go with a blog or similarly content-heavy, mostly static site in no time! + +With [community-built loaders](https://astro.build/integrations/?search=&categories%5B%5D=loaders) or by building a [custom build-time collection loader](#custom-build-time-loaders) or [live loader](#creating-a-live-loader) yourself, you can fetch remote data from any external source, such as a CMS, database, or headless payment system, either at build time or live on demand. + +### Types of collections + +[Build-time content collections](#defining-build-time-content-collections) are updated at build time, and data is saved to a storage layer. This provides excellent performance for most content, but may not be suitable for frequently updating data sources requiring up-to-the-moment data freshness, such as live stock prices. + +For the best performance and scalability, use build-time content collections when one or more of these is true: + +- **Performance is critical** and you want to prerender data at build time. +- **Your data is relatively static** (e.g., blog posts, documentation, product descriptions). +- **You want to benefit from build-time optimization** and caching. +- **You need to process MDX** or **perform image optimization**. +- **Your data can be fetched once and reused** across multiple builds. + +:::tip[Quick start] +See [the official Astro blog starter template](https://github.com/withastro/astro/tree/latest/examples/blog) to get up and running quickly with an example of using the [built-in `glob()` loader](#the-glob-loader) and [defining a schema](#defining-the-collection-schema) for a collection of local Markdown or MDX blog posts. +::: + +[Live content collections](#live-content-collections) fetch their data at runtime rather than build time. This allows you to access frequently updated data from CMSs, APIs, databases, or other sources using a unified API, without needing to rebuild your site when the data changes. However, this can come at a performance cost since data is fetched at each request and returned directly with no data store persistence. + +Live content collections are designed for data that changes frequently and needs to be up-to-date when a page is requested. Consider using them when one or more of these is true: + +- **You need real-time information** (e.g. user-specific data, current stock levels). +- **You want to avoid constant rebuilds** for content that changes often. +- **Your data updates frequently** (e.g. up-to-the-minute product inventory, prices, availability). +- **You need to pass dynamic filters** to your data source based on user input or request parameters. +- **You're building preview functionality** for a CMS where editors need to see draft content immediately. + +Both kinds of collections can exist in the same project, so you can always choose the best type of collection for each individual data source. For example, a build-time collection can manage product descriptions, while a live collection can manage content inventory. + +Both types of collections use similar APIs (e.g. `getCollection()` and `getLiveCollection()`), so that working with collections will feel familiar no matter which one you choose, while still ensuring that you always know which type of collection you are working with. + +We suggest using build-time content collections whenever possible, and using live collections when your content needs updating in real time and the performance tradeoffs are acceptable. Additionally, live content collections have some limitations compared to build-time collections: + +- **No MDX support**: MDX cannot be rendered at runtime +- **No image optimization**: Images cannot be processed at runtime +- **Performance considerations**: Data is fetched on each request (unless cached) +- **No data store persistence**: Data is not saved to the content layer data store + +### When to create a collection + +Define your data as a collection when: + +- You have multiple files or data to organize that share the same overall structure (e.g. a directory of blog posts written in Markdown which all have the same frontmatter properties). +- You have existing content stored remotely, such as in a CMS, and want to take advantage of the collections helper functions instead of using `fetch()` or SDKs. +- You need to fetch (tens of) thousands of related pieces of data at build time, and need a querying and caching method that handles at scale. + +Much of the benefit of using collections comes from: + +- Defining a common data shape to validate that an individual entry is "correct" or "complete", avoiding errors in production. +- Content-focused APIs designed to make querying intuitive (e.g. `getCollection()` instead of `import.meta.glob()`) when importing and rendering content on your pages. +- Access to both built-in loaders and access to the low-level [Content Loader API](/en/reference/content-loader-reference/) for retrieving your content. There are additionally several third-party and community-built loaders available, and you can build your own custom loader to fetch data from anywhere. +- Performance and scalability. Build-time content collections data can be cached between builds and is suitable for tens of thousands of content entries. + +### When not to create a collection + +Collections provide excellent structure, safety, and organization when you have multiple pieces of content that must share the same properties. + +Collections may not be your solution if: + +- You have only one or a small number of different content pages. Consider [making individual page components](/en/basics/astro-pages/) such as `src/pages/about.astro` with your content directly instead. +- You are displaying files that are not processed by Astro, such as PDFs. Place these static assets in the [`public/` directory](/en/basics/project-structure/#public) of your project instead. +- Your data source has its own SDK/client library for imports that is incompatible with or does not offer a content loader, and you prefer to use it directly. ## TypeScript configuration for collections -Content collections rely on TypeScript to provide Zod validation, Intellisense and type checking in your editor. If you are not extending one of Astro's `strict` or `strictest` TypeScript settings, you will need to ensure the following `compilerOptions` are set in your `tsconfig.json`: +Content collections rely on TypeScript to provide Zod validation, Intellisense, and type checking in your editor. By default, Astro configures a [`strict` TypeScript template](/en/guides/typescript/#tsconfig-templates) when you create a new project using the `create astro` CLI command. Both of Astro's `strict` and `strictest` templates include the TypeScript settings your project needs for content collections. -```json title="tsconfig.json" ins={5} {6} +If you changed this setting to `base` because you are not writing TypeScript in your project, or are not using any of Astro's built-in templates, you will need to also add the following `compilerOptions` in your `tsconfig.json` to use content collections: + +```json title="tsconfig.json" ins={4-7} { - // Included with "astro/tsconfigs/strict" or "astro/tsconfigs/strictest" "extends": "astro/tsconfigs/base", + // not needed for `strict` or `strictest` "compilerOptions": { - "strictNullChecks": true, // add if using `base` template - "allowJs": true // required, and included with all Astro templates + "strictNullChecks": true, + "allowJs": true } } ``` -## Defining Collections - -Individual collections use `defineCollection()` to configure: -- a `loader` for a data source (required) -- a `schema` for type safety (optional, but highly recommended!) +## Defining build-time content collections -### The collection config file +All of your build-time content collections are defined in a special `src/content.config.ts` file (`.js` and `.mjs` extensions are also supported) using `defineCollection()`, and then a single collections object is exported for use in your project. -To define collections, you must create a `src/content.config.ts` file in your project (`.js` and `.mjs` extensions are also supported.) This is a special file that Astro will use to configure your content collections based on the following structure: +Each individual collection configures: +- [a build-time `loader`](#build-time-collection-loaders) for a data source (required) +- [a build-time `schema`](#defining-the-collection-schema) for type safety (optional, but highly recommended!) ```ts title="src/content.config.ts" // 1. Import utilities from `astro:content` @@ -72,87 +141,151 @@ import { glob, file } from 'astro/loaders'; // 3. Import Zod import { z } from 'astro/zod'; -// 4. Define your collection(s) -const blog = defineCollection({ /* ... */ }); -const dogs = defineCollection({ /* ... */ }); +// 4. Define a `loader` and `schema` for each collection +const blog = defineCollection({ + loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), + schema: z.object({ + title: z.string(), + description: z.string(), + pubDate: z.coerce.date(), + updatedDate: z.coerce.date().optional(), + }), +}); // 5. Export a single `collections` object to register your collection(s) -export const collections = { blog, dogs }; +export const collections = { blog }; ``` -### Defining the collection `loader` +You can then use the dedicated `getCollection()` and `getEntry()` functions to [query your content collections data](#querying-build-time-collections) and render your content. + +You can choose to [generate page routes](#generating-routes-from-content) from your build-time collection entries at build time for an entirely static, prerendered site. Or, you can render your build-time collections on demand, choosing to delay building your page until it is first requested. This is useful when you have a large number of pages (e.g. thousands or tens of thousands) and want to delay building a static page until it is needed. -The Content Layer API allows you to fetch your content (whether stored locally in your project or remotely) and uses a `loader` property to retrieve your data. +## Build-time collection loaders -#### Built-in loaders +Astro provides two built-in loaders (`glob()` and `file()`) for fetching your local content at build time. Pass the location of your data in your project or on your filesystem, and these loaders will automatically handle your data and update the persistent data store content layer. -Astro provides [two built-in loader functions](/en/reference/content-loader-reference/#built-in-loaders) (`glob()` and `file()`) for fetching your local content, as well as access to the API to construct your own loader and fetch remote data. +To fetch remote data at build time, you can [build a custom loader](#custom-build-time-loaders) to retrieve your data and update the data store. Or, you can use any [third-party or community-published loader integration](https://astro.build/integrations/2/?search=&categories%5B%5D=loaders). Several already exist for popular content management systems as well as common data sources such as Obsidian vaults, GitHub repositories, or Bluesky posts. -The [`glob()` loader](/en/reference/content-loader-reference/#glob-loader) creates entries from directories of Markdown, MDX, Markdoc, JSON, YAML, or TOML files from anywhere on the filesystem. It accepts a `pattern` of entry files to match using glob patterns supported by [micromatch](https://github.com/micromatch/micromatch#matching-features), and a base file path of where your files are located. Each entry's `id` will be automatically generated from its file name. Use this loader when you have one file per entry. +### The `glob()` loader -The [`file()` loader](/en/reference/content-loader-reference/#file-loader) creates multiple entries from a single local file. Each entry in the file must have a unique `id` key property. It accepts a `base` file path to your file and optionally a [`parser` function](#parser-function) for data files it cannot parse automatically. Use this loader when your data file can be parsed as an array of objects. +The [`glob()` loader](/en/reference/content-loader-reference/#glob-loader) fetches entries from directories of Markdown, MDX, Markdoc, JSON, YAML, or TOML files from anywhere on the filesystem. If you store your content entries locally as separate files, such as a directory of blog posts, then the `glob()` loader is all you need to access your content. -```ts title="src/content.config.ts" {6,10} +This loader requires a `pattern` of entry files to match using glob patterns supported by [micromatch](https://github.com/micromatch/micromatch#matching-features), and a `base` file path of where your files are located. A unique `id` for each entry will be automatically generated from its file name, but you can [define custom IDs](#defining-custom-ids) if needed. + +```ts title="src/content.config.ts" {5} import { defineCollection } from 'astro:content'; -import { glob, file } from 'astro/loaders'; // Not available with legacy API -import { z } from 'astro/zod'; +import { glob } from 'astro/loaders'; const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }), - schema: /* ... */ }); + +export const collections = { blog }; +``` + +#### Defining custom IDs + +When using the [`glob()` loader](#the-glob-loader) with Markdown, MDX, Markdoc, JSON, or TOML files, every content entry [`id`](/en/reference/modules/astro-content/#id) is automatically generated in an URL-friendly format based on the content filename. This unique `id` is used to query the entry directly from your collection. It is also useful when [creating new pages and URLs from your content](#generating-routes-from-content). + +You can override a single entry’s generated `id` by adding your own `slug` property to the file frontmatter or data object for JSON files. This is similar to the “permalink” feature of other web frameworks. + +```md title="src/blog/1.md" {3} +--- +title: My Blog Post +slug: my-custom-id/supports/slashes +--- +Your blog post content here. +``` + +```json title="src/categories/1.json" {3} +{ + "title": "My Category", + "slug": "my-custom-id/supports/slashes", + "description": "Your category description here." +} +``` + +You can also pass options to the `glob()` loader's [`generateID()` helper function](/en/reference/content-loader-reference/#generateid) when you define your build-time collection to adjust how `id`s are generated. For example, you may wish to revert the default behavior of converting uppercase letters to lowercase for each collection entry: + +```js title="src/content.config.ts" +const authors = defineCollection({ + /* Retrieve all JSON files in your authors directory while retaining + * uppercase letters in the ID. */ + loader: glob({ + pattern: '**/*.json', + base: "./src/data/authors", + generateId: ({ entry }) => entry.replace(/\.json$/, ''), + }), +}); +``` + +### The `file()` loader + +The [`file()` loader](/en/reference/content-loader-reference/#file-loader) fetches multiple entries from a single local file defined in your collection. The `file()` loader will automatically detect and parse (based on the file extension) a single array of objects from JSON and YAML files, and will treat each top-level table as an independent entry in TOML files. + +```ts title="src/content.config.ts" {5} +import { defineCollection } from 'astro:content'; +import { file } from 'astro/loaders'; + const dogs = defineCollection({ loader: file("src/data/dogs.json"), - schema: /* ... */ }); -const probes = defineCollection({ - // `loader` can accept an array of multiple patterns as well as string patterns - // Load all markdown files in the space-probes directory, except for those that start with "voyager-" - loader: glob({ pattern: ['*.md', '!voyager-*'], base: 'src/data/space-probes' }), - schema: z.object({ - name: z.string(), - type: z.enum(['Space Probe', 'Mars Rover', 'Comet Lander']), - launch_date: z.date(), - status: z.enum(['Active', 'Inactive', 'Decommissioned']), - destination: z.string(), - operator: z.string(), - notable_discoveries: z.array(z.string()), - }), -}); +export const collections = { dogs }; +``` + +Each entry object in the file must have a unique `id` key property so that the entry can be identified and queried. Unlike the `glob()` loader, the `file()` loader will not automatically generate IDs for each entry. -export const collections = { blog, dogs, probes }; +You can provide your entries as an array of objects with an `id` property, or in object form where the unique `id` is the key: + +```json title="src/data/dogs.json" +// Specify an `id` property in each object of an array +[ + { "id": "poodle", "coat": "curly", "shedding": "low" }, + { "id": "afghan", "coat": "short", "shedding": "low" } +] ``` -##### `parser` function +```json title="src/data/dogs.json" +// Each key will be used as the `id` +{ + "poodle": { "coat": "curly", "shedding": "low" }, + "afghan": { "coat": "silky", "shedding": "low" } +} +``` -The `file()` loader accepts a second argument that defines a `parser` function. This allows you to specify a custom parser (e.g. `csv-parse`) to create a collection from a file's contents. +#### Parsing other data formats -The `file()` loader will automatically detect and parse (based on their file extension) a single array of objects from JSON and YAML files, and will treat each top-level table as an independent entry in TOML files. Support for these file types is built-in, and there is no need for a `parser` unless you have a [nested JSON document](#nested-json-documents). To use other files, such as `.csv`, you will need to create a parser function. +Support for parsing single JSON, YAML, and TOML files into collection entries withe the `file()` loader is built-in (unless you have a [nested JSON document](#nested-json-documents)). To load your collection from unsupported file types, such as `.csv`, you will need to create a [parser function](/en/reference/content-loader-reference/#parser). -The following example shows importing a CSV parser, then loading a `cats` collection into your project by passing both a file path and `parser` function to the `file()` loader: +The following example shows importing a third-party CSV parser then passing a custom `parser` function to the `file()` loader: -```typescript title="src/content.config.ts" +```typescript title="src/content.config.ts" {3} "parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true })" import { defineCollection } from "astro:content"; import { file } from "astro/loaders"; import { parse as parseCsv } from "csv-parse/sync"; const cats = defineCollection({ - loader: file("src/data/cats.csv", { parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true })}) + loader: file("src/data/cats.csv", { + parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true }), + }), }); ``` -###### Nested `.json` documents +##### Nested `.json` documents -The `parser` argument also allows you to load a single collection from a nested JSON document. For example, this JSON file contains multiple collections: +The `parser()` argument can be used to load a single collection from a nested JSON document. For example, this JSON file contains multiple collections: ```json title="src/data/pets.json" {"dogs": [{}], "cats": [{}]} ``` -You can separate these collections by passing a custom `parser` to the `file()` loader for each collection: +You can separate these collections by passing a custom `parser()` function to the `file()` loader for each collection, using Astro's built-in JSON parsing: ```typescript title="src/content.config.ts" +import { file } from "astro/loaders"; +import { defineCollection } from "astro:content"; + const dogs = defineCollection({ loader: file("src/data/pets.json", { parser: (text) => JSON.parse(text).dogs }) }); @@ -161,48 +294,35 @@ const cats = defineCollection({ }); ``` -#### Building a custom loader - -You can build a custom loader to fetch remote content from any data source, such as a CMS, a database, or an API endpoint. - -Using a loader to fetch your data will automatically create a collection from your remote data. This gives you all the benefits of local collections, such as collection-specific API helpers such as `getCollection()` and `render()` to query and display your data, as well as schema validation. - -:::tip -Find community-built and third-party loaders in the [Astro integrations directory](https://astro.build/integrations/?search=&categories%5B%5D=loaders). -::: - -##### Inline loaders +### Custom build-time loaders -You can define a loader inline, inside your collection, as an async function that returns an array of entries. +You can [build a custom loader](/en/reference/content-loader-reference/#building-a-loader) using the Content Loader API to fetch remote content from any data source, such as a CMS, a database, or an API endpoint. -This is useful for loaders that don't need to manually control how the data is loaded and stored. Whenever the loader is called, it will clear the store and reload all the entries. +Then you can import and define your custom loader in your collections config, passing any required values: ```ts title="src/content.config.ts" -const countries = defineCollection({ - loader: async () => { - const response = await fetch("https://restcountries.com/v3.1/all"); - const data = await response.json(); - // Must return an array of entries with an id property, or an object with IDs as keys and entries as values - return data.map((country) => ({ - id: country.cca3, - ...country, - })); - }, - schema: /* ... */ +import { defineCollection } from 'astro:content'; +import { myLoader } from './loader.ts'; + +const blog = defineCollection({ + loader: myLoader({ + url: "https://api.example.com/posts", + apiKey: "my-secret", + }), }); ``` -The returned entries are stored in the collection and can be queried using the `getCollection()` and `getEntry()` functions. - -##### Loader objects +:::tip +Find community-built and third-party loaders in the [Astro integrations directory](https://astro.build/integrations/?search=&categories%5B%5D=loaders). +::: -For more control over the loading process, you can use the Content Loader API to create a loader object. For example, with access to the `load` method directly, you can create a loader that allows entries to be updated incrementally or clears the store only when necessary. +Using a custom loader to fetch your data will automatically create a collection from your remote data. This gives you all the benefits of local collections, including collection-specific API helpers such as `getCollection()` and `render()` to [query and display your data](#querying-build-time-collections), as well as schema validation. -Similar to creating an Astro integration or Vite plugin, you can [distribute your loader as an NPM package](/en/reference/publish-to-npm/) that others can use in their projects. +Similar to creating an Astro integration or Vite plugin, you can [distribute your loader as an npm package](/en/reference/publish-to-npm/) that others can use in their projects. -See the full [Content Loader API](/en/reference/content-loader-reference/) and examples of how to build your own loader. +See the full [Content Loader API](/en/reference/content-loader-reference/) for examples of how to build your own loader. -### Defining the collection schema +## Defining the collection schema Schemas enforce consistent frontmatter or entry data within a collection through Zod validation. A schema **guarantees** that this data exists in a predictable form when you need to reference or query it. If any file violates its collection schema, Astro will provide a helpful error to let you know. @@ -212,12 +332,12 @@ Schemas also power Astro's automatic TypeScript typings for your content. When y In order for Astro to recognize a new or updated schema, you may need to restart the dev server or [sync the content layer](/en/reference/cli-reference/#astro-dev) (s + enter) to define the `astro:content` module. ::: -Every frontmatter or data property of your collection entries must be defined using a [Zod data type](/en/reference/modules/astro-zod/#common-data-type-validators): +Providing a `schema` is optional, but highly recommended! If you choose to use a schema, then every frontmatter or data property of your collection entries must be defined using a [Zod data type](/en/reference/modules/astro-zod/#common-data-type-validators): ```ts title="src/content.config.ts" {7-12,16-20} import { defineCollection } from 'astro:content'; -import { glob, file } from 'astro/loaders'; import { z } from 'astro/zod'; +import { glob, file } from 'astro/loaders'; const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }), @@ -240,23 +360,23 @@ const dogs = defineCollection({ export const collections = { blog, dogs }; ``` -#### Defining datatypes with Zod +### Defining datatypes with Zod -Astro uses [Zod](https://github.com/colinhacks/zod) to power its content schemas. With Zod, Astro is able to validate every file's data within a collection *and* provide automatic TypeScript types when you go to query content from inside your project. +Astro uses [Zod](https://github.com/colinhacks/zod) to power its content schemas. With Zod, Astro is able to validate every file's data within a collection *and* provide automatic TypeScript types when you query content from inside your project. -To use Zod in Astro, import the `z` utility from `"astro/zod"`. This is a re-export of the Zod library, and it supports all of the features of Zod. +To use Zod in Astro, import the `z` utility from `"astro/zod"`. This is a re-export of the Zod library, and it supports all of the features of Zod 4. See the [`z` utility reference](/en/reference/modules/astro-zod/) for a cheatsheet of common datatypes and to learn how Zod works and what features are available. -##### Zod schema methods +#### Zod schema methods All [Zod schema methods](/en/reference/modules/astro-zod/#using-zod-methods) (e.g. `.parse()`, `.transform()`) are available, with some limitations. Notably, performing custom validation checks on images using `image().refine()` is unsupported. -#### Defining collection references +### Defining collection references Collection entries can also "reference" other related entries. -With the [`reference()` function](/en/reference/modules/astro-content/#reference) from the Collections API, you can define a property in a collection schema as an entry from another collection. For example, you can require that every `space-shuttle` entry includes a `pilot` property which uses the `pilot` collection's own schema for type checking, autocomplete, and validation. +With the [`reference()` function](/en/reference/modules/astro-content/#reference), you can define a property in a collection schema as an entry from another collection. For example, you can require that every `space-shuttle` entry includes a `pilot` property which uses the `pilot` collection's own schema for type checking, autocomplete, and validation. A common example is a blog post that references reusable author profiles stored as JSON, or related post URLs stored in the same collection: @@ -266,18 +386,18 @@ import { glob } from 'astro/loaders'; import { z } from 'astro/zod'; const blog = defineCollection({ - loader: glob({ pattern: '**/[^_]*.md', base: "./src/data/blog" }), + loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), schema: z.object({ title: z.string(), // Reference a single author from the `authors` collection by `id` author: reference('authors'), - // Reference an array of related posts from the `blog` collection by `slug` + // Reference an array of related posts from the `blog` collection by `id` relatedPosts: z.array(reference('blog')), }) }); const authors = defineCollection({ - loader: glob({ pattern: '**/[^_]*.json', base: "./src/data/authors" }), + loader: glob({ pattern: '**/*.json', base: "./src/data/authors" }), schema: z.object({ name: z.string(), portfolio: z.string().url(), @@ -301,38 +421,17 @@ relatedPosts: These references will be transformed into objects containing a `collection` key and an `id` key, allowing you to easily [query them in your templates](/en/guides/content-collections/#accessing-referenced-data). -### Defining custom IDs - -When using the `glob()` loader with Markdown, MDX, Markdoc, or JSON files, every content entry [`id`](/en/reference/modules/astro-content/#id) is automatically generated in an URL-friendly format based on the content filename. The `id` is used to query the entry directly from your collection. It is also useful when creating new pages and URLs from your content. - -You can override an entry’s generated `id` by adding your own `slug` property to the file frontmatter or data object for JSON files. This is similar to the “permalink” feature of other web frameworks. - -```md title="src/blog/1.md" {3} ---- -title: My Blog Post -slug: my-custom-id/supports/slashes ---- -Your blog post content here. -``` - -```json title="src/categories/1.json" {3} -{ - "title": "My Category", - "slug": "my-custom-id/supports/slashes", - "description": "Your category description here." -} -``` - -## Querying Collections +## Querying build-time collections -Astro provides helper functions to query a collection and return one (or more) content entries. +Astro provides helper functions to query a build-time collection and return one or more content entries. - [`getCollection()`](/en/reference/modules/astro-content/#getcollection) fetches an entire collection and returns an array of entries. - [`getEntry()`](/en/reference/modules/astro-content/#getentry) fetches a single entry from a collection. These return entries with a unique `id`, a `data` object with all defined properties, and will also return a `body` containing the raw, uncompiled body of a Markdown, MDX, or Markdoc document. -```js +```astro title="src/pages/index.astro" +--- import { getCollection, getEntry } from 'astro:content'; // Get all entries from a collection. @@ -342,8 +441,7 @@ const allBlogPosts = await getCollection('blog'); // Get a single entry from a collection. // Requires the name of the collection and `id` const poodleData = await getEntry('dogs', 'poodle'); - - +--- ``` The sort order of generated collections is non-deterministic and platform-dependent. This means that if you are calling `getCollection()` and need your entries returned in a specific order (e.g. blog posts sorted by date), you must sort the collection entries yourself: @@ -362,7 +460,9 @@ const posts = (await getCollection('blog')).sort( ### Using content in Astro templates -After querying your collections, you can access each entry's content directly inside of your Astro component template. For example, you can create a list of links to your blog posts, displaying information from your entry's frontmatter using the `data` property. +After querying your collections, you can access each entry's content and metadata directly inside of your Astro component template. + +For example, you can create a list of links to your blog posts, displaying information from your entry's frontmatter using the `data` property: ```astro title="src/pages/index.astro" @@ -377,21 +477,20 @@ const posts = await getCollection('blog'); ))} ``` -#### Rendering body content -Once queried, you can render Markdown and MDX entries to HTML using the [`render()`](/en/reference/modules/astro-content/#render) function property. Calling this function gives you access to rendered HTML content, including both a `` component and a list of all rendered headings. +### Rendering body content -```astro title="src/pages/blog/post-1.astro" {5,8} +Once queried, you can render Markdown and MDX entries to HTML using the [`render()`](/en/reference/modules/astro-content/#render) function from `astro:content`. Calling this function gives you access to rendered HTML content, including both a `` component and a list of all rendered headings. + +```astro title="src/pages/blog/post-1.astro" {2,9} --- import { getEntry, render } from 'astro:content'; const entry = await getEntry('blog', 'post-1'); -if (!entry) { - // Handle Error, for example: - throw new Error('Could not find blog post 1'); -} -const { Content, headings } = await render(entry); + +const { Content } = await render(entry); --- +

{entry.data.title}

Published on: {entry.data.published.toDateString()}

``` @@ -422,48 +521,60 @@ const { post } = Astro.props; You can use this to filter by any content criteria you like. For example, you can filter by properties like `draft` to prevent any draft blog posts from publishing to your blog: -```js +```astro title="src/pages/blog.astro" +--- // Example: Filter out content entries with `draft: true` import { getCollection } from 'astro:content'; const publishedBlogEntries = await getCollection('blog', ({ data }) => { return data.draft !== true; }); +--- ``` You can also create draft pages that are available when running the dev server, but not built in production: -```js +```astro title="src/pages/blog.astro" +--- // Example: Filter out content entries with `draft: true` only when building for production import { getCollection } from 'astro:content'; const blogEntries = await getCollection('blog', ({ data }) => { return import.meta.env.PROD ? data.draft !== true : true; }); +--- ``` The filter argument also supports filtering by nested directories within a collection. Since the `id` includes the full nested path, you can filter by the start of each `id` to only return items from a specific nested directory: -```js +```astro title="src/pages/blog.astro" +--- // Example: Filter entries by sub-directory in the collection import { getCollection } from 'astro:content'; const englishDocsEntries = await getCollection('docs', ({ id }) => { return id.startsWith('en/'); }); +--- ``` ### Accessing referenced data -Any [references defined in your schema](/en/guides/content-collections/#defining-collection-references) must be queried separately after first querying your collection entry. Since the [`reference()` function](/en/reference/modules/astro-content/#reference) transforms a reference to an object with `collection` and `id` as keys, you can use the `getEntry()` function to return a single referenced item, or `getEntries()` to retrieve multiple referenced entries from the returned `data` object. +To access [references defined in your schema](/en/guides/content-collections/#defining-collection-references), first query your collection entry. Your references will be available on the returned `data` object. (e.g. `entry.data.author` and `entry.data.relatedPosts`) -```astro title="src/pages/blog/welcome.astro" +Then, you can use the `getEntry()` function again (or `getEntries()` to retrieve multiple referenced entries) by passing those returned values. The `reference()` function in your schema transforms those values into one or more `collection` and `id` objects as a convenient way to query this related data. + + +```astro title="src/pages/blog/adventures-in-space.astro" --- import { getEntry, getEntries } from 'astro:content'; -const blogPost = await getEntry('blog', 'welcome'); +// First, query a blog post +const blogPost = await getEntry('blog', 'Adventures in Space'); -// Resolve a singular reference (e.g. `{collection: "authors", id: "ben-holmes"}`) +// Retrieve a single reference item: the blog post's author +// Equivalent to querying `{collection: "authors", id: "ben-holmes"}` const author = await getEntry(blogPost.data.author); -// Resolve an array of references -// (e.g. `[{collection: "blog", id: "about-me"}, {collection: "blog", id: "my-year-in-review"}]`) + +// Retrieve an array of referenced items: all the related posts +// Equivalent to querying `[{collection: "blog", id: "visiting-mars"}, {collection: "blog", id: "leaving-earth-for-the-first-time"}]` const relatedPosts = await getEntries(blogPost.data.relatedPosts); --- @@ -480,17 +591,17 @@ const relatedPosts = await getEntries(blogPost.data.relatedPosts); ## Generating Routes from Content -Content collections are stored outside of the `src/pages/` directory. This means that no pages or routes are generated for your collection items by default. +Content collections are stored outside of the `src/pages/` directory. This means that no pages or routes are generated for your collection items by default by Astro's [file-based routing](/en/guides/routing/). -You will need to manually create a new [dynamic route](/en/guides/routing/#dynamic-routes) if you want to generate HTML pages for each of your collection entries, such as individual blog posts. Your dynamic route will map the incoming request param (e.g. `Astro.params.slug` in `src/pages/blog/[...slug].astro`) to fetch the correct entry for each page. +You will need to manually create a new [dynamic route](/en/guides/routing/#dynamic-routes) if you want to generate HTML pages for each of your collection entries, such as individual blog posts. Your dynamic route will map the incoming request param (e.g. `Astro.params.id` in `src/pages/blog/[...id].astro`) to fetch the correct entry for each page. The exact method for generating routes will depend on whether your pages are prerendered (default) or rendered on demand by a server. ### Building for static output (default) -If you are building a static website (Astro's default behavior), use the [`getStaticPaths()`](/en/reference/routing-reference/#getstaticpaths) function to create multiple pages from a single page component (e.g. `src/pages/[slug]`) during your build. +If you are building a static website (Astro's default behavior) with build-time collections, use the [`getStaticPaths()`](/en/reference/routing-reference/#getstaticpaths) function to create multiple pages from a single page component (e.g. `src/pages/[id].astro`) during your build. -Call `getCollection()` inside of `getStaticPaths()` to have your collection data available for building static routes. Then, create the individual URL paths using the `id` property of each content entry. Each page is passed the entire collection entry as a prop for [use in your page template](#using-content-in-astro-templates). +Call `getCollection()` inside of `getStaticPaths()` to have your collection data available for building static routes. Then, create the individual URL paths using the `id` property of each content entry. Each page receives the entire collection entry as a prop for [use in your page template](#using-content-in-astro-templates). ```astro title="src/pages/posts/[id].astro" "{ id: post.id }" "{ post }" --- @@ -514,28 +625,37 @@ const { Content } = await render(post); This will generate a page route for every entry in the `blog` collection. For example, an entry at `src/blog/hello-world.md` will have an `id` of `hello-world`, and therefore its final URL will be `/posts/hello-world/`. :::note -If your custom slugs contain the `/` character to produce URLs with multiple path segments, you must use a [rest parameter (e.g. `[...slug]`)](/en/guides/routing/#rest-parameters) in the `.astro` filename for this dynamic routing page. +If your custom slugs contain the `/` character to produce URLs with multiple path segments, you must use a [rest parameter (e.g. `[...id]`)](/en/guides/routing/#rest-parameters) in the `.astro` filename for this dynamic routing page. ::: -### Building for server output (SSR) +### Building routes on demand at request time + +With an adapter installed for [on-demand rendering](/en/guides/on-demand-rendering/), you can generate your dynamic page routes at request time. First, examine the request (using `Astro.request` or `Astro.params`) to find the slug on demand, and then fetch it using one of Astro's content collection helper functions: -If you are building a dynamic website (using Astro's SSR support), you are not expected to generate any paths ahead of time during the build. Instead, your page should examine the request (using `Astro.request` or `Astro.params`) to find the `slug` on-demand, and then fetch it using [`getEntry()`](/en/reference/modules/astro-content/#getentry). +- [`getEntry()`](/en/reference/modules/astro-content/#getentry) for build-time collection pages that are generated once, upon first request. +- [`getLiveEntry()`](/en/reference/modules/astro-content/#getentry) for live collection pages where data is (re)fetched at each request time. ```astro title="src/pages/posts/[id].astro" --- +export const prerender = false; // Not needed in 'server' mode + import { getEntry, render } from "astro:content"; + // 1. Get the slug from the incoming server request const { id } = Astro.params; if (id === undefined) { return Astro.redirect("/404"); } + // 2. Query for the entry directly using the request slug const post = await getEntry("blog", id); + // 3. Redirect if the entry does not exist if (post === undefined) { return Astro.redirect("/404"); } + // 4. Render the entry to HTML in the template const { Content } = await render(post); --- @@ -544,10 +664,210 @@ const { Content } = await render(post); ``` :::tip -Explore the `src/pages/` folder of the [blog tutorial demo code on GitHub](https://github.com/withastro/blog-tutorial-demo/tree/content-collections/src/pages) to see full examples of creating pages from your collections for blog features like a list of blog posts, tags pages, and more! +Explore the `src/pages/` folder of the [blog tutorial demo code on GitHub](https://github.com/withastro/blog-tutorial-demo/tree/content-collections/src/pages) to see full examples of creating dynamic pages from your collections for blog features like a list of blog posts, tags pages, and more! ::: -## Collection JSON Schemas +## Live content collections + +Live collections use a different API than build-time content collections, although the configuration and helper functions are designed to feel familiar. + +Key differences include: + +1. **Execution time**: Run at request time instead of build time +2. **Configuration file**: Use `src/live.config.ts` instead of `src/content.config.ts` +3. **Collection definition**: Use `defineLiveCollection()` instead of `defineCollection()` +4. **Loader API**: Implement `loadCollection` and `loadEntry` methods instead of the `load` method +5. **Data return**: Return data directly instead of storing in the data store +6. **User-facing functions**: Use `getLiveCollection()`/`getLiveEntry()` instead of `getCollection()`/`getEntry()` + +Additionally, you must have an adapter configured for [on-demand rendering](/en/guides/on-demand-rendering/) of live collection data. + +Define your live collections in the special file `src/live.config.ts` (separate from your `src/content.config.ts` for build-time collections, if you have one). + +Each individual collection configures: +- a [live `loader`](#creating-a-live-loader) for your data source, and optionally for type safety (required) +- a [live collection `schema`](#using-zod-schemas-with-live-collections) for type safety (optional) + +Unlike for build-time collections, there are no built-in live loaders available. You will need to [create a custom live loader](#creating-a-live-loader) for your specific data source or find a third-party loader to pass to your live collection's `loader` property. + +You can optionally [include type safety in your live loaders](/en/reference/content-loader-reference/#the-liveloader-object). Therefore, [defining a Zod `schema`](#using-zod-schemas-with-live-collections) for live collections is optional. However, if you provide one, it will take precedence over the live loader's types. + +```ts title="src/live.config.ts" +// Define live collections for accessing real-time data +import { defineLiveCollection } from 'astro:content'; +import { storeLoader } from '@mystore/astro-loader'; + +const products = defineLiveCollection({ + loader: storeLoader({ + apiKey: process.env.STORE_API_KEY, + endpoint: 'https://api.mystore.com/v1', + }), +}); + +// Export a single `collections` object to register your collection(s) +export const collections = { products }; +``` + +You can then use the dedicated `getLiveCollection()` and `getLiveEntry()` functions to [access your live data](#accessing-live-data) and render your content. + +You can [generate page routes](#generating-routes-from-content) from your live collection entries on demand, fetching your data fresh at runtime upon each request without needing a rebuild of your site like [build-time collections](#defining-build-time-content-collections) do. This is useful when accessing live, up-to-the-moment data is more important than having your content available in a performant data storage layer that persists between site builds. + +### Creating a live loader + +You can build a custom [live loader](/en/reference/content-loader-reference/#live-loaders) using the Live Loader API to fetch remote content fresh upon request from any data source, such as a CMS, a database or an API endpoint. You will have to tell your live loader how to fetch and return content entries from your desired data source, as well as provide error handling for unsuccessful data requests. + +Using a live loader to fetch your data will automatically create a collection from your remote data. This gives you all the benefits of Astro's content collections, including collection-specific API helpers such as `getLiveCollection()` and `render()` to [query and display your data](#querying-build-time-collections), as well as helpful error handling. + +:::tip +Find community-built and third-party live loaders in the [Astro integrations directory](https://astro.build/integrations/?search=&categories%5B%5D=loaders). +::: + +See the basics of [building a live loader](/en/reference/content-loader-reference/#building-a-live-loader) using the Live Loader API + +### Using Zod schemas with live collections + +You can use Zod schemas with live collections to validate and transform data at runtime. This Zod validation works the same way as [schemas for build-time collections](#defining-the-collection-schema). + +When you define a schema for a live collection, it takes precedence over [the live loader's types](/en/reference/content-loader-reference/#the-liveloader-object) when you query the collection: + +```ts title="src/live.config.ts" +import { z, defineLiveCollection } from 'astro:content'; +import { apiLoader } from './loaders/api-loader'; + +const products = defineLiveCollection({ + loader: apiLoader({ endpoint: process.env.API_URL }), + schema: z + .object({ + id: z.string(), + name: z.string(), + price: z.number(), + // Transform the API's category format + category: z.string().transform((str) => str.toLowerCase().replace(/\s+/g, '-')), + // Coerce the date to a Date object + createdAt: z.coerce.date(), + }) + .transform((data) => ({ + ...data, + // Add a formatted price field + displayPrice: `$${data.price.toFixed(2)}`, + })), +}); + +export const collections = { products }; +``` + +When using Zod schemas with live collections, validation errors are automatically caught and returned as `AstroError` objects: + +```astro title="src/pages/store/index.astro" +--- +export const prerender = false; // Not needed in 'server' mode + +import { LiveCollectionValidationError } from 'astro/content/runtime'; +import { getLiveEntry } from 'astro:content'; + +const { entry, error } = await getLiveEntry('products', '123'); + +// You can handle validation errors specifically +if (LiveCollectionValidationError.is(error)) { + console.error(error.message); + return Astro.rewrite('/500'); +} + +// TypeScript knows entry.data matches your Zod schema, not the loader's type +console.log(entry?.data.displayPrice); // e.g., "$29.99" +--- +``` + +See [Zod's README](https://github.com/colinhacks/zod) for complete documentation on how Zod works and what features are available. + + +### Accessing live data + +Astro provides live collection helper functions to access live data on each request and return one (or more) content entries. These can be used similarly to their [build-time collection counterparts](#querying-build-time-collections). + +- [`getLiveCollection()`](/en/reference/modules/astro-content/#getlivecollection) fetches an entire collection and returns an array of entries. +- [`getLiveEntry()`](/en/reference/modules/astro-content/#getliveentry) fetches a single entry from a collection. + +These return entries with a unique `id`, and `data` object with all defined properties from the live loader. When using third-party or community loaders distributed as npm packages, check their own documentation for the expected shape of data returned. + +You can use these functions to access your live data, passing the name of the collection and optionally filtering conditions. + +```astro title="src/pages/store/[slug].astro" +--- +export const prerender = false; // Not needed in 'server' mode + +import { getLiveCollection, getLiveEntry } from 'astro:content'; + +// Use loader-specific filters +const { entries: draftArticles } = await getLiveCollection('articles', { + status: 'draft', + author: 'john-doe', +}); + +// Get a specific product by ID +const { entry: product } = await getLiveEntry('products', Astro.params.slug); +--- +``` + +#### Rendering content + +If your live loader [returns a `rendered` property](/en/reference/content-loader-reference/#rendered-1), you can use [the `render()` function and `` component](#rendering-body-content) to render your content directly in your pages, using the same method as build-time collections. + +You also have access to any [error returned by the live loader](/en/reference/content-loader-reference/#error-handling-in-live-loaders), for example, to rewrite to a 404 page when content cannot be displayed: + +```astro title="src/pages/store/[id].astro" "render(entry)" "" +--- +export const prerender = false; // Not needed in 'server' mode + +import { getLiveEntry, render } from 'astro:content'; +const { entry, error } = await getLiveEntry('articles', Astro.params.id); +if (error) { + return Astro.rewrite('/404'); +} + +const { Content } = await render(entry); +--- + +

{entry.data.title}

+ +``` + +#### Error handling + +Live loaders can fail due to network issues, API errors, or validation problems. The API is designed to make error handling explicit. + +When you call `getLiveCollection()` or `getLiveEntry()`, the error will be one of: + +- The error type defined by the loader (if it returned an error) +- A `LiveEntryNotFoundError` if the entry was not found +- A `LiveCollectionValidationError` if the collection data does not match the expected schema +- A `LiveCollectionCacheHintError` if the cache hint is invalid +- A `LiveCollectionError` for other errors, such as uncaught errors thrown in the loader + +You can use `instanceof` to check the type of an error at runtime: + +```astro title="src/pages/store/[id].astro" "LiveEntryNotFoundError.is(error)" +--- +export const prerender = false; // Not needed in 'server' mode + +import { LiveEntryNotFoundError } from 'astro/content/runtime'; +import { getLiveEntry } from 'astro:content'; + +const { entry, error } = await getLiveEntry('products', Astro.params.id); + +if (error) { + if (error instanceof LiveEntryNotFoundError) { + console.error(`Product not found: ${error.message}`); + Astro.response.status = 404; + } else { + console.error(`Error loading product: ${error.message}`); + return Astro.redirect('/500'); + } +} +--- +``` + +## Using JSON Schema files in your editor

@@ -556,7 +876,7 @@ Astro auto-generates [JSON Schema](https://json-schema.org/) files for collectio A JSON Schema file is generated for each collection in your project and output to the `.astro/collections/` directory. For example, if you have two collections, one named `authors` and another named `posts`, Astro will generate `.astro/collections/authors.schema.json` and `.astro/collections/posts.schema.json`. -### Use JSON Schemas in JSON files +

Use JSON Schemas in JSON files

You can manually point to an Astro-generated schema by setting the `$schema` field in your JSON file. The value should be a relative file path from the data file to the schema. @@ -570,9 +890,9 @@ In the following example, a data file in `src/data/authors/` uses the schema gen } ``` -#### Use a schema for a group of JSON files in VS Code +

Use a schema for a group of JSON files in VS Code

-In VS Code, you can configure a schema to apply for all files in a collection using the [`json.schemas` setting](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings). +In VS Code, you can configure a schema to apply to all files in a collection using the [`json.schemas` setting](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings). In the following example, all files in the `src/data/authors/` directory will use the schema generated for the `authors` collection: ```json @@ -586,7 +906,7 @@ In the following example, all files in the `src/data/authors/` directory will us } ``` -### Use schemas in YAML files in VS Code +

Use schemas in YAML files in VS Code

In VS Code, you can add support for using JSON schemas in YAML files using the [Red Hat YAML](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) extension. With this extension installed, you can reference a schema in a YAML file using a special comment syntax: @@ -599,9 +919,9 @@ skills: - Starlight ``` -#### Use schemas for a group of YAML files in VS Code +

Use schemas for a group of YAML files in VS Code

-With the Red Hat YAML extension, you can configure a schema to apply for all YAML files in a collection using the `yaml.schemas` setting. +With the Red Hat YAML extension, you can configure a schema to apply to all YAML files in a collection using the `yaml.schemas` setting. In the following example, all YAML files in the `src/data/authors/` directory will use the schema generated for the `authors` collection: ```json @@ -613,31 +933,3 @@ In the following example, all YAML files in the `src/data/authors/` directory wi ``` See [“Associating schemas”](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml#associating-schemas) in the Red Hat YAML extension documentation for more details. - -## When to create a collection - -You can [create a collection](#defining-collections) any time you have a group of related data or content that shares a common structure. - -Much of the benefit of using collections comes from: - -- Defining a common data shape to validate that an individual entry is "correct" or "complete", avoiding errors in production. -- Content-focused APIs designed to make querying intuitive (e.g. `getCollection()` instead of `import.meta.glob()`) when importing and rendering content on your pages. -- A [Content Loader API](/en/reference/content-loader-reference/) for retrieving your content that provides both built-in loaders and access to the low-level API. There are several third-party and community-built loaders available, and you can build your own custom loader to fetch data from anywhere. -- Performance and scalability. The Content Layer API allows data to be cached between builds and is suitable for tens of thousands of content entries. - -[Define your data](#defining-collections) as a collection when: - -- You have multiple files or data to organize that share the same overall structure (e.g. blog posts written in Markdown which all have the same frontmatter properties). -- You have existing content stored remotely, such as in a CMS, and want to take advantage of the collections helper functions and Content Layer API instead of using `fetch()` or SDKs. -- You need to fetch (tens of) thousands of related pieces of data, and need a querying and caching method that handles at scale. - -### When not to create a collection - -Collections provide excellent structure, safety, and organization when you have **multiple pieces of content that must share the same properties**. - -Collections **may not be your solution** if: - -- You have only one or a small number of different pages. Consider [making individual page components](/en/basics/astro-pages/) such as `src/pages/about.astro` with your content directly instead. -- You are displaying files that are not processed by Astro, such as PDFs. Place these static assets in the [`public/` directory](/en/basics/project-structure/#public) of your project instead. -- Your data source has its own SDK/client library for imports that is incompatible with or does not offer a content loader and you prefer to use it directly. -- You are using APIs that need to be updated in real time. Content collections are only updated at build time, so if you need live data, use other methods of [importing files](/en/guides/imports/#import-statements) or [fetching data](/en/guides/data-fetching/) with [on-demand rendering](/en/guides/on-demand-rendering/). diff --git a/src/content/docs/en/guides/deploy/microsoft-azure.mdx b/src/content/docs/en/guides/deploy/microsoft-azure.mdx index 9eba99e7c30ac..889968503d19d 100644 --- a/src/content/docs/en/guides/deploy/microsoft-azure.mdx +++ b/src/content/docs/en/guides/deploy/microsoft-azure.mdx @@ -40,7 +40,7 @@ The GitHub action yaml that is created for you assumes the use of node 14. This ``` "engines": { - "node": ">=18.0.0" + "node": ">=22.12.0" }, ``` diff --git a/src/content/docs/en/guides/deploy/netlify.mdx b/src/content/docs/en/guides/deploy/netlify.mdx index 1d59154bafb77..df5016fe5d20d 100644 --- a/src/content/docs/en/guides/deploy/netlify.mdx +++ b/src/content/docs/en/guides/deploy/netlify.mdx @@ -101,7 +101,7 @@ You can also create a new site on Netlify and link up your Git repository by ins ### Set a Node.js version -If you are using a legacy [build image](https://docs.netlify.com/configure-builds/get-started/#build-image-selection) (Xenial) on Netlify, make sure that your Node.js version is set. Astro requires `v18.20.8` or `v20.3.0` or higher. +If you are using a legacy [build image](https://docs.netlify.com/configure-builds/get-started/#build-image-selection) (Xenial) on Netlify, make sure that your Node.js version is set. Astro requires `v22.12.0` or higher. You can [specify your Node.js version in Netlify](https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript) using: - a [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc) file in your base directory. diff --git a/src/content/docs/en/guides/endpoints.mdx b/src/content/docs/en/guides/endpoints.mdx index 3329a1bd64dad..51f9c9c65904b 100644 --- a/src/content/docs/en/guides/endpoints.mdx +++ b/src/content/docs/en/guides/endpoints.mdx @@ -47,6 +47,8 @@ import type { APIRoute } from "astro"; export const GET: APIRoute = async ({ params, request }) => {...} ``` +Note that endpoints whose URLs include a file extension (e.g. `src/pages/sitemap.xml.ts`) can only be accessed without a trailing slash (e.g. `/sitemap.xml`), regardless of your [`build.trailingSlash`](/en/reference/configuration-reference/#trailingslash) configuration. + ### `params` and Dynamic routing Endpoints support the same [dynamic routing](/en/guides/routing/#dynamic-routes) features that pages do. Name your file with a bracketed parameter name and export a [`getStaticPaths()` function](/en/reference/routing-reference/#getstaticpaths). Then, you can access the parameter using the `params` property passed to the endpoint function: diff --git a/src/content/docs/en/guides/environment-variables.mdx b/src/content/docs/en/guides/environment-variables.mdx index af2b99b85fd9e..24d3eba46ba38 100644 --- a/src/content/docs/en/guides/environment-variables.mdx +++ b/src/content/docs/en/guides/environment-variables.mdx @@ -58,7 +58,6 @@ Astro includes a few environment variables out of the box: - `import.meta.env.DEV`: `true` if your site is running in development; `false` otherwise. Always the opposite of `import.meta.env.PROD`. - `import.meta.env.BASE_URL`: The base URL your site is being served from. This is determined by the [`base` config option](/en/reference/configuration-reference/#base). - `import.meta.env.SITE`: This is set to [the `site` option](/en/reference/configuration-reference/#site) specified in your project's `astro.config`. -- `import.meta.env.ASSETS_PREFIX`: The prefix for Astro-generated asset links if the [`build.assetsPrefix` config option](/en/reference/configuration-reference/#buildassetsprefix) is set. This can be used to create asset links not handled by Astro. Use them like any other environment variable. diff --git a/src/content/docs/en/guides/images.mdx b/src/content/docs/en/guides/images.mdx index 58ed5107a8a09..fa38b308a1ecb 100644 --- a/src/content/docs/en/guides/images.mdx +++ b/src/content/docs/en/guides/images.mdx @@ -51,7 +51,7 @@ All native HTML tags, including `` and ``, are also available in `.ast For all images in `.astro` files, **the value of the image `src` attribute is determined by the location of your image file**: - A local image from your project `src/` folder uses an import from the file's relative path. - + The image and picture components use the named import directly (e.g. `src={rocket}`), while the `` tag uses the `src` object property of the import (e.g. `src={rocket.src}`). - Remote and `public/` images use a URL path. @@ -321,7 +321,7 @@ import myImage from '../assets/my_image.png'; // Image is 1600x900 Responsive images are images that adjust to improve performance across different devices. These images can resize to fit their container, and can be served in different sizes depending on your visitor's screen size and resolution. -With [responsive image properties](/en/reference/modules/astro-assets/#responsive-image-properties) applied to the `` or `` components, Astro will automatically generate the required `srcset` and `sizes` values for your images, and apply the necessary [styles to ensure they resize correctly](#responsive-image-styles). +With the [layout property](/en/reference/modules/astro-assets/#layout) applied to the `` or `` components, Astro will automatically generate the required `srcset` and `sizes` values for your images, and apply the necessary [styles to ensure they resize correctly](#responsive-image-styles). When this responsive behavior is [configured globally with `image.layout`](/en/reference/configuration-reference/#imagelayout), it will apply to all image components and also to any local and remote images using [the Markdown `![]()` syntax](/en/guides/images/#images-in-markdown-files). @@ -396,7 +396,7 @@ The global styles applied by Astro will depend on the layout type, and are desig The styles use the [`:where()` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:where), which has a [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Specificity) of 0, meaning that it is easy to override with your own styles. Any CSS selector will have a higher specificity than `:where()`, so you can easily override the styles by adding your own styles to target the image. -You can override the `object-fit` and `object-position` styles on a per-image basis by setting the `fit` and `position` props on the `` or `` component. +You can override the `object-fit` and `object-position` styles on a per-image basis by setting the `fit` and `position` props on the `` or `` component. #### Responsive images with Tailwind 4 @@ -590,8 +590,8 @@ This is a blog post The `image` helper for the content collections schema lets you validate and import the image. ```ts title="src/content.config.ts" -import { defineCollection } from "astro:content"; -import { z } from "astro/zod"; +import { defineCollection } from 'astro:content'; +import { z } from 'astro/zod'; const blogCollection = defineCollection({ schema: ({ image }) => z.object({ diff --git a/src/content/docs/en/guides/imports.mdx b/src/content/docs/en/guides/imports.mdx index 2b7145046b4c6..132658cccef41 100644 --- a/src/content/docs/en/guides/imports.mdx +++ b/src/content/docs/en/guides/imports.mdx @@ -344,7 +344,7 @@ Additionally, glob patterns must begin with one of the following: ### `import.meta.glob()` vs `getCollection()` -[Content collections](/en/guides/content-collections/) provide a [`getCollection()` API](/en/reference/modules/astro-content/#getcollection) for loading multiple files instead of `import.meta.glob()`. If your content files (e.g. Markdown, MDX, Markdoc) are located in collections within the `src/content/` directory, use `getCollection()` to [query a collection](/en/guides/content-collections/#querying-collections) and return content entries. +[Content collections](/en/guides/content-collections/) provide [performant, content-focused APIs](/en/reference/modules/astro-content/) for loading multiple files instead of `import.meta.glob()`. Use `getCollection()` and `getLiveCollection()` to query your collections and return content entries. ## WASM diff --git a/src/content/docs/en/guides/integrations-guide/markdoc.mdx b/src/content/docs/en/guides/integrations-guide/markdoc.mdx index 9e6a0539580e1..755ee0da22de4 100644 --- a/src/content/docs/en/guides/integrations-guide/markdoc.mdx +++ b/src/content/docs/en/guides/integrations-guide/markdoc.mdx @@ -113,7 +113,7 @@ Markdoc files can only be used within content collections. Add entries to any co - quick-start.mdoc -Then, query your collection using the [Content Collection APIs](/en/guides/content-collections/#querying-collections): +Then, [query and display your posts and collections](/en/guides/content-collections/#querying-build-time-collections): ```astro title="src/pages/why-markdoc.astro" --- diff --git a/src/content/docs/en/guides/integrations-guide/mdx.mdx b/src/content/docs/en/guides/integrations-guide/mdx.mdx index 56cd324fb1165..c799f48f3ca69 100644 --- a/src/content/docs/en/guides/integrations-guide/mdx.mdx +++ b/src/content/docs/en/guides/integrations-guide/mdx.mdx @@ -96,9 +96,9 @@ It also adds extra features to standard MDX, including support for Markdown-styl `.mdx` files must be written in [MDX syntax](https://mdxjs.com/docs/what-is-mdx/#mdx-syntax) rather than Astro’s HTML-like syntax. -### Using MDX with content collections +### Using local MDX with content collections -To include MDX files in a content collection, make sure that your [collection loader](/en/guides/content-collections/#defining-the-collection-loader) is configured to load content from `.mdx` files: +To include your local MDX files in a content collection, make sure that your [collection loader](/en/guides/content-collections/#build-time-collection-loaders) is configured to load content from `.mdx` files: ```js title="src/content.config.ts" ins="mdx" import { defineCollection } from 'astro:content'; diff --git a/src/content/docs/en/guides/internationalization.mdx b/src/content/docs/en/guides/internationalization.mdx index 3180d449571b5..7fc1b1a3d3174 100644 --- a/src/content/docs/en/guides/internationalization.mdx +++ b/src/content/docs/en/guides/internationalization.mdx @@ -170,15 +170,11 @@ Set this option when all routes will have their `/locale/` prefix in their URL a - URLs without a locale prefix, (e.g. `example.com/about/`) will return a 404 (not found) status code unless you specify a [fallback strategy](#fallback). -### `redirectToDefaultLocale` +#### Opting out of redirects for the home URL -

+Even with your default locale routes prefixed, this behaviour does not apply by default to your site's index page. This allows you to have a home page that exists outside of your configured locale structure, where all of your localized routes are prefixed except the home URL of your site. -Configures whether or not the home URL (`/`) generated by `src/pages/index.astro` will redirect to `/`. - -Setting `prefixDefaultLocale: true` will also automatically set `redirectToDefaultLocale: true` in your `routing` config object. By default, the required `src/pages/index.astro` file will automatically redirect to the index page of your default locale. - -You can opt out of this behavior by [setting `redirectToDefaultLocale: false`](/en/reference/configuration-reference/#i18nroutingredirecttodefaultlocale). This allows you to have a site home page that exists outside of your configured locale folder structure. +You can opt out of this behavior so that your main site URL will also redirect to a prefixed, localized route for your default locale. When `prefixDefaultLocale: true` is set, you can additionally configure `redirectToDefaultLocale: true`. This will ensure that the home URL (`/`) generated by `src/pages/index.astro` will redirect to `/[defaultLocale]/`. ### `manual` diff --git a/src/content/docs/en/guides/markdown-content.mdx b/src/content/docs/en/guides/markdown-content.mdx index f189181302590..baff39e40e234 100644 --- a/src/content/docs/en/guides/markdown-content.mdx +++ b/src/content/docs/en/guides/markdown-content.mdx @@ -399,7 +399,7 @@ When using the frontmatter `layout` property, you must include the `See the [API reference for `IntegrationRouteData`](/en/reference/integrations-reference/#integrationroutedata-type-reference). +See the API reference for `IntegrationRouteData`. ### Changed: `distURL` is now an array (Integrations API) @@ -1139,7 +1139,7 @@ if (route.distURL) { } ``` -See the [API reference for `IntegrationRouteData`](/en/reference/integrations-reference/#integrationroutedata-type-reference). +See the API reference for `IntegrationRouteData`. ### Changed: Arguments passed to `app.render()` (Adapter API) diff --git a/src/content/docs/en/guides/upgrade-to/v6.mdx b/src/content/docs/en/guides/upgrade-to/v6.mdx new file mode 100644 index 0000000000000..8ab434f741830 --- /dev/null +++ b/src/content/docs/en/guides/upgrade-to/v6.mdx @@ -0,0 +1,1273 @@ +--- +title: Upgrade to Astro v6 +description: How to upgrade your project to Astro v6.0. +sidebar: + label: v6.0 +i18nReady: true +--- +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro' +import { Steps } from '@astrojs/starlight/components'; +import ReadMore from '~/components/ReadMore.astro' +import SourcePR from '~/components/SourcePR.astro' + +This guide will help you migrate from Astro v5 to Astro v6. + +Need to upgrade an older project to v5 first? See our [older migration guide](/en/guides/upgrade-to/v5/). + +Need to see the v5 docs? Visit this [older version of the docs site (unmaintained v5.xx snapshot)](https://v5.docs.astro.build/). + +## Upgrade Astro + +Update your project's version of Astro to the latest version using your package manager: + + + + ```shell + # Upgrade Astro and official integrations together + npx @astrojs/upgrade + ``` + + + ```shell + # Upgrade Astro and official integrations together + pnpm dlx @astrojs/upgrade + ``` + + + ```shell + # Upgrade Astro and official integrations together + yarn dlx @astrojs/upgrade + ``` + + + +You can also [upgrade your Astro integrations manually](/en/guides/integrations-guide/#manual-upgrading) if needed, and you may also need to upgrade other dependencies in your project. + +:::note[Need to continue?] +After upgrading Astro, you may not need to make any changes to your project at all! + +But, if you notice errors or unexpected behavior, please check below for what has changed that might need updating in your project. +::: + +Astro v6.0 includes [potentially breaking changes](#breaking-changes), as well as the removal and deprecation of some features. + +If your project doesn't work as expected after upgrading to v6.0, check this guide for an overview of all breaking changes and instructions on how to update your codebase. + +See [the Astro changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md) for full release notes. + +## Dependency Upgrades + +Any major upgrades to Astro's dependencies may cause breaking changes in your project. + +### Node 22 + + + +Node 18 reached its End of Life in March 2025 and Node 20 is scheduled to reach its End of Life in April 2026. + +Astro v6.0 drops Node 18 and Node 20 support entirely so that all Astro users can take advantage of Node's more modern features. + +#### What should I do? + +Check that both your development environment and your deployment environment are using **Node `22.12.0` or higher**. + + +1. Check your local version of Node using: + + ```sh + node -v + ``` + +2. Check your [deployment environment's](/en/guides/deploy/) own documentation to verify that they support Node 22. + + You can specify Node `22.12.0` for your Astro project either in a dashboard configuration setting or a `.nvmrc` file. + + ```bash title=".nvmrc" + 22.12.0 + ``` + + +### Vite 7.0 + + + +Astro v6.0 upgrades to Vite v7.0 as the development server and production bundler. + +#### What should I do? + +If you are using Vite-specific plugins, configuration, or APIs, check the [Vite migration guide](https://vite.dev/guide/migration) for their breaking changes and upgrade your project as needed. + +### Vite Environment API + + + +Astro v6.0 introduces significant changes to how Astro manages different runtime environments (client, server, and prerender) after an internal refactor to use [Vite's new Environments API](https://vite.dev/guide/api-environment). + +Integration and adapter maintainers should pay special attention to changes affecting these parts of the Integration API and Adapter API (full details included below with other breaking changes to these APIs): + +- [integration hooks and HMR access patterns](#changed-integration-hooks-and-hmr-access-patterns-integration-api) +- [`SSRManifest` structure](#changed-ssrmanifest-interface-structure-adapter-api) +- [generating routes with `RouteData`](#removed-routedatagenerate-adapter-api) +- [routes with percent-encoded percent signs (e.g. `%25`)](#removed-percent-encoding-in-routes) +- [`astro:ssr-manifest` virtual module](#removed-astrossr-manifest-virtual-module-integration-api) + +## 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. + +## 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`: + +```astro title="src/pages/blog/[slug].astro" del={5,6} ins={7} +--- +import { getPages } from "../../../utils/data"; + +export async function getStaticPaths() { + console.log(Astro.generator); + return getPages(Astro.site); + return getPages(import.meta.env.SITE); +} +--- +``` + +Read more about [built-in environment variables such as `import.meta.env.SITE`](/en/guides/environment-variables/#default-environment-variables) that are accessible when [using `getStaticPaths()` to dynamically generate static routes](/en/guides/routing/#static-ssg-mode). + +### 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 model 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 occurances 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: + +```ts del={4} ins={2,5} +import { someLogic } from "./utils" +import { build } from "astro:config/server" + +someLogic(import.meta.env.ASSETS_PREFIX) +someLogic(build.assetsPrefix) +``` + +Read more about the [`astro:config` virtual modules](/en/reference/modules/astro-config/). + +### Deprecated: `astro:schema` and `z` from `astro:content` + + + +In Astro 5.x, `astro:schema` was introduced as an alias of `astro/zod`. `z` was also exported from `astro:content` for convenience. However this occasionally created confusion for users who were unsure about where they should be importing from. + +Astro 6.0 deprecates `astro:schema` and `z` from `astro:content` in favor of `astro/zod`. + +#### What should I do? + +Replace any occurrences of `astro:schema` with `astro/zod`: + +```ts del={1} ins={2} +import { z } from "astro:schema" +import { z } from "astro/zod" +``` + +Remove `z` from your `astro:content` imports and import `z` separately from `astro/zod` instead: + +```ts title="src/content.config.ts" del={1} ins={2-3} +import { defineCollection, z } from "astro:content" +import { defineCollection } from "astro:content" +import { z } from "astro/zod" +``` + +See more about [defining collection schemas with Zod](/en/guides/content-collections/#defining-datatypes-with-zod). + +### Deprecated: exposed `astro:transitions` internals + + + +In Astro 5.x, some internals were exported from `astro:transitions` and `astro:transitions/client` that were not meant to be exposed for public use. + +Astro 6.0 removes the following functions and types as exports from the `astro:transitions` and `astro:transitions/client` virtual modules. These can no longer be imported in your project files: + +- `createAnimationScope()` +- `isTransitionBeforePreparationEvent()` +- `isTransitionBeforeSwapEvent()` +- `TRANSITION_BEFORE_PREPARATION` +- `TRANSITION_AFTER_PREPARATION` +- `TRANSITION_BEFORE_SWAP` +- `TRANSITION_AFTER_SWAP` +- `TRANSITION_PAGE_LOAD` + +#### What should I do? + +Remove any occurrences of `createAnimationScope()`: + +```ts del={1} +import { createAnimationScope } from 'astro:transitions'; +``` + +Update any occurrences of the other deprecated exports: + +```ts del={1-4,6,9} ins={7,10} +import { + isTransitionBeforePreparationEvent, + TRANSITION_AFTER_SWAP, +} 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](/en/reference/modules/astro-transitions/). + +## 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](https://astro.build/blog/introducing-content-collections/), **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](https://astro.build/blog/content-layer-deep-dive/) 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. + +```ts title="astro.config.mjs" del={5} +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + legacy: { + collections: true, + } +}) +``` + +Additionally, if you did not upgrade your collections for Astro v5.0, ensure that your content collections are **fully updated** for the new API. + +Astro v5.x included some automatic backwards compatibility to allow content collections to continue to work even if they had not been updated to use the new API. Therefore, your v5 collections may contain one or more legacy features that need updating to the newer API for v6, even if your project was previously error-free. + +If you have [content collections errors](/en/reference/error-reference/#content-collection-errors) or warnings after upgrading to v6, use the following list to help you identify and upgrade any legacy features that may exist in your code. + +##### If you have... + +
+no content collections configuration file +Create `src/content.config.ts` and [define your collections](/en/guides/content-collections/#defining-build-time-content-collections) in it. +
+ +
+a configuration file located at `src/content/config.ts` / ([`LegacyContentConfigError`](/en/reference/errors/legacy-content-config-error/)) +Rename and move this file to `src/content.config.ts` +
+ +
+a collection that does not define a `loader`/ ([`ContentCollectionMissingALoaderError`](/en/reference/errors/content-collection-missing-loader/)) + +Import [Astro's built-in `glob()` loader](/en/guides/content-collections/#the-glob-loader) and define the `pattern` and `base` for your collection entries: + +```ts ins={4,7} +// src/content.config.ts +import { defineCollection } from 'astro:content'; +import { z } from 'astro/zod'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + 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(), + }), +}); +``` +
+ +
+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. + +```ts del={8} +// src/content.config.ts +import { defineCollection } from 'astro:content'; +import { z } from 'astro/zod'; +import { glob } from 'astro/loaders'; + +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`: + +```astro ins={6} del={5} title="src/pages/[slug].astro" +--- +export async function getStaticPaths() { + const posts = await getCollection('blog'); + 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)`: + +```astro title="src/pages/index.astro" ins=", render" del={6} ins={7} +--- +import { getEntry, render } from 'astro:content'; + +const post = await getEntry('pages', 'homepage'); + +const { Content, headings } = await post.render(); +const { Content, headings } = await render(post); +--- + +``` + +
+ + See [the Astro v5 upgrade guide](/en/guides/upgrade-to/v5/#legacy-v20-content-collections-api) for previous guidance about backwards compatibility of legacy collections in Astro v5 and full step-by-step instructions for upgrading legacy collections to the new Content Layer API. + +### Removed: `` component + + + +In Astro 5.0, the `` component was renamed to `` to clarify the role of the component. The new name makes it more clear that the features you get from Astro's `` routing component are slightly different from the native CSS-based MPA router. However, a deprecated version of the `` component still existed and may have functioned in Astro 5.x. + +Astro 6.0 removes the `` component entirely and it can no longer be used in your project. Update to the `` component to continue to use these features. + +#### What should I do? + +Replace all occurrences of the `ViewTransitions` import and component with `ClientRouter`: + +```astro title="src/layouts/MyLayout.astro" del={1,7} ins={2,8} +import { ViewTransitions } from 'astro:transitions'; +import { ClientRouter } from 'astro:transitions'; + + + + ... + + + + +``` + +Read more about [view transitions and client-side routing in Astro](/en/guides/view-transitions/). + +### 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: + +```ts del={1,5} ins={2,6} +import { emitESMImage } from 'astro/assets/utils'; +import { emitImageMetadata } from 'astro/assets/utils'; + +const imageId = '/images/photo.jpg'; +const result = await emitESMImage(imageId, false, false); +const result = await emitImageMetadata(imageId); +``` + +Read more about [`emitImageMetadata()`](/en/reference/image-service-reference/#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](/en/guides/imports/#glob-patterns). + +```astro title="src/pages/blog.astro" del={2} ins={3} +--- +const posts = await Astro.glob('./posts/*.md'); +const posts = Object.values(import.meta.glob('./posts/*.md', { eager: true })); +--- + +{posts.map((post) =>
  • {post.frontmatter.title}
  • )} +``` + +Where appropriate, consider using [content collections](/en/guides/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`](https://www.npmjs.com/package/fast-glob). + +Learn more about [importing files with `import.meta.glob`](/en/guides/imports/#importmetaglob). + +### 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_CODES` +- `ActionInputError` +- `appendForwardSlash` +- `astroCalledServerError` +- `callSafely` +- `deserializeActionResult` +- `formDataToObject` +- `getActionQueryString` +- `serializeActionResult` +- `type Actions` +- `type ActionAccept` +- `type AstroActionContext` +- `type SerializedActionResult` + +#### What should I do? + +Replace all imports of `serializeActionResult()` and `deserializeActionResult()` with `getActionContext()`. These two methods are now available through `getActionContext()`: + +```ts title="src/middleware.ts" del={2} ins={3,6} +import { defineMiddleware } from 'astro:middleware'; +import { serializeActionResult, deserializeActionResult } from 'astro:actions'; +import { getActionContext } from 'astro:actions'; + +export const onRequest = defineMiddleware(async (context, next) => { + const { serializeActionResult, deserializeActionResult } = getActionContext(context); + // ... +}); +``` + +Remove any occurrences of the other removed exports: + +```ts del={1-13} +import { + ACTION_ERROR_CODES, + ActionInputError, + appendForwardSlash, + astroCalledServerError, + callSafely, + formDataToObject, + getActionQueryString, + type Actions, + type ActionAccept, + type AstroActionContext, + type SerializedActionResult, +} from 'astro:actions'; +``` + +Learn more about all utilities available in the [Actions API Reference](/en/reference/modules/astro-actions/). + +### 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: + +```bash del={1} ins={2} +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: + +```ts del={1} ins={2,3} +import { manifest } from 'astro:ssr-manifest'; +import { srcDir, outDir, root } from 'astro:config/server'; +// Use srcDir, outDir, root, etc. for configuration values +``` + +Learn more about [the `astro:config` virtual module](/en/reference/modules/astro-config/). + +### 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: + +```ts del={1} +const generated = route.generate(params); +``` + +Learn more about [the Adapter API](/en/reference/adapter-reference/). + +### 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: + +```js title="my-integration.mjs" ins={2,6-8,11,13-18} del={10} +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) + } + } + } +} +``` + +Learn more about [the Integration API `astro:routes:resolved` hook](/en/reference/integrations-reference/#astroroutesresolved) for building integrations. + +### Removed: `entryPoints` on `astro:build:ssr` hook (Integration API) + + + +In Astro 5.0, [`functionPerRoute` was deprecated](/en/guides/upgrade-to/v5/#deprecated-functionperroute-adapter-api). 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`: + +```js title="my-integration.mjs" del={6} +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: + +```ts title="my-adapter/entrypoint.ts" del={1} ins={2} +app.render(request, routeData, locals) +app.render(request, { routeData, locals }) +``` + +Learn more about the [Adapter API](/en/reference/adapter-reference/). + +### 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. + +Learn more about the [Adapter API](/en/reference/adapter-reference/). + +### Removed: `handleForms` prop for the `` component + + + +In Astro 4.0, the `handleForms` prop of the `` 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 `` component if it exists. It has provided no additional functionality, and so removing it should not change any behavior in your project: + +```astro title="src/pages/index.astro" del="handleForms" +--- +import { ClientRouter } from "astro:transitions"; +--- + + + + + + + + +``` + +Learn more about [transitions with forms](/en/guides/view-transitions/#transitions-with-forms). + +### 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 ` + +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()`: + + +```ts title="src/actions/index.ts" del={10} +import { defineAction } from 'astro:actions'; +import { z } from 'astro:schema'; + +export const server = { + getGreeting: defineAction({ + input: z.object({ + // ... + }), + handler: async (input, context) => { + context.rewrite('/') + // ... + } + }) +} +``` + +Learn more about [rewrites](/en/guides/routing/#rewrites). + +### Removed: schema function signature (Content Loader API) + +In Astro 5.x, a content loader could choose to define a schema as a function instead of defining a Zod schema object for validation. This is useful to dynamically generate the schema based on the configuration options or by introspecting an API. + +Astro 6.0 removes this signature and introduces a new `createSchema()` property as a replacement for those who still want to dynamically define a schema in their content loader. + +Providing a schema function in the old way will log a warning message that the loader's schema is being ignored, but otherwise the loader will continue to work as if no schema had been provided. In a future major version, loaders that provide a schema function will throw an error and cannot be used. + +#### What should I do? + +If you are building a content loader and using a function to dynamically return a collection `schema` property, you must remove your existing function and use the new `createSchema()` property to define your schema instead. + +For example, you can reproduce Astro's previous behavior by using `zod-to-ts` directly with `createSchema()` and any previous function logic: + +```ts del={11} ins={2,12-22} +import type { Loader } from 'astro/loaders' +import { createTypeAlias, zodToTs } from 'zod-to-ts' +import { getSchemaFromApi } from './utils' + +function myLoader() { + return { + name: 'my-loader', + load: async (context) => { + // ... + }, + schema: async () => await getSchemaFromApi(), + createSchema: async () => { + const schema = await getSchemaFromApi() + const identifier = 'Entry' + const { node } = zodToTs(schema, identifier) + const typeAlias = createTypeAlias(node, identifier) + + return { + schema, + types: `export ${typeAlias}` + } + } + } satisfies Loader +} +``` + +Learn more about [`createSchema()`](/en/reference/content-loader-reference/#createschema) in the Content Loader API reference. + +### Experimental Flags + +Experimental flags allow you to opt in to features while they are in early development. Astro may also use experimental flags to test breaking changes to default behavior. The following experimental flags have been removed in Astro 6.0 and are now stable, or the new default behavior. + +Remove these experimental flags from your Astro config if you were previously using them: + +```js del={5-9} title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + liveContentCollections: true, + preserveScriptOrder: true, + staticImportMetaEnv: true, + headingIdCompat: true, + failOnPrerenderConflict: true + }, +}) +``` + +#### Experimental features now stable: + +- `liveContentCollections` (See the updated [content collections docs](/en/guides/content-collections/) to learn more about live collections.) +- `failOnPrerenderConflict` (See the new [`prerenderConflictBehavior`](/en/reference/configuration-reference/#prerenderconflictbehavior) configuration option.) + +#### New default or recommended behavior: + +- `preserveScriptOrder` (See below for breaking changes to [default ` + +``` + +Read more about [using `script`](/en/guides/client-side-scripts/) and [`style`](/en/guides/styling/) tags. + +## Breaking Changes + +The following changes are considered breaking changes in Astro v5.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: + +```html del={1} ins={2} title="src/pages/index.astro" +Sitemap +Sitemap +``` + +Learn more about [custom endpoints](/en/guides/endpoints/). + +### 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](https://vite.dev/guide/env-and-mode.html#env-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](#experimental-flags) as it no longer exists. + +If you were relying on coercion, you may need to update your project code to apply it manually: + +```ts title="src/components/MyComponent.astro" del={1} ins={2} +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: + +```ts title="src/components/MyComponent.astro" del={1} ins={2} +const enabled: boolean = import.meta.env.DB_PASSWORD; +const enabled: boolean = process.env.DB_PASSWORD; +``` + +You may also need to update types: + +```ts title="src/env.d.ts" del={3-4} ins={5,12-16} +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`. + +Learn more about [environment variables](/en/guides/environment-variables/) in Astro, including `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: + +```ts title="src/components/MyImage.astro" del={5} ins={6} +--- +import { Image } from 'astro:assets'; +import myImage from '../assets/photo.jpg'; +--- + + +``` + +### Changed: Never upscale images in default image service + + + +In Astro 5.0, 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: 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`](https://github.com/Flet/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: + +```md +## `` +``` + +will now generate the following HTML with a trailing hyphen in the heading `id`: + +```html ins="-" +

    <Picture />

    +``` + +and must now be linked to as: + +```markdown ins="-" +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](#experimental-flags) 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: + +```js title="astro.config.mjs" del={8} ins={9} +import { defineConfig } from 'astro/config'; +import { rehypeHeadingIds } from '@astrojs/markdown-remark'; +import { otherPluginThatReliesOnHeadingIDs } from 'some/plugin/source'; + +export default defineConfig({ + markdown: { + rehypePlugins: [ + [rehypeHeadingIds, { headingIdCompat: true }], + [rehypeHeadingIds], + otherPluginThatReliesOnHeadingIDs, + ], + }, +}); +``` + +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 + + + +1. 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 + ``` + + + +2. Create a custom rehype plugin that will generate headings IDs like Astro v5: + + ```js title="plugins/rehype-slug.mjs" + 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; + } + }); + }; + } + ``` + +3. Add the custom plugin to your Markdown configuration in `astro.config.mjs`: + + ```js title="astro.config.mjs" ins={2} ins="rehypeSlug" + import { defineConfig } from 'astro/config'; + import { rehypeSlug } from './plugins/rehype-slug'; + + export default defineConfig({ + markdown: { + rehypePlugins: [rehypeSlug], + }, + }); + ``` + + + +
    + +Learn more about [Heading IDs](/en/guides/markdown-content/#heading-ids). + +### 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: + +```astro title="src/pages/post/[id]/[label].astro" del={6,13} ins={7,14} +--- +export function getStaticPaths() { + return [ + { + params: { + id: 1, + id: "1", + label: "foo", + } + }, + { + params: { + id: 2, + id: "2", + label: "bar", + } + }, + ] +} +--- +``` + +Learn more about [dynamic SSG routes with `getStaticPaths()`](/en/guides/routing/#static-ssg-mode). + +### 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: + +```ts title="my-integration.mjs" del={3-7} ins={8-10} +{ + 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()`: + +```ts del={1} ins={2} +server.hot.send(event) +server.environments.client.hot.send(event) +``` + +Learn more about the [Vite Environment API](https://vite.dev/guide/api-environment) and Astro [integration hooks](/en/reference/integrations-reference/#astrobuildsetup). + +### 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: + +```ts del={2} ins={3} +// 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: + +```ts del={1,2} ins={3,4} +const mappings = manifest.serverIslandMappings; +const driver = manifest.sessionDriver; +const mappings = await manifest.serverIslandMappings?.(); +const driver = await manifest.sessionDriver?.(); +``` + +Learn more about [the Adapter API](/en/reference/adapter-reference/). +### 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](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator): + +```ts del={3,11} ins={4,12} +import type { Loader } from 'astro/loaders' + +function myLoader(): Loader { +function myLoader() { + return { + name: 'my-loader', + load: async (context) => { + // ... + }, + schema: z.object({/* ... */}) + } + } satisfies Loader +} +``` + +Learn more about [defining loader schema types](/en/reference/content-loader-reference/#the-loader-object). + +## Community Resources + +Know a good resource for Astro v5.0? [Edit this page](https://github.com/withastro/docs/edit/main/src/content/docs/en/guides/upgrade-to/v6.mdx) and add a link below! + +## Known Issues + +Please check [Astro's issues on GitHub](https://github.com/withastro/astro/issues/) for any reported issues, or to file an issue yourself. diff --git a/src/content/docs/en/install-and-setup.mdx b/src/content/docs/en/install-and-setup.mdx index d5986b397a2d3..8571d24b98b94 100644 --- a/src/content/docs/en/install-and-setup.mdx +++ b/src/content/docs/en/install-and-setup.mdx @@ -21,7 +21,7 @@ Prefer to try Astro in your browser? Visit [astro.new](https://astro.new/) to br ## Prerequisites -- **Node.js** - `v18.20.8` or `v20.3.0`, `v22.0.0` or higher. (`v19` and `v21` are not supported.) +- **Node.js** - `v22.12.0` or higher. Odd-numbered versions like `v23` are not supported. - **Text editor** - We recommend [VS Code](https://code.visualstudio.com/) with our [Official Astro extension](https://marketplace.visualstudio.com/items?itemName=astro-build.astro-vscode). - **Terminal** - Astro is accessed through its command-line interface (CLI). diff --git a/src/content/docs/en/reference/adapter-reference.mdx b/src/content/docs/en/reference/adapter-reference.mdx index a08e35d1b6b25..fa23c137aa788 100644 --- a/src/content/docs/en/reference/adapter-reference.mdx +++ b/src/content/docs/en/reference/adapter-reference.mdx @@ -480,7 +480,7 @@ If not provided, Astro will fallback to its default behavior for fetching error **Default:** `app.match(request)`

    -Provides a value for [`integrationRouteData`](/en/reference/integrations-reference/#integrationroutedata-type-reference) if you already know the route to render. Doing so will bypass the internal call to [`app.match()`](#appmatch) to determine the route to render. +Provides a value for [`integrationResolvedRoute`](/en/reference/integrations-reference/#integrationresolvedroute-type-reference) if you already know the route to render. Doing so will bypass the internal call to [`app.match()`](#appmatch) to determine the route to render. ```js "routeData" const routeData = app.match(request); diff --git a/src/content/docs/en/reference/api-reference.mdx b/src/content/docs/en/reference/api-reference.mdx index 8bcdbedcee4bb..bd6c7f2b044e5 100644 --- a/src/content/docs/en/reference/api-reference.mdx +++ b/src/content/docs/en/reference/api-reference.mdx @@ -1048,118 +1048,3 @@ Loads a session by ID. In normal use, a session is loaded automatically from the - - -### Deprecated object properties - -#### `Astro.glob()` - -:::caution[Deprecated in v5.0] -Use [Vite's `import.meta.glob`](https://vite.dev/guide/features.html#glob-import) to query project files. - -`Astro.glob('../pages/post/*.md')` can be replaced with: - -`Object.values(import.meta.glob('../pages/post/*.md', { eager: true }));` - -See the [imports guide](/en/guides/imports/#importmetaglob) for more information and usage. -::: - -`Astro.glob()` is a way to load many local files into your static site setup. - -```astro ---- -// src/components/my-component.astro -const posts = await Astro.glob('../pages/post/*.md'); // returns an array of posts that live at ./src/pages/post/*.md ---- - -
    -{posts.slice(0, 3).map((post) => ( -
    -

    {post.frontmatter.title}

    -

    {post.frontmatter.description}

    - Read more -
    -))} -
    -``` - -`.glob()` only takes one parameter: a relative URL glob of which local files you'd like to import. It’s asynchronous and returns an array of the exports from matching files. - -`.glob()` can't take variables or strings that interpolate them, as they aren't statically analyzable. (See [the imports guide](/en/guides/imports/#supported-values) for a workaround.) This is because `Astro.glob()` is a wrapper of Vite's [`import.meta.glob()`](https://vite.dev/guide/features.html#glob-import). - -:::note -You can also use `import.meta.glob()` itself in your Astro project. You may want to do this when: -- You need this feature in a file that isn't `.astro`, like an API route. `Astro.glob()` is only available in `.astro` files, while `import.meta.glob()` is available anywhere in the project. -- You don't want to load each file immediately. `import.meta.glob()` can return functions that import the file content, rather than returning the content itself. Note that this import includes all styles and scripts for any imported files. These will be bundled and added to the page whether or not a file is actually used, as this is decided by static analysis, not at runtime. -- You want access to each file's path. `import.meta.glob()` returns a map of a file's path to its content, while `Astro.glob()` returns a list of content. -- You want to pass multiple patterns; for example, you want to add a "negative pattern" that filters out certain files. `import.meta.glob()` can optionally take an array of glob strings, rather than a single string. - -Read more in the [Vite documentation](https://vite.dev/guide/features.html#glob-import). -::: - -##### Markdown Files - -Markdown files loaded with `Astro.glob()` return the following `MarkdownInstance` interface: - -```ts -export interface MarkdownInstance> { - /* Any data specified in this file's YAML/TOML frontmatter */ - frontmatter: T; - /* The absolute file path of this file */ - file: string; - /* The rendered path of this file */ - url: string | undefined; - /* Astro Component that renders the contents of this file */ - Content: AstroComponentFactory; - /** (Markdown only) Raw Markdown file content, excluding layout HTML and YAML/TOML frontmatter */ - rawContent(): string; - /** (Markdown only) Markdown file compiled to HTML, excluding layout HTML */ - compiledContent(): string; - /* Function that returns an array of the h1...h6 elements in this file */ - getHeadings(): Promise<{ depth: number; slug: string; text: string }[]>; - default: AstroComponentFactory; -} -``` - -You can optionally provide a type for the `frontmatter` variable using a TypeScript generic. - -```astro ---- -interface Frontmatter { - title: string; - description?: string; -} -const posts = await Astro.glob('../pages/post/*.md'); ---- - -
      - {posts.map(post =>
    • {post.frontmatter.title}
    • )} -
    -``` - -##### Astro Files - -Astro files have the following interface: - -```ts -export interface AstroInstance { - /* The file path of this file */ - file: string; - /* The URL for this file (if it is in the pages directory) */ - url: string | undefined; - default: AstroComponentFactory; -} -``` - -##### Other Files - -Other files may have various different interfaces, but `Astro.glob()` accepts a TypeScript generic if you know exactly what an unrecognized file type contains. - -```ts ---- -interface CustomDataFile { - default: Record; -} -const data = await Astro.glob('../data/**/*.js'); ---- -``` diff --git a/src/content/docs/en/reference/cli-reference.mdx b/src/content/docs/en/reference/cli-reference.mdx index bcf56ecc23dc8..0a0b87b283a9b 100644 --- a/src/content/docs/en/reference/cli-reference.mdx +++ b/src/content/docs/en/reference/cli-reference.mdx @@ -420,7 +420,7 @@ astro --config config/astro.config.mjs dev

    -Clear the [content layer cache](/en/guides/content-collections/#defining-the-collection-loader), forcing a full rebuild. +Clear the content layer cache, forcing a full rebuild. ### `--mode ` diff --git a/src/content/docs/en/reference/configuration-reference.mdx b/src/content/docs/en/reference/configuration-reference.mdx index 4e30098a4ecb4..ecc513b5d18da 100644 --- a/src/content/docs/en/reference/configuration-reference.mdx +++ b/src/content/docs/en/reference/configuration-reference.mdx @@ -366,6 +366,27 @@ Using `'class'` is helpful when you want to ensure that element selectors within Using `'where'` gives you more control over specificity, but requires that you use higher-specificity selectors, layers, and other tools to control which selectors are applied. Using `'attribute'` is useful when you are manipulating the `class` attribute of elements and need to avoid conflicts between your own styling logic and Astro's application of styles. +### prerenderConflictBehavior + +

    + +**Type:** `'error' | 'warn' | 'ignore'`
    +**Default:** `'warn'`
    + +

    + +Determines the default behavior when two routes generate the same prerendered URL: + + - `error`: fail the build and display an error, forcing you to resolve the conflict + - `warn` (default): log a warning when conflicts occur, but build using the highest-priority route + - `ignore`: silently build using the highest-priority route when conflicts occur + +```js +{ + prerenderConflictBehavior: 'error' +} +``` + ### security

    @@ -1587,14 +1608,14 @@ export default defineConfig({

    **Type:** `boolean`
    -**Default:** `true`
    +**Default:** `false`

    Configures whether or not the home URL (`/`) generated by `src/pages/index.astro` will redirect to `/[defaultLocale]` when `prefixDefaultLocale: true` is set. -Set `redirectToDefaultLocale: false` to disable this automatic redirection at the root of your site: +Set `redirectToDefaultLocale: true` to enable this automatic redirection at the root of your site: ```js // astro.config.mjs export default defineConfig({ @@ -1603,7 +1624,7 @@ export default defineConfig({ locales: ["en", "fr"], routing: { prefixDefaultLocale: true, - redirectToDefaultLocale: false + redirectToDefaultLocale: true } } }) diff --git a/src/content/docs/en/reference/content-loader-reference.mdx b/src/content/docs/en/reference/content-loader-reference.mdx index ac5bbb40a78b4..6e421c1a91b65 100644 --- a/src/content/docs/en/reference/content-loader-reference.mdx +++ b/src/content/docs/en/reference/content-loader-reference.mdx @@ -3,20 +3,26 @@ title: Astro Content Loader API sidebar: label: Content Loader API i18nReady: true +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 3 --- import Since from '~/components/Since.astro'; +import ReadMore from '~/components/ReadMore.astro'; -Astro's Content Loader API allows you to load your data from any source, local or remote, and interact with Astro's content layer to manage your [content collections](/en/guides/content-collections/). +Astro's Content Loader API allows you to load your data from any source, local or remote, and interact with Astro's content layer to manage your [content collections](/en/guides/content-collections/). -## What is a loader? +This API includes two ready-to-use loaders for content stored locally. It also provides tools for building your own custom objects that can load data from any source into content collections. -Astro loaders allow you to load data into [content collections](/en/guides/content-collections/), which can then be used in pages and components. The [built-in `glob()` and `file()` loaders](/en/guides/content-collections/#built-in-loaders) are used to load content from the file system, and you can create your own loaders to load content from other sources. +Learn more about [querying data loaded from build-time loaders](/en/guides/content-collections/#querying-build-time-collections) or [accessing live data from live loaders](/en/guides/content-collections/#accessing-live-data) with guided explanations and example usage in the content collections guide. -Each collection needs [a loader defined in its schema](/en/guides/content-collections/#defining-the-collection-loader). You can define a loader inline in your project's `src/content.config.ts` file, share one loader between multiple collections, or even [publish your loader to NPM as a package](/en/reference/publish-to-npm/) to share with others and be included in our integrations library. +## Build-time loaders -## Built-in loaders +Build-time loaders are objects with a [`load()` method](#load) that is called at build time to fetch data and update the data store. This object can also define a schema for the entries, which can be used to validate the data and generate static types. -Astro provides two built-in loaders to help you fetch your collections. Both offer options to suit a wide range of use cases. +Astro's [`glob()`](#glob-loader) and [`file()`](#file-loader) loaders are examples of object loaders that are provided out-of-the-box for use with local content. For remote content, no prebuilt loaders are provided. You will have to build an object loader or use a [community-published loader](https://astro.build/integrations/?search=&categories%5B%5D=loaders) to retrieve remote content and interact with the data store. + +For simple data fetching, you can also [define a loader as an async function](#defining-a-loader-as-a-function) that returns an array or object containing entries. ### `glob()` loader @@ -30,19 +36,17 @@ The `glob()` loader creates entries from directories of files from anywhere on t This loader accepts an object with the following properties: `pattern`, `base` (optional), and `generateId` (optional). -```ts title="src/content.config.ts" {2,6,11,17-21} +```ts title="src/content.config.ts" import { defineCollection } from 'astro:content'; import { glob } from 'astro/loaders'; const pages = defineCollection({ /* Retrieve all Markdown files in your pages directory. */ loader: glob({ pattern: "**/*.md", base: "./src/data/pages" }), - schema: /* ... */ }); const blog = defineCollection({ /* Retrieve all Markdown and MDX files in your blog directory. */ loader: glob({ pattern: "**/*.(md|mdx)", base: "./src/data/blog" }), - schema: /* ... */ }); const authors = defineCollection({ /* Retrieve all JSON files in your authors directory while retaining @@ -52,8 +56,9 @@ const authors = defineCollection({ base: "./src/data/authors", generateId: ({ entry }) => entry.replace(/\.json$/, ''), }), - schema: /* ... */ }); + +export const collections = { pages, blog, authors }; ``` #### `pattern` @@ -99,26 +104,28 @@ By default it uses [`github-slugger`](https://github.com/Flet/github-slugger) to

    -The `file()` loader creates entries from a single file that contains an array of objects with a unique `id` field, or an object with IDs as keys and entries as values. It supports JSON, YAML, or TOML files and you can provide a custom `parser` for data files it cannot parse by default. +The `file()` loader creates entries from a single file that contains an array of objects with a unique `id` field, or an object with IDs as keys and entries as values. + +It supports JSON, YAML, or TOML files and you can provide a custom `parser` for data files it cannot parse by default. -This loader accepts a `fileName` property and an optional object as second argument: +This loader accepts a `fileName` property and an optional options object as second argument: -```ts title="src/content.config.ts" {2,6,11-13} +```ts title="src/content.config.ts" import { defineCollection } from 'astro:content'; import { file } from 'astro/loaders'; const authors = defineCollection({ /* Retrieve all entries from a JSON file. */ loader: file("src/data/authors.json"), - schema: /* ... */ }); const products = defineCollection({ /* Retrieve all entries from a CSV file using a custom parser. */ loader: file("src/data/products.csv", { parser: (fileContent) => { /* your parser logic */ }, }), - schema: /* ... */ }); + +export const collections = { authors, products }; ``` #### `fileName` @@ -146,19 +153,108 @@ An optional object with the following properties: **Type:** `(text: string) => Record> | Array>`

    -A callback function to create a collection from a file’s contents. Use it when you need to process file not supported by default (e.g. `.csv`) or when using [nested `.json` documents](/en/guides/content-collections/#nested-json-documents). +A callback function to create a collection from a file’s contents. Use it when you need to process files other than JSON, YAML, or TOML that not supported by default (e.g. `.csv`) or when using [nested `.json` documents](/en/guides/content-collections/#nested-json-documents). + +### Building a loader + +The Content Loader API is flexible and full-featured, allowing for a variety of data fetching options. It is possible to build both simple and complex loaders. Your custom loader will depend on both the source and the shape of your data, as well as how you choose to manage the persistent data storage layer. + +Most loaders will export a function that accepts configuration options and returns a [loader object](#the-loader-object) including a `name` for your loader, a `load()` method, and a `schema` defining your entries. + +#### Loading collections into the data store + +The [`load()`](#load) function returned in the loader object defines how your content is fetched, parsed, validated and updated. It accepts a `context` object that allows you to customize your data handling in a variety of ways and interact with the data store. A typical `load()` function will: + +- Fetch your data from a source. +- Clear the existing data store. +- Parse and validate your data entries according to a provided schema. +- Update the data store with new entries. + +The `load()` method also provides helpers to log messages to the console, render content to HTML, watch for changes in dev mode and reload data, provide access to metadata +and even the full Astro config, and more. + +See the full [`LoaderContext`](#loadercontext) list of properties for all options available to the `load()` function. + +#### Providing a schema + +Providing a Zod [`schema`](#schema) in your loader allows you to validate your fetched content entries with [`parseData()`](#parsedata) before adding them to the data [store](#store). This schema will also be used as the collection's default schema when one does not exist in `src/content.config.ts` to provide type safety and editor tooling. You do not also need a schema defined in the content collection if the loader provides this property. + +However, if the content collection also [defines a schema](/en/guides/content-collections/#defining-the-collection-schema), that schema will be used instead of your loader's schema. This is to allow users of your loader to extend its schema, or transform data for use in their project. If you are [publishing and distributing a loader](#distributing-your-loader) for others to use, you may wish to document this behavior and encourage users not to define a collection schema themselves, or how to do so safely if they need data returned in a different format. + +If you need to dynamically generate the schema based on the configuration options or by introspecting an API, you can use [`createSchema()`](#createschema) instead. + +#### Loader example + +The following example shows a loader that fetches data from a provided feed URL (using a custom `loadFeedData` utility) and updates the data store with new entries each time the site is built: + +```ts title="src/feed-loader.ts" +// 1. Import the `Loader` type and any other dependencies needed +import type { Loader } from 'astro/loaders'; +import { z } from 'astro/zod'; +import { loadFeedData } from "./feed.js"; + +// 2. Define any options that your loader needs +export function myLoader(options: { url: string, apiKey: string }) { + const feedUrl = new URL(options.url); + // 3. Return a loader object + return { + name: "feed-loader", + load: async ({ store, parseData }) => { + const feed = await loadFeedData(feedUrl, options.apiKey); + + store.clear(); + + for (const item of feed.items) { + const id = item.guid; + const data = await parseData({ + id, + data: item, + }); + store.set({ + id, + data, + }); + } + }, + // 4. Define the schema of an entry. + schema: z.object({ + // ... + }) + } satisfies Loader; +} +``` + +#### Defining your collection with your loader + +Use your custom loader as the value of the `loader` property when you define your collection in `src/content.config.ts`. Configuration options can be passed to your loader as arguments: + +```ts title="src/content.config.ts" +import { defineCollection } from 'astro:content'; +import { feedLoader } from './feed-loader.ts'; + +const blog = defineCollection({ + loader: feedLoader({ + url: "https://api.example.com/posts", + apiKey: "my-secret", + }), +}); + +export const collections = { blog }; +``` -## Loader types +### Defining a loader as a function -Loaders can be defined either as a simple function that returns an array of entries or with the more powerful object Content Loader API for more control over the loading process. +For simple data fetches that do not need custom data store handling, validation, logging, or any other helpers provided by the [build-time loader object](#the-loader-object), you can define your loader as a function. -### Inline loaders +The function can be async and must return either an array of entries that each contain a unique `id` field, or an object where each key is a unique ID and each value is the entry. -An inline loader is an async function that returns an array or object containing entries. Use this for simple loaders, particularly those that are defined inline in the `src/content.config.ts` file. +This pattern provides a convenient shorthand to accomplish the basic tasks normally performed by the `load()` function to [load collections into the data store](#loading-collections-into-the-data-store). At build-time, the loader will automatically clear the data store and reload all the entries. No further customization options or helpers for data handling are provided. -The function can be async and must return either an array of entries that each contain a unique `id` field, or an object where each key is a unique ID and each value is the entry. Whenever the loader is invoked, it will clear the store and reload all the entries. +These loaders are often simple enough that you may choose to define them inline in the `src/content.config.ts` file: ```ts title="src/content.config.ts" +import { defineCollection } from "astro:content"; + const countries = defineCollection({ loader: async () => { const response = await fetch("https://restcountries.com/v3.1/all"); @@ -170,105 +266,479 @@ const countries = defineCollection({ ...country, })); }, - schema: /* ... */ }); + +export const collections = { countries }; ``` -### Object loaders +## Live Loaders -A loader is an object with a `load()` method that is called at build time to fetch data and update the data store. It allows entries to be updated incrementally, or for the store to be cleared only when necessary. It can also define a schema for the entries, which can be used to validate the data and generate static types. +The Live Loader API is built to handle querying any data in real time. Live loaders can filter incoming data and verify content with type safety. Since live loaders fetch data fresh upon every request, there is no data store to update. These loaders are designed to return either data or an `Error` object to allow you to handle errors gracefully. -The recommended pattern is to define a function that accepts configuration options and returns the loader object, in the same way that you would normally define an Astro integration or Vite plugin. +### Building a live loader +Most live loaders will export a function that accepts configuration options and returns a [live loader object](#the-liveloader-object) including a `name` for your loader and two methods to define how to load your collection of entries and how to load a single entry: `loadCollection()` and `loadEntry()`. -```ts title=loader.ts -import type { Loader, LoaderContext } from 'astro/loaders'; -import { z } from 'astro/zod'; -import { loadFeedData } from "./feed.js"; +#### Loading live data -// Define any options that the loader needs -export function myLoader(options: { url: string, apiKey: string }): Loader { - // Configure the loader - const feedUrl = new URL(options.url); - // Return a loader object +To return data about your collection, you must provide a [`loadCollection()`](#loadcollection) function that fetches data, and returns an array of content [`entries`](#entries-1) or an error. + +To return a single live collection entry, you must provide a [`loadEntry()`](#loadentry) function that fetches data filtered for a given `id`, and returns a single [`entry`](#livedataentry), `undefined`, or an error. + +The data fetching for both of these functions is typically done using a [`try...catch` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) to [handle errors when accessing live data](#error-handling-in-live-loaders). + +See the full [Live Loader API](#live-loader-api) for more about the functions and types available for building your live loader. + +#### Providing a schema for live loaders + +Live loaders do not include a schema property. Instead, you can provide type safety by [defining a Zod schema for your collection](/en/guides/content-collections/#using-zod-schemas-with-live-collections) in `src/live.config.ts`, or by passing generic types to the `LiveLoader` interface for the data they return. + +#### Example live loader + +The following example shows a live loader that defines data fetching from a CMS (using a custom `fetchFromCMS` utility) for both a collection of entries and a single entry, including type safety and error handling: + +```ts title="src/article-loader.ts" +import type { LiveLoader } from 'astro/loaders'; +import { fetchFromCMS } from './cms-client.js'; + +interface Article { + id: string; + title: string; + htmlContent: string; + author: string; +} + +interface EntryFilter { + id: string; +} + +interface CollectionFilter { + author?: string; +} + +export function articleLoader(config: { apiKey: string }): LiveLoader { return { - name: "my-loader", - // Called when updating the collection. - load: async (context: LoaderContext): Promise => { - // Load data and update the store - const response = await loadFeedData(feedUrl, options.apiKey); + name: 'article-loader', + loadCollection: async ({ filter }) => { + try { + const articles = await fetchFromCMS({ + apiKey: config.apiKey, + type: 'article', + filter, + }); + + return { + entries: articles.map((article) => ({ + id: article.id, + data: article, + })), + }; + } catch (error) { + return { + error: new Error('Failed to load articles', { cause: error }), + }; + } + }, + loadEntry: async ({ filter }) => { + try { + // filter will be { id: "some-id" } when called with a string + const article = await fetchFromCMS({ + apiKey: config.apiKey, + type: 'article', + id: filter.id, + }); + + if (!article) { + return { + error: new Error('Article not found'), + }; + } + + return { + id: article.id, + data: article, + rendered: { + html: article.htmlContent, + }, + }; + } catch (error) { + return { + error: new Error('Failed to load article', { cause: error }), + }; + } }, - // Optionally, define the schema of an entry. - // It will be overridden by user-defined schema. - schema: async () => z.object({ - // ... - }) }; } ``` -These configuration options can then be set when defining a collection: +#### Defining your live collection with your loader -```ts title="src/content.config.ts" {3,6-9} -import { defineCollection } from 'astro:content'; -import { z } from 'astro/zod'; -import myLoader from '../../loader.ts'; +Use your custom live loader as the value of the `loader` property when you define your collection in `src/live.config.ts`. Configuration options can be passed to your loader as arguments: -const blog = defineCollection({ - loader: myLoader({ - url: "https://api.example.com/posts", +```ts title="src/live.config.ts" {2,5-7} +import { defineLiveCollection } from 'astro:content'; +import { articleLoader } from './article-loader.ts'; + +const blog = defineLiveCollection({ + loader: articleLoader({ apiKey: "my-secret", - }), - schema: /* ... */ -}); + }), +}); + +export const collections = { blog }; +``` + +#### Error handling in live loaders + +Live loaders return an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) subclass for errors. You can create [custom error types](#creating-live-loader-error-types) and use them for more specific error handling if needed. If an error is thrown in the live loader, it will be caught and returned, wrapped in a `LiveCollectionError`. + +Astro will generate some errors itself, depending on the response from the live loader: + +- If `loadEntry` returns `undefined`, Astro will return a `LiveEntryNotFoundError` to the user. +- If a schema is defined for the collection and the data does not match the schema, Astro will return a `LiveCollectionValidationError`. +- If the loader returns an invalid cache hint, Astro will return a `LiveCollectionCacheHintError`. The `cacheHint` field is optional, so if you do not have valid data to return, you can simply omit it. + +```ts title="my-loader.ts" {10-12} +import type { LiveLoader } from 'astro/loaders'; +import type { MyData } from "./types"; +import { MyLoaderError } from './errors'; + +export function myLoader(config): LiveLoader { + return { + name: 'my-loader', + loadCollection: async () => { + // Return your custom error type + return { + error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), + }; + }, + // ... + }; +} +``` + +##### Creating live loader error types + +You can create custom error types for [errors returned by your loader](#error-handling-in-live-loaders) and pass them as a generic to get proper typing: + +```ts title="my-loader.ts" +import type { LiveLoader } from "astro/loaders"; +import type { MyData } from "./types" + +export class MyLoaderError extends Error { + constructor(message: string, public code?: string) { + super(message); + this.name = 'MyLoaderError'; + } +} + +export function myLoader(config): LiveLoader { + return { + name: 'my-loader', + loadCollection: async () => { + // Return your custom error type + return { + error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), + }; + }, + // ... + }; +} +``` + +When you use `getLiveCollection()` or `getLiveEntry()`, TypeScript will infer the custom error type, allowing you to handle it appropriately: + +```astro +--- +export const prerender = false; // Not needed in 'server' mode + +import { getLiveEntry } from 'astro:content'; +import { MyLoaderError } from "../my-loader"; + +const { entry, error } = await getLiveEntry('products', '123'); + +if (error) { + if (error instanceof MyLoaderError) { + console.error(`Loader error: ${error.message} (code: ${error.code})`); + } else { + console.error(`Unexpected error: ${error.message}`); + } + return Astro.rewrite('/500'); +} +--- +``` + +#### Defining custom filter types + +Live loaders can define custom filter types for both `getLiveCollection()` and `getLiveEntry()`. This enables type-safe querying that matches your API's capabilities, making it easier for users to discover available filters and ensure they are used correctly. If you include JSDoc comments in your filter types, the user will see these in their IDE as hints when using the loader. + +```ts title="store-loader.ts" "EntryFilter, CollectionFilter" {6,8} +import type { LiveLoader } from 'astro/loaders'; +import { fetchProduct, fetchCategory, type Product } from './store-client'; + +interface CollectionFilter { + category?: string; + /** Minimum price to filter products */ + minPrice?: number; + /** Maximum price to filter products */ + maxPrice?: number; +} + +interface EntryFilter { + /** Alias for `sku` */ + id?: string; + slug?: string; + sku?: string; +} + +export function productLoader(config: { + apiKey: string; + endpoint: string; +}): LiveLoader { + return { + name: 'product-loader', + loadCollection: async ({ filter }) => { + // filter is typed as CollectionFilter + const data = await fetchCategory({ + apiKey: config.apiKey, + category: filter?.category ?? 'all', + minPrice: filter?.minPrice, + maxPrice: filter?.maxPrice, + }); + + return { + entries: data.products.map((product) => ({ + id: product.sku, + data: product, + })), + }; + }, + loadEntry: async ({ filter }) => { + // filter is typed as EntryFilter | { id: string } + const product = await fetchProduct({ + apiKey: config.apiKey, + slug: filter.slug, + sku: filter.sku || filter.id, + }); + if (!product) { + return { + error: new Error('Product not found'), + }; + } + return { + id: product.sku, + data: product, + }; + }, + }; +} +``` + +#### Cache hints + +Live loaders can provide cache hints to help with response caching. You can use this data to send HTTP cache headers or otherwise inform your caching strategy. + +```ts title="my-loader.ts" +import type { LiveLoader } from "astro/loaders"; +import { loadStoreProduct, loadStoreProducts, getLastModifiedDate } from "./store"; +import type { Product, ProductEntryFilter, ProductCollectionFilter } from "./types"; + +export function myLoader(config): LiveLoader { + return { + name: 'cached-loader', + loadCollection: async ({ filter }) => { + const products = await loadStoreProducts(filter); + return { + entries: products.map((item) => ({ + id: item.id, + data: item, + // You can optionally provide cache hints for each entry + cacheHint: { + tags: [`product-${item.id}`, `category-${item.category}`], + }, + })), + cacheHint: { + // All fields are optional, and are combined with each entry's cache hints + // tags are merged from all entries + // lastModified is the most recent lastModified of all entries and the collection + lastModified: getLastModifiedDate(products), + tags: ['products'], + }, + }; + }, + loadEntry: async ({ filter }) => { + const item = await loadStoreProduct(filter); + return { + id: item.id, + data: item, + cacheHint: { + lastModified: new Date(item.lastModified), + tags: [`product-${item.id}`, `category-${item.category}`], + }, + }; + }, + }; +} +``` + +You can then use these hints in your pages: + +```astro title="src/pages/store/[id].astro" +--- +export const prerender = false; // Not needed in 'server' mode + +import { getLiveEntry } from 'astro:content'; + +const { entry, error, cacheHint } = await getLiveEntry('products', Astro.params.id); + +if (error) { + return Astro.redirect('/404'); +} + +// Apply cache hints to response headers +if (cacheHint?.tags) { + Astro.response.headers.set('Cache-Tag', cacheHint.tags.join(',')); +} + +if (cacheHint?.lastModified) { + Astro.response.headers.set('Last-Modified', cacheHint.lastModified.toUTCString()); +} +--- + +

    {entry.data.name}

    +

    {entry.data.description}

    ``` +:::note +Cache hints only provide values that can be used in other parts of your project and do not automatically cause the response to be cached by Astro. You can use them to create your own caching strategy, such as setting HTTP headers or using a CDN. +::: + +## Distributing your loader + +Loaders can be defined in your site or as a separate npm package. If you want to share your loader with the community, you can [publish it to npm with the `withastro` and `astro-loader` keywords](/en/reference/publish-to-npm/#packagejson-data). + +A loader should export a function that returns a `LiveLoader` object for live loaders or a `Loader` object for build-time loaders, allowing users to configure it with their own settings. + ## Object loader API -The API for [inline loaders](#inline-loaders) is very simple, and is shown above. This section shows the API for defining an object loader. +

    + +This section shows the API for defining a [build-time object loader](#building-a-loader). ### The `Loader` object -The loader object has the following properties: + +

    + +**Type:** `Loader` +

    + +A loader function returns an object with [two required properties](#loader-properties). In addition to providing a name for the loader, this object describes how to fetch the collection data. + +Optionally, you can return a third property defining a schema to validate your collection entries. Use the [Typescript `satisfies` operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator) instead of a return type annotation to provide type safety inside your loader object and to preserve type inference when your loader is used in a collection. + +#### Loader properties #### `name`

    **Type**: `string` +

    A unique name for the loader, used in logs and [for conditional loading](/en/reference/integrations-reference/#refreshcontent-option). -#### `load` +#### `load()`

    **Type**: (context: LoaderContext) => Promise<void> +

    -An async function that is called at build time to load data and update the store. See [`LoaderContext`](#loadercontext) for more information. +An async function that is called at build time to load data and update the store. It is passed a [`LoaderContext`](#loadercontext) object that contains helper functions and properties for writing your loader's implementation logic, as well as the `store` database and methods for interacting with it. #### `schema`

    -**Type**: `ZodSchema | Promise | (() => ZodSchema | Promise)` +**Type**: `ZodSchema` +

    An optional [Zod schema](/en/guides/content-collections/#defining-datatypes-with-zod) that defines the shape of the entries. It is used to both validate the data and also to generate TypeScript types for the collection. -If a function is provided, it will be called at build time before `load()` to generate the schema. You can use this to dynamically generate the schema based on the configuration options or by introspecting an API. +When you need to dynamically generate the schema at build time based on configuration options or by introspecting an API, use [`createSchema()`](#createschema) instead. + +If present, it will be overridden by any Zod `schema` defined for the collection in the `src/content.config.ts` file. + +#### `createSchema()` + +

    + +**Type**: `() => Promise<{ schema: ZodSchema; types: string }>` + +

    + +An optional async function that returns an object containing a [Zod schema](/en/guides/content-collections/#defining-datatypes-with-zod) and types. It is used to dynamically generate the schema at build time based on the configuration options or by introspecting an API. + +When you only need to provide a static schema, provide a Zod validation object using [`schema`](#schema) instead. + +If present, it will be overridden by any Zod `schema` defined for the collection in the `src/content.config.ts` file. + +The returned `types` contents will be written to a TypeScript file, and must export an `Entry` type or interface: + +```ts title="src/feed-loader.ts" ins={27-35} +import type { Loader } from 'astro/loaders'; +import { z } from 'astro/zod'; +import { loadFeedData, getSchema, getTypes } from "./feed.js"; + +export function myLoader(options: { url: string, apiKey: string }) { + const feedUrl = new URL(options.url); + + return { + name: "feed-loader", + load: async ({ store, parseData }) => { + const feed = await loadFeedData(feedUrl, options.apiKey); + + store.clear(); + + for (const item of feed.items) { + const id = item.guid; + const data = await parseData({ + id, + data: item, + }); + store.set({ + id, + data, + }); + } + }, + createSchema: async () => { + const schema = await getSchema(); + const types = await getTypes(); + + return { + schema, + types: `export type Entry = ${types}`, + }; + }, + } satisfies Loader; +} +``` ### `LoaderContext` -This object is passed to the `load()` method of the loader, and contains the following properties: +This object is passed to the [`load()`](#load) method of the loader, and contains the following properties: #### `collection`

    **Type**: `string` +

    The unique name of the collection. This is the key in the `collections` object in the `src/content.config.ts` file. @@ -278,6 +748,7 @@ The unique name of the collection. This is the key in the `collections` object i

    **Type**: [`DataStore`](#datastore) +

    A database to store the actual data. Use this to update the store with new entries. See [`DataStore`](#datastore) for more information. @@ -287,6 +758,7 @@ A database to store the actual data. Use this to update the store with new entri

    **Type**: `MetaStore` +

    A key-value store scoped to the collection, designed for things like sync tokens and last-modified times. This metadata is persisted between builds alongside the collection data but is only available inside the loader. @@ -302,33 +774,72 @@ meta.set("lastModified", new Date().toISOString());

    **Type**: [`AstroIntegrationLogger`](/en/reference/integrations-reference/#astrointegrationlogger) +

    -A logger that can be used to log messages to the console. Use this instead of `console.log` for more helpful logs that include the loader name in the log message. See [`AstroIntegrationLogger`](/en/reference/integrations-reference/#astrointegrationlogger) for more information. +A logger that can be used to log messages to the console. Use this instead of `console.log` for more helpful logs that include loader-specific content such as the loader name or information about the loading process in the log message. See [`AstroIntegrationLogger`](/en/reference/integrations-reference/#astrointegrationlogger) for more information. + +```ts title="Extract from the file() loader" {10} "logger" +return { + name: 'file-loader', + load: async ({ config, store, logger, watcher }) => { + const url = new URL(fileName, config.root); + const filePath = fileURLToPath(url); + await syncData(filePath, store); + + watcher?.on('change', async (changedPath) => { + if (changedPath === filePath) { + logger.info(`Reloading data from ${fileName}`); + await syncData(filePath, store); + } + }); + }, +}; +``` #### `config`

    **Type**: `AstroConfig` +

    The full, resolved Astro configuration object with all defaults applied. See [the configuration reference](/en/reference/configuration-reference/) for more information. -#### `parseData` +```ts title="Extract from the file() loader" {4} "config" +return { + name: 'file-loader', + load: async ({ config, store, logger, watcher }) => { + const url = new URL(fileName, config.root); + const filePath = fileURLToPath(url); + await syncData(filePath, store); + + watcher?.on('change', async (changedPath) => { + if (changedPath === filePath) { + logger.info(`Reloading data from ${fileName}`); + await syncData(filePath, store); + } + }); + }, +}; +``` + +#### `parseData()`

    **Type**: `(props: ParseDataOptions) => Promise` +

    Validates and parses the data according to the collection schema. Pass data to this function to validate and parse it before storing it in the data store. -```ts title=loader.ts {14-17} +```ts title=loader.ts {15-18} import type { Loader } from "astro/loaders"; import { loadFeed } from "./feed.js"; -export function feedLoader({ url }): Loader { +export function feedLoader({ url }) { const feedUrl = new URL(url); return { name: "feed-loader", @@ -338,8 +849,9 @@ export function feedLoader({ url }): Loader { store.clear(); for (const item of feed.items) { + const id = item.guid; const data = await parseData({ - id: item.guid, + id, data: item, }); store.set({ @@ -348,11 +860,11 @@ export function feedLoader({ url }): Loader { }); } }, - }; + } satisfies Loader; } ``` -#### `renderMarkdown` +#### `renderMarkdown()`

    @@ -362,7 +874,7 @@ export function feedLoader({ url }): Loader { Renders a Markdown string to HTML, returning a `RenderedContent` object. -This allows you to render Markdown content directly within your loaders using the same Markdown processing as Astro's built-in `glob` loader and provides access to the `render()` function and `` component for [rendering body content](/en/guides/content-collections/#rendering-body-content). +This allows you to render Markdown content directly within your loaders using the same Markdown processing as Astro's built-in `glob()` loader and provides access to the `render()` function and `` component for [rendering body content](/en/guides/content-collections/#rendering-body-content). Assign this object to the [rendered](#rendered) field of the [DataEntry](#dataentry) object to allow users to [render the content in a page](/en/guides/content-collections/#rendering-body-content). @@ -370,7 +882,7 @@ Assign this object to the [rendered](#rendered) field of the [DataEntry](#dataen import type { Loader } from 'astro/loaders'; import { loadFromCMS } from './cms.js'; -export function myLoader(settings): Loader { +export function myLoader(settings) { return { name: 'cms-loader', async load({ renderMarkdown, store }) { @@ -387,24 +899,25 @@ export function myLoader(settings): Loader { }); } }, - }; + } satisfies Loader; } ``` -#### `generateDigest` +#### `generateDigest()`

    **Type**: `(data: Record | string) => string` +

    Generates a non-cryptographic content digest of an object or string. This can be used to track if the data has changed by setting [the `digest` field](#digest) of an entry. -```ts title=loader.ts {19,24} +```ts title=loader.ts {20,25} import type { Loader } from "astro/loaders"; import { loadFeed } from "./feed.js"; -export function feedLoader({ url }): Loader { +export function feedLoader({ url }) { const feedUrl = new URL(url); return { name: "feed-loader", @@ -414,8 +927,9 @@ export function feedLoader({ url }): Loader { store.clear(); for (const item of feed.items) { + const id = item.guid; const data = await parseData({ - id: item.guid, + id, data: item, }); @@ -428,7 +942,7 @@ export function feedLoader({ url }): Loader { }); } }, - }; + } satisfies Loader; } ``` @@ -437,6 +951,7 @@ export function feedLoader({ url }): Loader {

    **Type**: `FSWatcher` +

    When running in dev mode, this is a filesystem watcher that can be used to trigger updates. See [`ViteDevServer`](https://vite.dev/guide/api-javascript.html#vitedevserver) for more information. @@ -464,12 +979,16 @@ return {

    **Type**: `Record` +

    If the loader has been triggered by an integration, this may optionally contain extra data set by that integration. It is only set when the loader is triggered by an integration. See the [`astro:server:setup`](/en/reference/integrations-reference/#refreshcontent-option) hook reference for more information. -```ts title=loader.ts {5-8} -export function myLoader(options: { url: string }): Loader { +```ts title=loader.ts {8-11} +import type { Loader } from "astro/loaders"; +import { processWebhook } from "./lib/webhooks"; + +export function myLoader(options: { url: string }) { return { name: "my-loader", load: async ({ refreshContextData, store, logger }) => { @@ -479,7 +998,7 @@ export function myLoader(options: { url: string }): Loader { } // ... }, - }; + } satisfies Loader; } ``` @@ -487,11 +1006,12 @@ export function myLoader(options: { url: string }): Loader { The data store is a loader's interface to the content collection data. It is a key-value (KV) store, scoped to the collection, and therefore a loader can only access the data for its own collection. -#### `get` +#### `get()`

    **Type**: (key: string) => DataEntry | undefined +

    Get an entry from the store by its ID. Returns `undefined` if the entry does not exist. @@ -502,19 +1022,21 @@ const existingEntry = store.get("my-entry"); The returned object is a [`DataEntry`](#dataentry) object. -#### `set` +#### `set()`

    **Type**: (entry: DataEntry) => boolean +

    Used after data has been [validated and parsed](#parsedata) to add an entry to the store, returning `true` if the entry was set. This returns `false` when the [`digest`](#digest) property determines that an entry has not changed and should not be updated. -```ts title=loader.ts {7-14} +```ts title=loader.ts {8-15} for (const item of feed.items) { + const id = item.guid; const data = await parseData({ - id: item.guid, + id, data: item, }); const digest = generateDigest(data); @@ -529,56 +1051,62 @@ Used after data has been [validated and parsed](#parsedata) to add an entry to t } ``` -#### `entries` +#### `entries()`

    -**Type**: `() => Array<[id: string, DataEntry]>` +**Type**: () => Array\<[id: string, DataEntry]\> +

    Get all entries in the collection as an array of key-value pairs. -#### `keys` +#### `keys()`

    **Type**: `() => Array` +

    Get all the keys of the entries in the collection. -#### `values` +#### `values()`

    -**Type**: `() => Array` +**Type**: () => Array\<DataEntry\> +

    Get all entries in the collection as an array. -#### `delete` +#### `delete()`

    **Type**: `(key: string) => void` +

    Delete an entry from the store by its ID. -#### `clear` +#### `clear()`

    **Type**: `() => void` +

    Clear all entries from the collection. -#### `has` +#### `has()`

    **Type**: `(key: string) => boolean` +

    Check if an entry exists in the store by its ID. @@ -592,26 +1120,29 @@ This is the type of the object that is stored in the data store. It has the foll

    **Type**: `string` +

    -An identifier for the entry, which must be unique within the collection. This is used to look up the entry in the store and is the key used with `getEntry` for that collection. +An identifier for the entry, which must be unique within the collection. This is used to look up the entry in the store and is the key used with [`getEntry()`](/en/reference/modules/astro-content/#getentry) for that collection. #### `data`

    **Type**: `Record` +

    The actual data for the entry. When a user accesses the collection, this will have TypeScript types generated according to the collection schema. -It is the loader's responsibility to use [`parseData`](#parsedata) to validate and parse the data before storing it in the data store: no validation is done when getting or setting the data. +It is the loader's responsibility to use [`parseData()`](#parsedata) to validate and parse the data before storing it in the data store: no validation is done when getting or setting the data. #### `filePath`

    **Type**: `string | undefined` +

    A path to the file that is the source of this entry, relative to the root of the site. This only applies to file-based loaders and is used to resolve paths such as images or other assets. @@ -623,6 +1154,7 @@ If not set, then any fields in the schema that use [the `image()` helper](/en/gu

    **Type**: `string | undefined` +

    The raw body of the entry, if applicable. If the entry includes [rendered content](#rendered), then this field can be used to store the raw source. This is optional and is not used internally. @@ -632,6 +1164,7 @@ The raw body of the entry, if applicable. If the entry includes [rendered conten

    **Type**: `string | undefined` +

    An optional content digest for the entry. This can be used to check if the data has changed. @@ -645,6 +1178,7 @@ The format of the digest is up to the loader, but it must be a string that chang

    **Type**: `RenderedContent | undefined` +

    Stores an object with an entry's rendered content and metadata if it has been rendered to HTML. For example, this can be used to store the rendered content of a Markdown entry, or HTML from a CMS. @@ -671,3 +1205,234 @@ The format of the `RenderedContent` object is: ``` If the entry has Markdown content then you can use the [`renderMarkdown()`](#rendermarkdown) function to generate this object from the Markdown string. + +## Live loader API + +

    + +This section shows the API for defining a [live loader](#live-loaders). + +### The `LiveLoader` object + +

    + +**Type:** `LiveLoader` + +

    + +A live loader function returns an object with [three required live loader properties](#live-loader-properties). In addition to providing a name for the loader, this object describes how to fetch both single entries and an entire collection from your live data source. + +Use the `LiveLoader` generic type to provide type safety in your loader. This type accepts the following type parameters, in this order: + +- **`TData`** (defaults to `Record`): The data structure of each entry returned by the loader. +- **`TEntryFilter`** (defaults to `never`): The filter object type accepted by [`getLiveEntry()`](/en/reference/modules/astro-content/#getliveentry) and accessible in [`loadEntry()`](#loadentry). Use `never` when you don't support filtering single entries. +- **`TCollectionFilter`** (defaults to `never`): The filter object type accepted by [`getLiveCollection()`](/en/reference/modules/astro-content/#getlivecollection) and accessible in [`loadCollection()`](#loadcollection). Use `never` when you don't support filtering collections. +- **`TError`** (defaults to `Error`): A [custom `Error` class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types) that can be returned by the loader for more granular error handling. + +#### Live loader properties + +##### `name` + +

    + +**Type:** `string` + +

    + + +A unique name for the loader, used in logs. + +##### `loadCollection()` + +

    + +**Type:** (context: LoadCollectionContext\) => Promise\<LiveDataCollection\ | \{ error: TError; \}\> + +

    + +Defines a method to load a collection of entries. This function receives a [context object](#loadcollectioncontext) containing an optional `filter` property and must return the data associated with this collection or the errors. + +##### `loadEntry()` + +

    + +**Type:** (context: LoadEntryContext\) => Promise\<LiveDataEntry\ | undefined | \{ error: TError; \}\> + +

    + +Defines a method to load a single entry. This function receives a [context object](#loadentrycontext) containing a `filter` property and returns either the data associated with the requested entry, `undefined` when the entry cannot be found, or the errors. + +### `LoadCollectionContext` + +

    + +**Type:** `{ filter?: TCollectionFilter; }` + +

    + +This object is passed to the [`loadCollection()` method](#loadcollection) of the loader and contains the following properties: + +#### `filter` + +

    + +**Type:** `Record | never`
    +**Default:** `never` + +

    + +An object describing the [filters supported by your loader](#defining-custom-filter-types). + +### `LoadEntryContext` + +

    + +**Type:** `{ filter: TEntryFilter; }` + +

    + +This object is passed to the [`loadEntry()` method](#loadentry) of the loader and contains the following properties: + +#### `filter` + +

    + +**Type:** `Record | never`
    +**Default:** `never` + +

    + +An object describing the [filters supported by your loader](#defining-custom-filter-types). + + +### `LiveDataEntry` + +

    + +**Type:** \{ id: string; data: TData; rendered?: \{ html: string \}; cacheHint?: CacheHint; \} + +

    + +This is the type object that is returned by the [`loadEntry()`](#loadentry) method. It contains the following properties: + +#### `id` + +

    + +**Type**: `string` + +

    + +An identifier for the entry, which must be unique within the collection. This is the key used with [`getLiveEntry()`](/en/reference/modules/astro-content/#getliveentry) for that collection. + +#### `data` + +

    + +**Type**: `Record` + +

    + +The actual data for the entry. When a user accesses the collection, this will have TypeScript types generated according to the collection schema. + +It is the loader's responsibility to validate and parse the data before returning it. + +#### `rendered` + +

    + +**Type**: `{ html: string }` + +

    + +An object with an entry's rendered content if it has been rendered to HTML. For example, this can be the rendered content of a Markdown entry, or HTML from a CMS. + +If this field is provided, then [the `render()` function and `` component](/en/guides/content-collections/#rendering-body-content) are available to render the entry in a page. + +If the loader does not return a `rendered` property for an entry, the `` component will render nothing. + +#### `cacheHint` + +

    + +**Type:** [`CacheHint`](#cachehint-2) + +

    + +An optional object to provide a hint for how to cache this specific entry. + +### `LiveDataCollection` + +

    + +**Type:** \{ entries: Array\<LiveDataEntry\\>; cacheHint?: CacheHint; \} + +

    + +This is the type object that is returned by the [`loadCollection()`](#loadcollection) method. It contains the following properties: + +#### `entries` + +

    + +**Type:** Array\<LiveDataEntry\\> + +

    + +An array of [`LiveDataEntry`](#livedataentry) objects. + +#### `cacheHint` + +

    + +**Type:** [`CacheHint`](#cachehint-2) + +

    + +An optional object providing guidance on how to cache this collection. This object will be merged with the cache hints defined for each individual entry, if provided. + +### `CacheHint` + +An object that loaders can return through the `cacheHint` property in [`LiveDataCollection`](#livedatacollection) or [`LiveDataEntry`](#livedataentry) to provide hints to assist in caching the response. This contains the following properties: + +#### `tags` + +

    + +**Type:** `Array` + +

    + +An array of string identifiers allowing fine-grained cache control. This allows you to group related content and selectively invalidate cached responses when specific content changes. + +The following example defines cache hint tags for a collection of posts filtered by author: + +```ts +return { + /* ... */ + cacheHint: { + tags: ["posts", `posts-${filter.author}`], + }, +}; +``` + +#### `lastModified` + +

    + +**Type:** `Date` + +

    + +The date of the last modification of the content (e.g., the last update of an entry in a collection). This can be used to set HTTP cache headers like [`Last-Modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Last-Modified) and [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Modified-Since). + +The following example defines a cache hint for a single product using its last update date: + +```ts +return { + /* ... */ + cacheHint: { + lastModified: new Date(product.updatedAt) + }, +}; +``` diff --git a/src/content/docs/en/reference/errors/astro-glob-no-match.mdx b/src/content/docs/en/reference/errors/astro-glob-no-match.mdx index 0e440b87c3717..e0f6b9d83d6b6 100644 --- a/src/content/docs/en/reference/errors/astro-glob-no-match.mdx +++ b/src/content/docs/en/reference/errors/astro-glob-no-match.mdx @@ -17,8 +17,3 @@ import DontEditWarning from '~/components/DontEditWarning.astro' ## What went wrong? `Astro.glob()` did not return any matching files. There might be a typo in the glob pattern. - -**See Also:** -- [Astro.glob](/en/reference/api-reference/#astroglob) - - diff --git a/src/content/docs/en/reference/errors/astro-glob-used-outside.mdx b/src/content/docs/en/reference/errors/astro-glob-used-outside.mdx index 0046b3892b504..f3473054c151f 100644 --- a/src/content/docs/en/reference/errors/astro-glob-used-outside.mdx +++ b/src/content/docs/en/reference/errors/astro-glob-used-outside.mdx @@ -17,8 +17,3 @@ import DontEditWarning from '~/components/DontEditWarning.astro' ## What went wrong? `Astro.glob()` can only be used in `.astro` files. You can use [`import.meta.glob()`](https://vite.dev/guide/features.html#glob-import) instead to achieve the same result. - -**See Also:** -- [Astro.glob](/en/reference/api-reference/#astroglob) - - diff --git a/src/content/docs/en/reference/errors/content-collection-invalid-type.mdx b/src/content/docs/en/reference/errors/content-collection-invalid-type.mdx new file mode 100644 index 0000000000000..79332174c63d2 --- /dev/null +++ b/src/content/docs/en/reference/errors/content-collection-invalid-type.mdx @@ -0,0 +1,23 @@ +--- +# NOTE: This file is auto-generated from 'scripts/error-docgen.mjs' +# Do not make edits to it directly, they will be overwritten. +# Instead, change this file: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +# Translators, please remove this note and the component. + +title: Content collection invalid type. +i18nReady: true +githubURL: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +--- + +import DontEditWarning from '~/components/DontEditWarning.astro' + + + + +> Invalid collection type "data". Remove the type from your collection definition in your content config file. + +### What went wrong? + +Content collections should no longer have a type field. Remove this field from your content config file. + +See the [Astro 6 migration guide](/en/guides/upgrade-to/v6/#removed-legacy-content-collections) for more information. \ No newline at end of file diff --git a/src/content/docs/en/reference/errors/content-collection-missing-loader.mdx b/src/content/docs/en/reference/errors/content-collection-missing-loader.mdx new file mode 100644 index 0000000000000..13652a54399f6 --- /dev/null +++ b/src/content/docs/en/reference/errors/content-collection-missing-loader.mdx @@ -0,0 +1,24 @@ +--- +# NOTE: This file is auto-generated from 'scripts/error-docgen.mjs' +# Do not make edits to it directly, they will be overwritten. +# Instead, change this file: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +# Translators, please remove this note and the component. + +title: Content collection missing loader. +i18nReady: true +githubURL: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +--- + +import DontEditWarning from '~/components/DontEditWarning.astro' + + + + +> Collections must have a loader defined. Check your collection definitions in your content config file. + +### What went wrong? + +A content collection is missing a loader definition. Make sure that each collection in your content config file has a loader. + +**See Also:** +- [Content collections configuration](/en/guides/content-collections/) diff --git a/src/content/docs/en/reference/errors/file-glob-not-supported.mdx b/src/content/docs/en/reference/errors/file-glob-not-supported.mdx index 9918fdd56ccb5..be9f4ce3a5767 100644 --- a/src/content/docs/en/reference/errors/file-glob-not-supported.mdx +++ b/src/content/docs/en/reference/errors/file-glob-not-supported.mdx @@ -19,6 +19,6 @@ import DontEditWarning from '~/components/DontEditWarning.astro' The `file` loader must be passed a single local file. Glob patterns are not supported. Use the built-in `glob` loader to create entries from patterns of multiple local files. **See Also:** -- [Astro's built-in loaders](/en/guides/content-collections/#built-in-loaders) +- [Astro's built-in `file()` loader](/en/guides/content-collections/#the-file-loader) diff --git a/src/content/docs/en/reference/errors/file-parser-not-found.mdx b/src/content/docs/en/reference/errors/file-parser-not-found.mdx index fb1e433ea18fd..5d614b41dee66 100644 --- a/src/content/docs/en/reference/errors/file-parser-not-found.mdx +++ b/src/content/docs/en/reference/errors/file-parser-not-found.mdx @@ -19,6 +19,6 @@ import DontEditWarning from '~/components/DontEditWarning.astro' The `file` loader can’t determine which parser to use. Please provide a custom parser (e.g. `csv-parse`) to create a collection from your file type. **See Also:** -- [Passing a `parser` to the `file` loader](/en/guides/content-collections/#parser-function) +- [Passing a `parser` to the `file` loader](/en/reference/content-loader-reference/#parser) diff --git a/src/content/docs/en/reference/errors/legacy-content-config-error.mdx b/src/content/docs/en/reference/errors/legacy-content-config-error.mdx new file mode 100644 index 0000000000000..0386c68efce2d --- /dev/null +++ b/src/content/docs/en/reference/errors/legacy-content-config-error.mdx @@ -0,0 +1,26 @@ +--- +# NOTE: This file is auto-generated from 'scripts/error-docgen.mjs' +# Do not make edits to it directly, they will be overwritten. +# Instead, change this file: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +# Translators, please remove this note and the component. + +title: Legacy content config error. +i18nReady: true +githubURL: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +--- + +import DontEditWarning from '~/components/DontEditWarning.astro' + + + + +> Legacy content config file found. + +### What went wrong? + +A legacy content config file was found. Move the file to `src/content.config.ts` and update any collection definitions if needed. + +See the [Astro 6 migration guide](/en/guides/upgrade-to/v6/#removed-legacy-content-collections) for more information. + +**See Also:** +- [Content collections configuration](/en/guides/content-collections/#defining-build-time-content-collections) diff --git a/src/content/docs/en/reference/errors/live-content-config-error.mdx b/src/content/docs/en/reference/errors/live-content-config-error.mdx index eee336c506e13..e8b5edb90662f 100644 --- a/src/content/docs/en/reference/errors/live-content-config-error.mdx +++ b/src/content/docs/en/reference/errors/live-content-config-error.mdx @@ -20,6 +20,6 @@ The schema cannot be a function for live collections. Please use a schema object Error in live content config. **See Also:** -- [Experimental live content](/en/reference/experimental-flags/live-content-collections/) +- [Live content collections](/en/guides/content-collections/#live-content-collections) diff --git a/src/content/docs/en/reference/experimental-flags/fail-on-prerender-conflict.mdx b/src/content/docs/en/reference/experimental-flags/fail-on-prerender-conflict.mdx deleted file mode 100644 index d55f79acbf8af..0000000000000 --- a/src/content/docs/en/reference/experimental-flags/fail-on-prerender-conflict.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Experimental prerender conflict error -sidebar: - label: Prerender conflict error -i18nReady: true ---- - -import Since from '~/components/Since.astro' - -

    - -**Type:** `boolean`
    -**Default:** `false`
    - -

    - -Turns prerender conflict warnings into errors during the build process. - -Astro currently warns you during the build about any conflicts between multiple dynamic routes that can result in the same output path. For example `/blog/[slug]` and `/blog/[...all]` both could try to prerender the `/blog/post-1` path. In such cases, Astro renders only the [highest priority route](/en/guides/routing/#route-priority-order) for the conflicting path. This allows your site to build successfully, although you may discover that some pages are rendered by unexpected routes. - -With this experimental flag set, the build will instead fail immediately with an error. This will require you to resolve any routing conflicts immediately, and will ensure that Astro is building your routes as you intend. - -To enable this behavior, add the `experimental.failOnPrerenderConflict` feature flag to your Astro config: - -```js title="astro.config.mjs" ins={4-6} -import { defineConfig } from "astro/config" - -defineConfig({ - experimental: { - failOnPrerenderConflict: true, - }, -}); -``` - -## Usage - -After enabling this flag, you may encounter errors about conflicting prerendered routes when you attempt to build your project. If this happens, you will need to update one or more of your [dynamic routes](/en/guides/routing/#dynamic-routes) to avoid ambiguous routing. diff --git a/src/content/docs/en/reference/experimental-flags/heading-id-compat.mdx b/src/content/docs/en/reference/experimental-flags/heading-id-compat.mdx deleted file mode 100644 index a19d1c9d01e49..0000000000000 --- a/src/content/docs/en/reference/experimental-flags/heading-id-compat.mdx +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Experimental Markdown heading ID compatibility -sidebar: - label: Markdown heading ID compatibility -i18nReady: true ---- - -import Since from '~/components/Since.astro' - -

    - -**Type:** `boolean`
    -**Default:** `false`
    - -

    - -The `experimental.headingIdCompat` flag makes the IDs generated by Astro for Markdown headings compatible with common platforms like GitHub and npm. - -To enable heading ID compatibility, set the flag to `true` in your Astro configuration: - -```js title="astro.config.mjs" ins={4-6} -import { defineConfig } from "astro/config" - -export default defineConfig({ - experimental: { - headingIdCompat: true, - } -}) -``` - -## Usage - -This experimental flag allows you to retain the trailing hyphens on the end of IDs for Markdown headings ending in special characters, creating IDs compatible with those generated by other common platforms. It requires no specific usage and only affects how Astro generates the `id` for your headings written using Markdown syntax. - -Astro, like many platforms, uses the popular [`github-slugger`](https://github.com/Flet/github-slugger) package to convert the text content of a Markdown heading to a slug to use in IDs. This experimental flag allows you to omit Astro's additional default processing step that strips a trailing hyphen from the end of IDs for headings ending in special characters. - -For example, the following Markdown heading: - -```md -## `` -``` - -will generate the following HTML in Astro by default: - -```html "picture" -

    <Picture />

    -``` - -Using `experimental.headingIdCompat`, the same Markdown will generate the following HTML, which is identical to that of platforms such as GitHub: - -```html "picture-" -

    <Picture />

    -``` - -In a future major version, Astro will switch to use the compatible ID style by default, but you can opt in to the future behavior early using the `experimental.headingIdCompat` flag. - -## Usage with `rehypeHeadingIds` plugin - -If you are [using the `rehypeHeadingIds` plugin](/en/guides/markdown-content/#heading-ids-and-plugins) directly, opt in to the compatibility mode when passing the plugin in your Astro configuration: - - -```js title="astro.config.mjs" {8} -import { defineConfig } from 'astro/config'; -import { rehypeHeadingIds } from '@astrojs/markdown-remark'; -import { otherPluginThatReliesOnHeadingIDs } from 'some/plugin/source'; - -export default defineConfig({ - markdown: { - rehypePlugins: [ - [rehypeHeadingIds, { headingIdCompat: true }], - otherPluginThatReliesOnHeadingIDs, - ], - }, -}); -``` diff --git a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx deleted file mode 100644 index 8e2b2356bd070..0000000000000 --- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx +++ /dev/null @@ -1,635 +0,0 @@ ---- -title: Experimental live content collections -sidebar: - label: Live content collections -i18nReady: true ---- - -import Since from '~/components/Since.astro'; - -

    - -**Type:** `boolean`
    -**Default:** `false`
    - - -

    - -Enables support for live content collections in your project. - -Live content collections are a new type of [content collection](/en/guides/content-collections/) that fetch their data at runtime rather than build time. This allows you to access frequently updated data from CMSs, APIs, databases, or other sources using a unified API, without needing to rebuild your site when the data changes. - -## Basic usage - -To enable the feature, make sure you have an adapter configured for [on-demand rendering](/en/guides/on-demand-rendering/) and add the `experimental.liveContentCollections` flag to your `astro.config.mjs` file: - -```js title="astro.config.mjs" -{ - experimental: { - liveContentCollections: true, - }, -} -``` - -Then create a new `src/live.config.ts` file (alongside your `src/content.config.ts` if you have one) to define your live collections with a [live loader](#creating-a-live-loader) and optionally a [schema](#using-zod-schemas) using the new `defineLiveCollection()` function from the `astro:content` module. - -```ts title="src/live.config.ts" -import { defineLiveCollection } from 'astro:content'; -import { storeLoader } from '@mystore/astro-loader'; - -const products = defineLiveCollection({ - loader: storeLoader({ - apiKey: process.env.STORE_API_KEY, - endpoint: 'https://api.mystore.com/v1', - }), -}); - -export const collections = { products }; -``` - -You can then use the dedicated `getLiveCollection()` and `getLiveEntry()` functions to access your live data: - -```astro ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveCollection, getLiveEntry } from 'astro:content'; - -// Get all products -const { entries: allProducts, error } = await getLiveCollection('products'); -if (error) { - // Handle error appropriately - console.error(error.message); -} - -// Get products with a filter (if supported by your loader) -const { entries: electronics } = await getLiveCollection('products', { category: 'electronics' }); - -// Get a single product by ID (string syntax) -const { entry: product, error: productError } = await getLiveEntry('products', Astro.params.id); -if (productError) { - return Astro.redirect('/404'); -} - -// Get a single product with a custom query (if supported by your loader) using a filter object -const { entry: productBySlug } = await getLiveEntry('products', { slug: Astro.params.slug }); ---- -``` - -## When to use live content collections - -Live content collections are designed for data that changes frequently and needs to be up-to-date when a page is requested. Consider using them when: - -- **You need real-time information** (e.g. user-specific data, current stock levels) -- **You want to avoid constant rebuilds** for content that changes often -- **Your data updates frequently** (e.g. up-to-the-minute product inventory, prices, availability) -- **You need to pass dynamic filters** to your data source based on user input or request parameters -- **You're building preview functionality** for a CMS where editors need to see draft content immediately - -In contrast, use build-time content collections when: - -- **Performance is critical** and you want to pre-render data at build time -- **Your data is relatively static** (e.g., blog posts, documentation, product descriptions) -- **You want to benefit from build-time optimization** and caching -- **You need to process MDX** or perform image optimization -- **Your data can be fetched once and reused** across multiple builds - -See the [limitations of experimental live collections](#live-collection-limitations) and [key differences from build-time collections](#differences-from-build-time-collections) for more details on choosing between live and preloaded collections. - -## Using live collections - -You can [create your own live loaders](#creating-a-live-loader) for your data source, or you can use community loaders distributed as npm packages. Here's how you could use example CMS and e-commerce loaders: - -```ts title="src/live.config.ts" -import { defineLiveCollection } from 'astro:content'; -import { cmsLoader } from '@example/cms-astro-loader'; -import { productLoader } from '@example/store-astro-loader'; - -const articles = defineLiveCollection({ - loader: cmsLoader({ - apiKey: process.env.CMS_API_KEY, - contentType: 'article', - }), -}); - -const products = defineLiveCollection({ - loader: productLoader({ - apiKey: process.env.STORE_API_KEY, - }), -}); - -export const collections = { articles, products }; -``` - -You can then get content from both loaders with a unified API: - -```astro ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveCollection, getLiveEntry } from 'astro:content'; - -// Use loader-specific filters -const { entries: draftArticles } = await getLiveCollection('articles', { - status: 'draft', - author: 'john-doe', -}); - -// Get a specific product by ID -const { entry: product } = await getLiveEntry('products', Astro.params.slug); ---- -``` - -### Error handling - -Live loaders can fail due to network issues, API errors, or validation problems. The API is designed to make error handling explicit. - -When you call `getLiveCollection()` or `getLiveEntry()`, the error will be one of: - -- The error type defined by the loader (if it returned an error) -- A `LiveEntryNotFoundError` if the entry was not found -- A `LiveCollectionValidationError` if the collection data does not match the expected schema -- A `LiveCollectionCacheHintError` if the cache hint is invalid -- A `LiveCollectionError` for other errors, such as uncaught errors thrown in the loader - -These errors have a static `is()` method that you can use to check the type of error at runtime: - -```astro "LiveEntryNotFoundError.is(error)" ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveEntry, LiveEntryNotFoundError } from 'astro:content'; - -const { entry, error } = await getLiveEntry('products', Astro.params.id); - -if (error) { - if (LiveEntryNotFoundError.is(error)) { - console.error(`Product not found: ${error.message}`); - Astro.response.status = 404; - } else { - console.error(`Error loading product: ${error.message}`); - return Astro.redirect('/500'); - } -} ---- -``` - -## Creating a live loader - -A live loader is an object with two methods: `loadCollection()` and `loadEntry()`. These methods should handle errors gracefully and return either data or an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object. - -The standard pattern is to export a function that returns this loader object, allowing you to pass configuration options like API keys or endpoints. - -Here's a basic example: - -```ts title="myloader.ts" -import type { LiveLoader } from 'astro/loaders'; -import { fetchFromCMS } from './cms-client.js'; - -interface Article { - id: string; - title: string; - content: string; - author: string; -} - -export function articleLoader(config: { apiKey: string }): LiveLoader
    { - return { - name: 'article-loader', - loadCollection: async ({ filter }) => { - try { - const articles = await fetchFromCMS({ - apiKey: config.apiKey, - type: 'article', - filter, - }); - - return { - entries: articles.map((article) => ({ - id: article.id, - data: article, - })), - }; - } catch (error) { - return { - error: new Error(`Failed to load articles: ${error.message}`), - }; - } - }, - loadEntry: async ({ filter }) => { - try { - // filter will be { id: "some-id" } when called with a string - const article = await fetchFromCMS({ - apiKey: config.apiKey, - type: 'article', - id: filter.id, - }); - - if (!article) { - return { - error: new Error('Article not found'), - }; - } - - return { - id: article.id, - data: article, - }; - } catch (error) { - return { - error: new Error(`Failed to load article: ${error.message}`), - }; - } - }, - }; -} -``` - -### Rendering content - -A loader can add support for directly rendered content by returning [a `rendered` property](/en/reference/content-loader-reference/#rendered) in the entry. This allows you to use [the `render()` function and `` component](/en/guides/content-collections/#rendering-body-content) to render the content directly in your pages. -If the loader does not return a `rendered` property for an entry, the `` component will render nothing. - -```ts title="myloader.ts" {16-19} -// ... -export function articleLoader(config: { apiKey: string }): LiveLoader
    { - return { - name: 'article-loader', - loadEntry: async ({ filter }) => { - try { - const article = await fetchFromCMS({ - apiKey: config.apiKey, - type: 'article', - id: filter.id, - }); - - return { - id: article.id, - data: article, - rendered: { - // Assuming the CMS returns HTML content - html: article.htmlContent, - }, - }; - } catch (error) { - return { - error: new Error(`Failed to load article: ${error.message}`), - }; - } - }, - // ... - }; -} -``` - -You can then render both content and metadata from live collection entries in pages using the same method as built-time collections. You also have access to any [error returned by the live loader](#error-handling-in-loaders), for example, to rewrite to a 404 page when content cannot be displayed: - -```astro "render(entry)" "" ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveEntry, render } from 'astro:content'; -const { entry, error } = await getLiveEntry('articles', Astro.params.id); -if (error) { - return Astro.rewrite('/404'); -} - -const { Content } = await render(entry); ---- - -

    {entry.data.title}

    - -``` - -### Error handling in loaders - -Loaders should handle all errors and return an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) subclass for errors. You can create custom error types and use them for more specific error handling if needed. If an error is thrown in the loader, it will be caught and returned, wrapped in a `LiveCollectionError`. You can also create [custom error types](#custom-error-types) for proper typing. - -Astro will generate some errors itself, depending on the response from the loader: - -- If `loadEntry` returns `undefined`, Astro will return a `LiveEntryNotFoundError` to the user. -- If a schema is defined for the collection and the data does not match the schema, Astro will return a `LiveCollectionValidationError`. -- If the loader returns an invalid cache hint, Astro will return a `LiveCollectionCacheHintError`. The `cacheHint` field is optional, so if you do not have valid data to return, you can simply omit it. - -```ts title="my-loader.ts" {9-11} -import type { LiveLoader } from 'astro/loaders'; -import { MyLoaderError } from './errors.js'; - -export function myLoader(config): LiveLoader { - return { - name: 'my-loader', - loadCollection: async ({ filter }) => { - // Return your custom error type - return { - error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), - }; - }, - // ... - }; -} -``` - -### Distributing your loader - -Loaders can be defined in your site or as a separate npm package. If you want to share your loader with the community, you can [publish it to NPM with the `astro-component` and `astro-loader` keywords](/en/reference/publish-to-npm/#packagejson-data). - -The loader should export a function that returns the `LiveLoader` object, allowing users to configure it with their own settings. - -## Type safety - -Like regular content collections, live collections can be typed to ensure type safety in your data. [Using Zod schemas](#using-zod-schemas) is supported, but not required to define types for live collections. Unlike preloaded collections defined at build time, live loaders can instead choose to pass generic types to the `LiveLoader` interface. -You can define the types for your collection and entry data, as well as custom filter types for querying, and custom error types for error handling. - -### Type-safe data - -Live loaders can define types for the data they return. This allows TypeScript to provide type checking and autocompletion when working with the data in your components. - -```ts title="store-loader.ts" "LiveLoader" "type Product" -import type { LiveLoader } from 'astro/loaders'; -import { fetchProduct, fetchCategory, type Product } from './store-client'; - -export function storeLoader(): LiveLoader { - // ... -} -``` - -When you use `getLiveCollection()` or `getLiveEntry()`, TypeScript will infer the types based on the loader's definition: - -```astro ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveEntry } from 'astro:content'; -const { entry: product } = await getLiveEntry('products', '123'); -// TypeScript knows product.data is of type Product -console.log(product?.data.name); ---- -``` - -### Type-safe filters - -Live loaders can define custom filter types for both `getLiveCollection()` and `getLiveEntry()`. This enables type-safe querying that matches your API's capabilities, making it easier for users to discover available filters and ensure they are used correctly. If you include JSDoc comments in your filter types, the user will see these in their IDE as hints when using the loader. - -```ts title="store-loader.ts" "EntryFilter, CollectionFilter" {6,8} -import type { LiveLoader } from 'astro/loaders'; -import { fetchProduct, fetchCategory, type Product } from './store-client'; - -interface CollectionFilter { - category?: string; - /** Minimum price to filter products */ - minPrice?: number; - /** Maximum price to filter products */ - maxPrice?: number; -} - -interface EntryFilter { - /** Alias for `sku` */ - id?: string; - slug?: string; - sku?: string; -} - -export function productLoader(config: { - apiKey: string; - endpoint: string; -}): LiveLoader { - return { - name: 'product-loader', - loadCollection: async ({ filter }) => { - // filter is typed as CollectionFilter - const data = await fetchCategory({ - apiKey: config.apiKey, - category: filter?.category ?? 'all', - minPrice: filter?.minPrice, - maxPrice: filter?.maxPrice, - }); - - return { - entries: data.products.map((product) => ({ - id: product.sku, - data: product, - })), - }; - }, - loadEntry: async ({ filter }) => { - // filter is typed as EntryFilter | { id: string } - const product = await fetchProduct({ - apiKey: config.apiKey, - slug: filter.slug, - sku: filter.sku || filter.id, - }); - if (!product) { - return { - error: new Error('Product not found'), - }; - } - return { - id: product.sku, - entry: product, - }; - }, - }; -} -``` - -### Custom error types - -You can create custom error types for [errors returned by your loader](#error-handling-in-loaders) and pass them as a generic to get proper typing: - -```ts title="my-loader.ts" -class MyLoaderError extends Error { - constructor( - message: string, - public code?: string - ) { - super(message); - this.name = 'MyLoaderError'; - } -} - -export function myLoader(config): LiveLoader { - return { - name: 'my-loader', - loadCollection: async ({ filter }) => { - // Return your custom error type - return { - error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), - }; - }, - // ... - }; -} -``` - -When you use `getLiveCollection()` or `getLiveEntry()`, TypeScript will infer the custom error type, allowing you to handle it appropriately: - -```astro ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveEntry } from 'astro:content'; - -const { entry, error } = await getLiveEntry('products', '123'); - -if (error) { - if (error.name === 'MyLoaderError') { - console.error(`Loader error: ${error.message} (code: ${error.code})`); - } else { - console.error(`Unexpected error: ${error.message}`); - } - return Astro.rewrite('/500'); -} ---- -``` - -## Using Zod schemas - -Just like with build-time collections, you can use [Zod schemas](/en/guides/content-collections/#defining-the-collection-schema) with live collections to validate and transform data at runtime. When you define a schema, it takes precedence over [the loader's types](#type-safe-data) when you query the collection: - -```ts title="src/live.config.ts" -import { defineLiveCollection } from 'astro:content'; -import { z } from 'astro/zod'; -import { apiLoader } from './loaders/api-loader'; - -const products = defineLiveCollection({ - loader: apiLoader({ endpoint: process.env.API_URL }), - schema: z - .object({ - id: z.string(), - name: z.string(), - price: z.number(), - // Transform the API's category format - category: z.string().transform((str) => str.toLowerCase().replace(/\s+/g, '-')), - // Coerce the date to a Date object - createdAt: z.coerce.date(), - }) - .transform((data) => ({ - ...data, - // Add a formatted price field - displayPrice: `$${data.price.toFixed(2)}`, - })), -}); - -export const collections = { products }; -``` - -When using Zod schemas, validation errors are automatically caught and returned as `AstroError` objects: - -```astro ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveEntry, LiveCollectionValidationError } from 'astro:content'; - -const { entry, error } = await getLiveEntry('products', '123'); - -// You can handle validation errors specifically -if (LiveCollectionValidationError.is(error)) { - console.error(error.message); - return Astro.rewrite('/500'); -} - -// TypeScript knows entry.data matches your Zod schema, not the loader's type -console.log(entry?.data.displayPrice); // e.g., "$29.99" ---- -``` - -## Cache hints - -Live loaders can provide cache hints to help with response caching. You can use this data to send HTTP cache headers or otherwise inform your caching strategy. - -```ts title="my-loader.ts" -import type { LiveLoader } from "astro/loaders"; -import { loadStoreProduct, loadStoreProducts, getLastModifiedDate } from "./store"; -import type { MyData } from "./types"; - -export function myLoader(config): LiveLoader { - return { - name: 'cached-loader', - loadCollection: async ({ filter }) => { - const products = await loadStoreProducts(filter); - return { - entries: products.map((item) => ({ - id: item.id, - data: item, - // You can optionally provide cache hints for each entry - cacheHint: { - tags: [`product-${item.id}`, `category-${item.category}`], - }, - })), - cacheHint: { - // All fields are optional, and are combined with each entry's cache hints - // tags are merged from all entries - // lastModified is the most recent lastModified of all entries and the collection - lastModified: getLastModifiedDate(products), - tags: ['products'], - }, - }; - }, - loadEntry: async ({ filter }) => { - const item = await loadStoreProduct(filter); - return { - id: item.id, - data: item, - cacheHint: { - lastModified: new Date(item.lastModified), - tags: [`product-${item.id}`, `category-${item.category}`], - }, - }; - }, - }; -} -``` - -You can then use these hints in your pages: - -```astro ---- -export const prerender = false; // Not needed in 'server' mode - -import { getLiveEntry } from 'astro:content'; - -const { entry, error, cacheHint } = await getLiveEntry('products', Astro.params.id); - -if (error) { - return Astro.redirect('/404'); -} - -// Apply cache hints to response headers -if (cacheHint?.tags) { - Astro.response.headers.set('Cache-Tag', cacheHint.tags.join(',')); -} -if (cacheHint?.lastModified) { - Astro.response.headers.set('Last-Modified', cacheHint.lastModified.toUTCString()); -} ---- - -

    {entry.data.name}

    -

    {entry.data.description}

    -``` - -:::note -Cache hints only provide values that can be used in other parts of your project and do not automatically cause the response to be cached by Astro. You can use them to create your own caching strategy, such as setting HTTP headers or using a CDN. -::: - -## Live collection limitations - -Live content collections have some limitations compared to build-time collections: - -- **No MDX support**: MDX cannot be rendered at runtime -- **No image optimization**: Images cannot be processed at runtime -- **Performance considerations**: Data is fetched on each request (unless cached) -- **No data store persistence**: Data is not saved to the content layer data store - -## Differences from build-time collections - -Live collections use a different API than current preloaded content collections. Key differences include: - -1. **Execution time**: Run at request time instead of build time -2. **Configuration file**: Use `src/live.config.ts` instead of `src/content.config.ts` -3. **Collection definition**: Use `defineLiveCollection()` instead of `defineCollection()` -4. **Loader API**: Implement `loadCollection` and `loadEntry` methods instead of the `load` method -5. **Data return**: Return data directly instead of storing in the data store -6. **User-facing functions**: Use `getLiveCollection`/`getLiveEntry` instead of `getCollection`/`getEntry` - -For a complete overview and to give feedback on this experimental API, see the [Live Content collections RFC](https://github.com/withastro/roadmap/blob/feat/live-loaders/proposals/0055-live-content-loaders.md). diff --git a/src/content/docs/en/reference/experimental-flags/preserve-scripts-order.mdx b/src/content/docs/en/reference/experimental-flags/preserve-scripts-order.mdx deleted file mode 100644 index 429fb416c60d5..0000000000000 --- a/src/content/docs/en/reference/experimental-flags/preserve-scripts-order.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Experimental preserve scripts order -sidebar: - label: Preserve scripts order -i18nReady: true ---- - -import Since from '~/components/Since.astro' - -

    - - **Type:** `boolean`
    - **Default:** `false`
    - -

    - -Renders multiple ` - - - -``` - -After compiling, Astro's default behavior will create an inline style where `yellow` appears first, and then `red`. This means the `red` background is applied. Similarly with the two scripts, the word `world!` is logged first, and then `hello` second: - -```css -body {background:#ff0} body {background:red} -``` - -```js -console.log("world!") -console.log("hello") -``` - -When `experimental.preserveScriptOrder` is set to `true`, the rendering order of ` - - - -``` - -Después de la compilación, el comportamiento predeterminado de Astro creará un estilo en línea donde `yellow` aparece primero, y luego `red`. Esto significa que se aplica el fondo `red`. De manera similar con los dos scripts, la palabra `world!` se registra primero, y luego `hello` en segundo lugar: - -```css -body {background:#ff0} body {background:red} -``` - -```js -console.log("world!") -console.log("hello") -``` - -Cuando `experimental.preserveScriptOrder` se establece en `true`, el orden de renderizado de las etiquetas ` - - - -``` - -Après la compilation, le comportement par défaut d'Astro crée un style en ligne où `yellow` apparaît en premier, puis `red`. Cela signifie que l'arrière-plan `red` est appliqué. De même, avec les deux scripts, le mot `world!` est affiché en premier, suivi de `hello` : - -```css -body {background:#ff0} body {background:red} -``` - -```js -console.log("world!") -console.log("hello") -``` - -Lorsque `experimental.preserveScriptOrder` est définie sur `true`, l'ordre de restitution des balises ` - - - -``` - -컴파일 후 Astro의 기본 동작은 `yellow`가 먼저 나타나고 `red`가 다음에 나타나는 인라인 스타일을 생성합니다. 이는 `red` 배경이 적용됨을 의미합니다. 두 스크립트의 경우 `world!`가 먼저 기록되고 `hello`가 다음에 기록됩니다. - -```css -body {background:#ff0} body {background:red} -``` - -```js -console.log("world!") -console.log("hello") -``` - -`experimental.preserveScriptOrder`가 `true`로 설정되면 ` - - - -``` - -在编译后,Astro 的默认行为会创建一个内联样式,其中 `yellow` 首先出现,然后是 `red`。这意味着最终应用的是 `red` 背景。类似地,对于两个脚本,会先打印 `world!`,然后是 `hello`: - -```css -body {background:#ff0} body {background:red} -``` - -```js -console.log("world!") -console.log("hello") -``` - -当 `experimental.preserveScriptOrder` 设置为 `true` 时,`