From 6a682118dd62d06e8ae3d1a47886d7b466ed31c6 Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Wed, 28 May 2025 10:41:26 +0100
Subject: [PATCH 01/11] Add docs for experimental live collections
---
astro.sidebar.ts | 1 +
.../live-content-collections.mdx | 593 ++++++++++++++++++
2 files changed, 594 insertions(+)
create mode 100644 src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
diff --git a/astro.sidebar.ts b/astro.sidebar.ts
index d494e32dc76e9..5b91b9624e05a 100644
--- a/astro.sidebar.ts
+++ b/astro.sidebar.ts
@@ -142,6 +142,7 @@ export const sidebar = [
'reference/experimental-flags/content-intellisense',
'reference/experimental-flags/preserve-scripts-order',
'reference/experimental-flags/heading-id-compat',
+ 'reference/experimental-flags/live-content-collections',
],
}),
'reference/legacy-flags',
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
new file mode 100644
index 0000000000000..94c5995e3e694
--- /dev/null
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -0,0 +1,593 @@
+---
+title: Experimental live content loaders
+sidebar:
+ label: Live content loaders
+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, 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:
+
+```ts title="src/live.config.ts"
+import { defineCollection } from 'astro:content';
+import { storeLoader } from '@mystore/astro-loader';
+
+const products = defineCollection({
+ type: 'live',
+ 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
+---
+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 loaders
+
+Live content loaders are designed for data that changes frequently and needs to be up-to-date when a page is requested. Consider using them when:
+
+- **Your data updates frequently** (e.g., 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
+- **You need real-time information** (e.g., user-specific data, current stock levels)
+- **You want to avoid frequent rebuilds** for content that changes often
+
+In contrast, use regular content loaders 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
+
+## Using loaders
+
+You can define your own loaders for your site, 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 { defineCollection } from 'astro:content';
+import { cmsLoader } from '@example/cms-astro-loader';
+import { productLoader } from '@example/store-astro-loader';
+
+const articles = defineCollection({
+ type: 'live',
+ loader: cmsLoader({
+ apiKey: process.env.CMS_API_KEY,
+ contentType: 'article',
+ }),
+});
+
+const products = defineCollection({
+ type: 'live',
+ loader: productLoader({
+ apiKey: process.env.STORE_API_KEY,
+ }),
+});
+
+export const collections = { articles, authors };
+```
+
+You can then get content from both loaders with a unified API:
+
+```astro
+---
+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);
+---
+```
+
+## 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';
+
+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}`),
+ };
+ }
+ },
+ };
+}
+```
+
+### 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)
+- An `AstroError` for schema validation failures or uncaught errors thrown in the loader
+
+```astro
+---
+import { getLiveEntry, getLiveCollection } from 'astro:content';
+
+// Basic error handling
+const { entries, error } = await getLiveCollection('products');
+if (error) {
+ // Log for debugging
+ console.error(`Failed to load products: ${error.message}`);
+
+ // You can throw the error to use Astro's error handling
+ throw error;
+
+ // Or handle it yourself
+ return Astro.redirect('/500');
+}
+
+// Handle not found
+const { entry, error: entryError } = await getLiveEntry('products', id);
+if (entryError) {
+ return Astro.redirect('/404');
+}
+
+// Fallback patterns
+const { entries: liveProducts } = await getLiveCollection('products');
+const products = liveProducts || [];
+---
+
+{
+ error ? (
+
+
Unable to load products. Please try again later.
+
+ ) : (
+
+ {entries.map((entry) => (
+ -
+ {entry.data.name} - {entry.data.price}
+
+ ))}
+
+ )
+}
+```
+
+Loaders shoould 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 an `AstroError`.
+
+### 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 users to use [the `render()` function and `` component](/en/guides/content-collections/#rendering-body-content) to render the content directly in their pages.
+
+```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}`),
+ };
+ }
+ },
+ // ...
+ };
+}
+```
+
+Users can then render the content in their pages like this:
+
+```astro "render(entry)" ""
+---
+import { getLiveEntry, render } from 'astro:content';
+const { entry, error } = await getLiveEntry('articles', Astro.params.id);
+if (error) {
+ return Astro.redirect('/404');
+}
+
+const { Content } = await render(entry);
+---
+
+{entry.data.title}
+
+```
+
+If the loader does not return a `rendered` property for an entry, the `` component will render nothing.
+
+### 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. 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 loaders, live loaders can be typed to ensure type safety in your data. Unlike regular content loaders, live loaders can be typed without using Zod if preferred, by passing 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
+---
+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 ensuring they are used correctly. If you include JSDoc comments in your filter types, 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
+
+Loaders must return an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) subclass when an error occurs. You can create custom error types 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
+---
+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.redirect('/500');
+}
+---
+```
+## Using Zod schemas
+
+You can use Zod schemas with live loaders to validate and transform data at runtime. When you define a schema, it takes precedence over the loader's types when you query the collection:
+
+```ts title="src/live.config.ts"
+import { z, defineCollection } from 'astro:content';
+import { apiLoader } from './loaders/api-loader';
+
+const products = defineCollection({
+ type: 'live',
+ 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
+---
+import { getLiveEntry } from 'astro:content';
+
+const { entry, error } = await getLiveEntry('products', '123');
+if (error) {
+ // This could be a network error, not found error, or Zod validation error
+ console.error(error.message);
+ return Astro.redirect('/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"
+export function myLoader(config): LiveLoader {
+ return {
+ name: 'cached-loader',
+ loadCollection: async ({ filter }) => {
+ // ... fetch data
+ return {
+ entries: data.map((item) => ({
+ id: item.id,
+ data: item,
+ })),
+ cacheHint: {
+ tags: ['products'],
+ maxAge: 300, // 5 minutes
+ },
+ };
+ },
+ loadEntry: async ({ filter }) => {
+ // ... fetch single item
+ return {
+ id: item.id,
+ data: item,
+ cacheHint: {
+ tags: [`product-${item.id}`, `category-${item.category}`],
+ maxAge: 3600, // 1 hour
+ },
+ };
+ },
+ };
+}
+```
+
+You can then use these hints in your pages:
+
+```astro
+---
+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) {
+ Astro.response.headers.set('Cache-Tag', cacheHint.tags.join(','));
+ Astro.response.headers.set('Cache-Control', `s-maxage=${cacheHint.maxAge}`);
+}
+---
+{entry.data.name}
+{entry.data.description}
+```
+
+:::note
+Cache hints are advisory, and currently do not automatically cause the response to be cached by Astro. You can use them to inform your own caching strategy, such as setting HTTP headers or using a CDN.
+:::
+
+## Limitations
+
+Live content loaders have some limitations compared to build-time loaders:
+
+- **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
+
+## Migration from regular loaders
+
+Live loaders use a different API than regular content loaders. Key differences:
+
+1. **File location**: Use `src/live.config.ts` instead of `src/content.config.ts`
+2. **Collection type**: Set `type: "live"` in `defineCollection`
+3. **Loader API**: Implement `loadCollection` and `loadEntry` methods instead of the `load` method
+4. **Data return**: Return data directly instead of storing in the data store
+5. **Execution time**: Runs at request time instead of build time
+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 Loaders RFC](https://github.com/withastro/roadmap/blob/feat/live-loaders/proposals/0055-live-content-loaders.md).
From 9eeafc4774a25f9b6fd6bdae67cb6a7b8679fa74 Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Wed, 28 May 2025 10:57:31 +0100
Subject: [PATCH 02/11] Rename!
---
.../reference/experimental-flags/live-content-collections.mdx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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
index 94c5995e3e694..5c820817ece71 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -1,7 +1,7 @@
---
-title: Experimental live content loaders
+title: Experimental live content collections
sidebar:
- label: Live content loaders
+ label: Live content collections
i18nReady: true
---
From 6e18de46b0647db907094881fff7e1a372181700 Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Wed, 28 May 2025 11:09:32 +0100
Subject: [PATCH 03/11] More renaming
---
.../live-content-collections.mdx | 52 ++++++++++---------
1 file changed, 28 insertions(+), 24 deletions(-)
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
index 5c820817ece71..4a28d7637286b 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -75,9 +75,9 @@ const { entry: productBySlug } = await getLiveEntry('products', { slug: Astro.pa
---
```
-## When to use live content loaders
+## When to use live content collections
-Live content loaders are designed for data that changes frequently and needs to be up-to-date when a page is requested. Consider using them when:
+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:
- **Your data updates frequently** (e.g., product inventory, prices, availability)
- **You need to pass dynamic filters** to your data source based on user input or request parameters
@@ -85,7 +85,7 @@ Live content loaders are designed for data that changes frequently and needs to
- **You need real-time information** (e.g., user-specific data, current stock levels)
- **You want to avoid frequent rebuilds** for content that changes often
-In contrast, use regular content loaders when:
+In contrast, use regular 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)
@@ -93,9 +93,9 @@ In contrast, use regular content loaders when:
- **You need to process MDX** or perform image optimization
- **Your data can be fetched once and reused** across multiple builds
-## Using loaders
+## Using live collections
-You can define your own loaders for your site, or you can use community loaders distributed as npm packages. Here's how you could use example CMS and e-commerce loaders:
+You can define your own live loaders for your site, 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 { defineCollection } from 'astro:content';
@@ -323,7 +323,7 @@ Loaders can be defined in your site or as a separate npm package. If you want to
## Type safety
-Like regular content loaders, live loaders can be typed to ensure type safety in your data. Unlike regular content loaders, live loaders can be typed without using Zod if preferred, by passing generic types to the `LiveLoader` interface.
+Like regular content collections, live collections can be typed to ensure type safety in your data. Unlike regular content collections, live loaders can define types for their collections without using Zod if preferred, by passing 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
@@ -461,9 +461,10 @@ if (error) {
}
---
```
+
## Using Zod schemas
-You can use Zod schemas with live loaders to validate and transform data at runtime. When you define a schema, it takes precedence over the loader's types when you query the collection:
+You can use Zod schemas with live collections to validate and transform data at runtime. When you define a schema, it takes precedence over the loader's types when you query the collection:
```ts title="src/live.config.ts"
import { z, defineCollection } from 'astro:content';
@@ -472,19 +473,21 @@ import { apiLoader } from './loaders/api-loader';
const products = defineCollection({
type: 'live',
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)}`,
- })),
+ 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 };
@@ -562,6 +565,7 @@ if (cacheHint) {
Astro.response.headers.set('Cache-Control', `s-maxage=${cacheHint.maxAge}`);
}
---
+
{entry.data.name}
{entry.data.description}
```
@@ -572,16 +576,16 @@ Cache hints are advisory, and currently do not automatically cause the response
## Limitations
-Live content loaders have some limitations compared to build-time loaders:
+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
-## Migration from regular loaders
+## Migration from regular collections
-Live loaders use a different API than regular content loaders. Key differences:
+Live collections use a different API than regular content collections. Key differences include:
1. **File location**: Use `src/live.config.ts` instead of `src/content.config.ts`
2. **Collection type**: Set `type: "live"` in `defineCollection`
@@ -590,4 +594,4 @@ Live loaders use a different API than regular content loaders. Key differences:
5. **Execution time**: Runs at request time instead of build time
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 Loaders RFC](https://github.com/withastro/roadmap/blob/feat/live-loaders/proposals/0055-live-content-loaders.md).
+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).
From b9b29d972c03c24e6496807891d810aa4440eb02 Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Wed, 11 Jun 2025 10:13:50 +0100
Subject: [PATCH 04/11] Apply suggestions from code review
Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
---
.../experimental-flags/live-content-collections.mdx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
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
index 4a28d7637286b..e49de5f41b481 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -319,11 +319,13 @@ If the loader does not return a `rendered` property for an entry, the `
Date: Wed, 11 Jun 2025 10:37:33 +0100
Subject: [PATCH 05/11] Changes from review
---
.../live-content-collections.mdx | 134 +++++++++---------
1 file changed, 68 insertions(+), 66 deletions(-)
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
index e49de5f41b481..40586962366a0 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -12,7 +12,7 @@ import Since from '~/components/Since.astro';
**Type:** `boolean`
**Default:** `false`
-
+
Enables support for live content collections in your project.
@@ -137,6 +137,36 @@ 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)"
+---
+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.
@@ -206,62 +236,6 @@ export function articleLoader(config: { apiKey: string }): LiveLoader {
}
```
-### 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)
-- An `AstroError` for schema validation failures or uncaught errors thrown in the loader
-
-```astro
----
-import { getLiveEntry, getLiveCollection } from 'astro:content';
-
-// Basic error handling
-const { entries, error } = await getLiveCollection('products');
-if (error) {
- // Log for debugging
- console.error(`Failed to load products: ${error.message}`);
-
- // You can throw the error to use Astro's error handling
- throw error;
-
- // Or handle it yourself
- return Astro.redirect('/500');
-}
-
-// Handle not found
-const { entry, error: entryError } = await getLiveEntry('products', id);
-if (entryError) {
- return Astro.redirect('/404');
-}
-
-// Fallback patterns
-const { entries: liveProducts } = await getLiveCollection('products');
-const products = liveProducts || [];
----
-
-{
- error ? (
-
-
Unable to load products. Please try again later.
-
- ) : (
-
- {entries.map((entry) => (
- -
- {entry.data.name} - {entry.data.price}
-
- ))}
-
- )
-}
-```
-
-Loaders shoould 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 an `AstroError`.
-
### 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 users to use [the `render()` function and `` component](/en/guides/content-collections/#rendering-body-content) to render the content directly in their pages.
@@ -317,6 +291,34 @@ const { Content } = await render(entry);
If the loader does not return a `rendered` property for an entry, the `` component will render nothing.
+### 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`.
+
+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" {6-8}
+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).
@@ -325,7 +327,7 @@ The loader should export a function that returns the `LiveLoader` object, allowi
## 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:
+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
@@ -585,15 +587,15 @@ Live content collections have some limitations compared to build-time collection
- **Performance considerations**: Data is fetched on each request (unless cached)
- **No data store persistence**: Data is not saved to the content layer data store
-## Migration from regular collections
+## Difference from built-time, preloaded collections
-Live collections use a different API than regular content collections. Key differences include:
+Live collections use a different API than current preloaded content collections. Key differences include:
-1. **File location**: Use `src/live.config.ts` instead of `src/content.config.ts`
-2. **Collection type**: Set `type: "live"` in `defineCollection`
-3. **Loader API**: Implement `loadCollection` and `loadEntry` methods instead of the `load` method
-4. **Data return**: Return data directly instead of storing in the data store
-5. **Execution time**: Runs at request time instead of build time
-6. **User-facing functions**: Use `getLiveCollection`/`getLiveEntry` instead of `getCollection`/`getEntry`
+1. **Execution time**: Runs at request time instead of build time
+1. **Configuration file**: Use `src/live.config.ts` instead of `src/content.config.ts`
+1. **Collection type**: Set `type: "live"` in `defineCollection`
+1. **Loader API**: Implement `loadCollection` and `loadEntry` methods instead of the `load` method
+1. **Data return**: Return data directly instead of storing in the data store
+1. **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).
From a80bb280fed326e47f8ef53a2bc1e8edfb11a513 Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Wed, 11 Jun 2025 10:42:14 +0100
Subject: [PATCH 06/11] Update
---
.../reference/experimental-flags/live-content-collections.mdx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
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
index 40586962366a0..fdd2c6f49b88b 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -93,6 +93,8 @@ In contrast, use regular content collections when:
- **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](#limitations) and [key differences from build-time collections](#difference-from-build-time-collections) for more details on choosing between live and preloaded collections.
+
## Using live collections
You can define your own live loaders for your site, or you can use community loaders distributed as npm packages. Here's how you could use example CMS and e-commerce loaders:
@@ -587,7 +589,7 @@ Live content collections have some limitations compared to build-time collection
- **Performance considerations**: Data is fetched on each request (unless cached)
- **No data store persistence**: Data is not saved to the content layer data store
-## Difference from built-time, preloaded collections
+## Difference from build-time collections
Live collections use a different API than current preloaded content collections. Key differences include:
From 451d0b30c9275ebb3c1e8f2a578c9c49c6a5e08d Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Thu, 12 Jun 2025 09:10:22 +0100
Subject: [PATCH 07/11] Apply suggestions from code review
Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
---
.../live-content-collections.mdx | 42 ++++++++++---------
1 file changed, 22 insertions(+), 20 deletions(-)
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
index fdd2c6f49b88b..a990ca2c75c1c 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -31,7 +31,7 @@ To enable the feature, add the `experimental.liveContentCollections` flag to you
}
```
-Then create a new `src/live.config.ts` file (alongside your `src/content.config.ts` if you have one) to define your live collections:
+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):
```ts title="src/live.config.ts"
import { defineCollection } from 'astro:content';
@@ -48,7 +48,7 @@ const products = defineCollection({
export const collections = { products };
```
-You can then use the dedicated `getLiveCollection` and `getLiveEntry` functions to access your live data:
+You can then use the dedicated `getLiveCollection()` and `getLiveEntry()` functions to access your live data:
```astro
---
@@ -79,13 +79,13 @@ const { entry: productBySlug } = await getLiveEntry('products', { slug: Astro.pa
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:
-- **Your data updates frequently** (e.g., product inventory, prices, availability)
+- **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
-- **You need real-time information** (e.g., user-specific data, current stock levels)
-- **You want to avoid frequent rebuilds** for content that changes often
-In contrast, use regular content collections when:
+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)
@@ -93,11 +93,11 @@ In contrast, use regular content collections when:
- **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](#limitations) and [key differences from build-time collections](#difference-from-build-time-collections) for more details on choosing between live and preloaded collections.
+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 define your own live loaders for your site, or you can use community loaders distributed as npm packages. Here's how you could use example CMS and e-commerce loaders:
+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 { defineCollection } from 'astro:content';
@@ -143,7 +143,7 @@ const { entry: product } = await getLiveEntry('products', Astro.params.slug);
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:
+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
@@ -171,7 +171,9 @@ if (error) {
## 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.
+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:
@@ -240,7 +242,7 @@ export function articleLoader(config: { apiKey: string }): LiveLoader {
### 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 users to use [the `render()` function and `` component](/en/guides/content-collections/#rendering-body-content) to render the content directly in their pages.
+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.
```ts title="myloader.ts" {16-19}
// ...
@@ -274,7 +276,7 @@ export function articleLoader(config: { apiKey: string }): LiveLoader {
}
```
-Users can then render the content in their pages like this:
+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 redirect to a 404 page when content cannot be displayed:
```astro "render(entry)" ""
---
@@ -295,7 +297,7 @@ If the loader does not return a `rendered` property for an entry, the ` {
}
```
-When you use `getLiveCollection` or `getLiveEntry`, TypeScript will infer the types based on the loader's definition:
+When you use `getLiveCollection()` or `getLiveEntry()`, TypeScript will infer the types based on the loader's definition:
```astro
---
@@ -358,7 +360,7 @@ 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 ensuring they are used correctly. If you include JSDoc comments in your filter types, user will see these in their IDE as hints when using the loader.
+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 ensuring they are used correctly. If you include JSDoc comments in your filter types, 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';
@@ -424,7 +426,7 @@ export function productLoader(config: {
### Custom error types
-Loaders must return an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) subclass when an error occurs. You can create custom error types and pass them as a generic to get proper typing:
+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 {
@@ -451,7 +453,7 @@ export function myLoader(config): LiveLoader
Date: Fri, 13 Jun 2025 10:55:12 +0100
Subject: [PATCH 08/11] Rename function
---
.../live-content-collections.mdx | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
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
index a990ca2c75c1c..bbf5aee32ca4b 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -31,13 +31,13 @@ To enable the feature, add the `experimental.liveContentCollections` flag to you
}
```
-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):
+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 { defineCollection } from 'astro:content';
+import { defineLiveCollection } from 'astro:content';
import { storeLoader } from '@mystore/astro-loader';
-const products = defineCollection({
+const products = defineLiveCollection({
type: 'live',
loader: storeLoader({
apiKey: process.env.STORE_API_KEY,
@@ -100,11 +100,11 @@ See the [limitations of experimental live collections](#live-collection-limitati
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 { defineCollection } from 'astro:content';
+import { defineLiveCollection } from 'astro:content';
import { cmsLoader } from '@example/cms-astro-loader';
import { productLoader } from '@example/store-astro-loader';
-const articles = defineCollection({
+const articles = defineLiveCollection({
type: 'live',
loader: cmsLoader({
apiKey: process.env.CMS_API_KEY,
@@ -112,7 +112,7 @@ const articles = defineCollection({
}),
});
-const products = defineCollection({
+const products = defineLiveCollection({
type: 'live',
loader: productLoader({
apiKey: process.env.STORE_API_KEY,
@@ -475,10 +475,10 @@ if (error) {
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 { z, defineCollection } from 'astro:content';
+import { z, defineLiveCollection } from 'astro:content';
import { apiLoader } from './loaders/api-loader';
-const products = defineCollection({
+const products = defineLiveCollection({
type: 'live',
loader: apiLoader({ endpoint: process.env.API_URL }),
schema: z
@@ -597,7 +597,8 @@ Live collections use a different API than current preloaded content collections.
1. **Execution time**: Runs at request time instead of build time
1. **Configuration file**: Use `src/live.config.ts` instead of `src/content.config.ts`
-1. **Collection type**: Set `type: "live"` in `defineCollection`
+1. **Collection definition**: Use `defineLiveCollection()` instead of `defineCollection()`
+1. **Collection type**: Set `type: "live"` in collection definition
1. **Loader API**: Implement `loadCollection` and `loadEntry` methods instead of the `load` method
1. **Data return**: Return data directly instead of storing in the data store
1. **User-facing functions**: Use `getLiveCollection`/`getLiveEntry` instead of `getCollection`/`getEntry`
From dfecda94301a6474fd655e3a73fafc6282e1773a Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Wed, 18 Jun 2025 09:39:14 +0100
Subject: [PATCH 09/11] Apply suggestions from code review
Co-authored-by: Yan <61414485+yanthomasdev@users.noreply.github.com>
---
.../reference/experimental-flags/live-content-collections.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
index bbf5aee32ca4b..8a315f2adeb23 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -360,7 +360,7 @@ 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 ensuring they are used correctly. If you include JSDoc comments in your filter types, user will see these in their IDE as hints when using the loader.
+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';
From c57ee62ab1a3a57bdcb713fe316e8e2fc4c61fb4 Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Wed, 18 Jun 2025 09:39:59 +0100
Subject: [PATCH 10/11] Update
src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
Co-authored-by: Yan <61414485+yanthomasdev@users.noreply.github.com>
---
.../reference/experimental-flags/live-content-collections.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
index 8a315f2adeb23..e6f15116b6fee 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -17,7 +17,7 @@ import Since from '~/components/Since.astro';
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.
+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
From 47fe44fe43c5687e21942ecc4ff59765b68df52d Mon Sep 17 00:00:00 2001
From: Matt Kane
Date: Thu, 19 Jun 2025 07:19:41 +0100
Subject: [PATCH 11/11] Revisions
---
.../live-content-collections.mdx | 29 +++++++++++--------
1 file changed, 17 insertions(+), 12 deletions(-)
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
index e6f15116b6fee..fb638c472a08b 100644
--- a/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
+++ b/src/content/docs/en/reference/experimental-flags/live-content-collections.mdx
@@ -179,7 +179,7 @@ Here's a basic example:
```ts title="myloader.ts"
import type { LiveLoader } from 'astro/loaders';
-import { fetchFromCMS } from './cms-client';
+import { fetchFromCMS } from './cms-client.js';
interface Article {
id: string;
@@ -242,7 +242,8 @@ export function articleLoader(config: { apiKey: string }): LiveLoader {
### 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.
+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}
// ...
@@ -276,14 +277,14 @@ export function articleLoader(config: { apiKey: string }): LiveLoader {
}
```
-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 redirect to a 404 page when content cannot be displayed:
+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)" ""
---
import { getLiveEntry, render } from 'astro:content';
const { entry, error } = await getLiveEntry('articles', Astro.params.id);
if (error) {
- return Astro.redirect('/404');
+ return Astro.rewrite('/404');
}
const { Content } = await render(entry);
@@ -293,8 +294,6 @@ const { Content } = await render(entry);
```
-If the loader does not return a `rendered` property for an entry, the `` component will render nothing.
-
### 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.
@@ -465,7 +464,7 @@ if (error) {
} else {
console.error(`Unexpected error: ${error.message}`);
}
- return Astro.redirect('/500');
+ return Astro.rewrite('/500');
}
---
```
@@ -505,17 +504,18 @@ When using Zod schemas, validation errors are automatically caught and returned
```astro
---
-import { getLiveEntry } from 'astro:content';
+import { getLiveEntry, LiveCollectionValidationError } from 'astro:content';
const { entry, error } = await getLiveEntry('products', '123');
-if (error) {
- // This could be a network error, not found error, or Zod validation error
+
+// You can handle validation errors specifically
+if (LiveCollectionValidationError.is(error)) {
console.error(error.message);
- return Astro.redirect('/500');
+ 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"
+console.log(entry?.data.displayPrice); // e.g., "$29.99"
---
```
@@ -533,6 +533,11 @@ export function myLoader(config): LiveLoader {
entries: data.map((item) => ({
id: item.id,
data: item,
+ // You can optionally provide cache hints for each entry
+ // These are merged with the collection's cache hint
+ cacheHint: {
+ tags: [`product-${item.id}`, `category-${item.category}`],
+ },
})),
cacheHint: {
tags: ['products'],