From 78ee94c9d436656c6e1c3d8afac855e970fca247 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 16 Oct 2025 12:59:13 +0100 Subject: [PATCH 1/5] Stabilise live collections --- .changeset/stale-ads-see.md | 72 +++++++++++++++++++ packages/astro/src/content/utils.ts | 30 ++++---- .../astro/src/core/config/schemas/base.ts | 5 -- packages/astro/src/types/public/config.ts | 11 --- .../fixtures/live-loaders/astro.config.mjs | 5 +- packages/integrations/sitemap/src/index.ts | 2 +- 6 files changed, 87 insertions(+), 38 deletions(-) create mode 100644 .changeset/stale-ads-see.md diff --git a/.changeset/stale-ads-see.md b/.changeset/stale-ads-see.md new file mode 100644 index 000000000000..8b9be8b1aee9 --- /dev/null +++ b/.changeset/stale-ads-see.md @@ -0,0 +1,72 @@ +--- +'astro': minor +--- + +Adds support for live content collections + +Live content collections are a new type of [content collection](https://docs.astro.build/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 collections vs build-time collections + +In Astro 5.0, the content layer API added support for adding diverse content sources to content collections. You can create loaders that fetch data from any source at build time, and then access it inside a page via `getEntry()` and `getCollection()`. The data is cached between builds, giving fast access and updates. + +However there is no method for updating the data store between builds, meaning any updates to the data need a full site deploy, even if the pages are rendered on-demand. This means that content collections are not suitable for pages that update frequently. Instead, today these pages tend to access the APIs directly in the frontmatter. This works, but leads to a lot of boilerplate, and means users don't benefit from the simple, unified API that content loaders offer. In most cases users tend to individually create loader libraries that they share between pages. + +Live content collections solve this problem by allowing you to create loaders that fetch data at runtime, rather than build time. This means that the data is always up-to-date, without needing to rebuild the site. + +#### How to use + +To use live collections, 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 content loader 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 `getLiveCollection()` and `getLiveEntry()` functions to access your live data: + +```astro +--- +import { getLiveCollection, getLiveEntry, render } 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 }); +const { Content } = await render(product); +--- +

{product.data.title}

+ +``` + +#### Upgrading from experimental live collections + +If you were using the experimental feature, you should remove the `experimental.liveContentCollections` flag from your `astro.config.*` file: + +```diff + export default defineConfig({ + // ... +- experimental: { +- liveContentCollections: true, +- }, + }); +``` diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index bedd4c93c5a4..0e04a09b236b 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -492,19 +492,17 @@ async function loadContentConfig({ console.error( `${green('[content]')} There was a problem with your content config:\n\n${message}\n`, ); - if (settings.config.experimental.liveContentCollections) { - const liveCollections = Object.entries(unparsedConfig.collections ?? {}).filter( - ([, collection]: [string, any]) => collection?.type === LIVE_CONTENT_TYPE, - ); - if (liveCollections.length > 0) { - throw new AstroError({ - ...AstroErrorData.LiveContentConfigError, - message: AstroErrorData.LiveContentConfigError.message( - 'Live collections must be defined in a `src/live.config.ts` file.', - path.relative(fileURLToPath(settings.config.root), configPathname), - ), - }); - } + const liveCollections = Object.entries(unparsedConfig.collections ?? {}).filter( + ([, collection]: [string, any]) => collection?.type === LIVE_CONTENT_TYPE, + ); + if (liveCollections.length > 0) { + throw new AstroError({ + ...AstroErrorData.LiveContentConfigError, + message: AstroErrorData.LiveContentConfigError.message( + 'Live collections must be defined in a `src/live.config.ts` file.', + path.relative(fileURLToPath(settings.config.root), configPathname), + ), + }); } return undefined; } @@ -592,7 +590,7 @@ export type ContentPaths = { }; export function getContentPaths( - { srcDir, root, experimental }: Pick, + { srcDir, root }: Pick, fs: typeof fsMod = fsMod, ): ContentPaths { const configStats = searchConfig(fs, srcDir); @@ -608,9 +606,7 @@ export function getContentPaths( } } - const liveConfigStats = experimental?.liveContentCollections - ? searchLiveConfig(fs, srcDir) - : { exists: false, url: new URL('./', srcDir) }; + const liveConfigStats = searchLiveConfig(fs, srcDir); const pkgBase = new URL('../../', import.meta.url); return { root: new URL('./', root), diff --git a/packages/astro/src/core/config/schemas/base.ts b/packages/astro/src/core/config/schemas/base.ts index 33f1518cdede..78a3a5822ed6 100644 --- a/packages/astro/src/core/config/schemas/base.ts +++ b/packages/astro/src/core/config/schemas/base.ts @@ -98,7 +98,6 @@ export const ASTRO_CONFIG_DEFAULTS = { clientPrerender: false, contentIntellisense: false, headingIdCompat: false, - liveContentCollections: false, csp: false, chromeDevtoolsWorkspace: false, failOnPrerenderConflict: false, @@ -478,10 +477,6 @@ export const AstroConfigSchema = z.object({ .optional() .default(ASTRO_CONFIG_DEFAULTS.experimental.headingIdCompat), fonts: z.array(z.union([localFontFamilySchema, remoteFontFamilySchema])).optional(), - liveContentCollections: z - .boolean() - .optional() - .default(ASTRO_CONFIG_DEFAULTS.experimental.liveContentCollections), csp: z .union([ z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.csp), diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 9d3ec11395b2..75ac93e32c62 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -2400,17 +2400,6 @@ export interface AstroUserConfig< directives?: CspDirective[]; }; - /** - * @name experimental.liveContentCollections - * @type {boolean} - * @default `false` - * @version 5.10 - * @description - * Enables the use of live content collections. - * - */ - liveContentCollections?: boolean; - /** * @name experimental.chromeDevtoolsWorkspace * @type {boolean} diff --git a/packages/astro/test/fixtures/live-loaders/astro.config.mjs b/packages/astro/test/fixtures/live-loaders/astro.config.mjs index 44512231b35f..1e7cd8c24e52 100644 --- a/packages/astro/test/fixtures/live-loaders/astro.config.mjs +++ b/packages/astro/test/fixtures/live-loaders/astro.config.mjs @@ -7,8 +7,5 @@ import node from '@astrojs/node'; export default defineConfig({ adapter: node({ mode: 'standalone' - }), - experimental: { - liveContentCollections: true - } + }) }); diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts index c59c7f6eb24b..bb4c53e55c8d 100644 --- a/packages/integrations/sitemap/src/index.ts +++ b/packages/integrations/sitemap/src/index.ts @@ -76,7 +76,7 @@ const isStatusCodePage = (locales: string[]) => { }; }; const createPlugin = (options?: SitemapOptions): AstroIntegration => { - let _routes: Array + let _routes: Array; let config: AstroConfig; return { From 33fa76239ed2ca769ac3566a1a7d61a32346f488 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 16 Oct 2025 14:29:07 +0100 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Yan <61414485+yanthomasdev@users.noreply.github.com> Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/stale-ads-see.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.changeset/stale-ads-see.md b/.changeset/stale-ads-see.md index 8b9be8b1aee9..9e23fef44ed4 100644 --- a/.changeset/stale-ads-see.md +++ b/.changeset/stale-ads-see.md @@ -4,15 +4,15 @@ Adds support for live content collections -Live content collections are a new type of [content collection](https://docs.astro.build/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](https://docs.astro.build/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 collections vs build-time collections In Astro 5.0, the content layer API added support for adding diverse content sources to content collections. You can create loaders that fetch data from any source at build time, and then access it inside a page via `getEntry()` and `getCollection()`. The data is cached between builds, giving fast access and updates. -However there is no method for updating the data store between builds, meaning any updates to the data need a full site deploy, even if the pages are rendered on-demand. This means that content collections are not suitable for pages that update frequently. Instead, today these pages tend to access the APIs directly in the frontmatter. This works, but leads to a lot of boilerplate, and means users don't benefit from the simple, unified API that content loaders offer. In most cases users tend to individually create loader libraries that they share between pages. +However there was no method for updating the data store between builds, meaning any updates to the data need a full site deploy, even if the pages are rendered on-demand. This meant that content collections were not suitable for pages that update frequently. Instead, these pages tended to access the APIs directly in the frontmatter. This worked, but leads to a lot of boilerplate, and meant users don't benefit from the simple, unified API that content loaders offer. In most cases users tended to individually create loader libraries that they share between pages. -Live content collections solve this problem by allowing you to create loaders that fetch data at runtime, rather than build time. This means that the data is always up-to-date, without needing to rebuild the site. +Live content collections ([introduced experimentally in Astro 5.10](https://astro.build/blog/live-content-collections-deep-dive/)) solve this problem by allowing you to create loaders that fetch data at runtime, rather than build time. This means that the data is always up-to-date, without needing to rebuild the site. #### How to use @@ -60,7 +60,7 @@ const { Content } = await render(product); #### Upgrading from experimental live collections -If you were using the experimental feature, you should remove the `experimental.liveContentCollections` flag from your `astro.config.*` file: +If you were using the experimental feature, you must remove the `experimental.liveContentCollections` flag from your `astro.config.*` file: ```diff export default defineConfig({ @@ -70,3 +70,5 @@ If you were using the experimental feature, you should remove the `experimental. - }, }); ``` + +No other changes to your project code are required as long as you have been keeping up with Astro 5.x patch releases which may have contained breaking changes to this experimental feature. If you experience problems with your live collections after upgrading to Astro v6 and removing this flag, please review the [Astro CHANGELOG from 5.10.2](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#5102) onwards for any potential updates you might have missed, or follow the [current v6 documentation for live collections](https://docs.astro.build/en/guides/content-collections/). From d3bc6abfff7ed4b4b3c1839043146cd56a025224 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 16 Oct 2025 19:41:59 +0100 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Yan <61414485+yanthomasdev@users.noreply.github.com> --- .changeset/stale-ads-see.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/stale-ads-see.md b/.changeset/stale-ads-see.md index 9e23fef44ed4..7387de5235cb 100644 --- a/.changeset/stale-ads-see.md +++ b/.changeset/stale-ads-see.md @@ -10,7 +10,7 @@ Live content collections are a new type of [content collection](https://docs.ast In Astro 5.0, the content layer API added support for adding diverse content sources to content collections. You can create loaders that fetch data from any source at build time, and then access it inside a page via `getEntry()` and `getCollection()`. The data is cached between builds, giving fast access and updates. -However there was no method for updating the data store between builds, meaning any updates to the data need a full site deploy, even if the pages are rendered on-demand. This meant that content collections were not suitable for pages that update frequently. Instead, these pages tended to access the APIs directly in the frontmatter. This worked, but leads to a lot of boilerplate, and meant users don't benefit from the simple, unified API that content loaders offer. In most cases users tended to individually create loader libraries that they share between pages. +However, there was no method for updating the data store between builds, meaning any updates to the data needed a full site deploy, even if the pages are rendered on demand. This meant that content collections were not suitable for pages that update frequently. Instead, these pages tended to access the APIs directly in the frontmatter. This worked, but it led to a lot of boilerplate, and meant users didn't benefit from the simple, unified API that content loaders offer. In most cases, users tended to individually create loader libraries shared between pages. Live content collections ([introduced experimentally in Astro 5.10](https://astro.build/blog/live-content-collections-deep-dive/)) solve this problem by allowing you to create loaders that fetch data at runtime, rather than build time. This means that the data is always up-to-date, without needing to rebuild the site. @@ -71,4 +71,4 @@ If you were using the experimental feature, you must remove the `experimental.li }); ``` -No other changes to your project code are required as long as you have been keeping up with Astro 5.x patch releases which may have contained breaking changes to this experimental feature. If you experience problems with your live collections after upgrading to Astro v6 and removing this flag, please review the [Astro CHANGELOG from 5.10.2](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#5102) onwards for any potential updates you might have missed, or follow the [current v6 documentation for live collections](https://docs.astro.build/en/guides/content-collections/). +No other changes to your project code are required as long as you have been keeping up with Astro 5.x patch releases, which may have contained breaking changes to this experimental feature. If you experience problems with your live collections after upgrading to Astro v6 and removing this flag, please review the [Astro CHANGELOG from 5.10.2](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#5102) onwards for any potential updates you might have missed, or follow the [current v6 documentation for live collections](https://docs.astro.build/en/guides/content-collections/). From 89cb2fa858ec82b2c38b7750a088c85e5a8f129b Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 16 Oct 2025 19:44:09 +0100 Subject: [PATCH 4/5] Clarify changes needed for Astro 6 upgrade Updated the note regarding changes required for Astro 6. --- .changeset/stale-ads-see.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/stale-ads-see.md b/.changeset/stale-ads-see.md index 7387de5235cb..5bd668ad5164 100644 --- a/.changeset/stale-ads-see.md +++ b/.changeset/stale-ads-see.md @@ -71,4 +71,4 @@ If you were using the experimental feature, you must remove the `experimental.li }); ``` -No other changes to your project code are required as long as you have been keeping up with Astro 5.x patch releases, which may have contained breaking changes to this experimental feature. If you experience problems with your live collections after upgrading to Astro v6 and removing this flag, please review the [Astro CHANGELOG from 5.10.2](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#5102) onwards for any potential updates you might have missed, or follow the [current v6 documentation for live collections](https://docs.astro.build/en/guides/content-collections/). +No other changes to your project code are required as long as you have been keeping up with Astro 5.x patch releases, which contained breaking changes to this experimental feature. If you experience problems with your live collections after upgrading to Astro v6 and removing this flag, please review the [Astro CHANGELOG from 5.10.2](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#5102) onwards for any potential updates you might have missed, or follow the [current v6 documentation for live collections](https://docs.astro.build/en/guides/content-collections/). From 617031836f97f449deed4d6e04021f7e4a75596d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 28 Oct 2025 09:31:03 +0000 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/stale-ads-see.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.changeset/stale-ads-see.md b/.changeset/stale-ads-see.md index 5bd668ad5164..b064b90d0376 100644 --- a/.changeset/stale-ads-see.md +++ b/.changeset/stale-ads-see.md @@ -32,7 +32,7 @@ const products = defineLiveCollection({ export const collections = { products }; ``` -You can then use the `getLiveCollection()` and `getLiveEntry()` functions to access your live data: +You can then use the `getLiveCollection()` and `getLiveEntry()` functions to access your live data, along with error handling (since anything can happen when requesting live data!): ```astro --- @@ -43,13 +43,16 @@ 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 }); const { Content } = await render(product);