Skip to content

Live content loaders #1151

Description

@ascorbic

Summary

Adds support for live data to content collections. Defines a new type of content loader that fetches data at runtime rather than build time, allowing users to get the data with a similar API.

Background & Motivation

In Astro 5, the content layer API added support for adding diverse content sources to content collections. Users 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 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.

This proposal introduces a new kind of loader that fetches data from an API at runtime, rather than build time. As with other content loaders, these loaders abstract the loading logic, meaning users don't need to understand the details of how data is loaded. These loaders can be distributed as node modules, or injected by integrations.

Goals

  • a new type of live content loader that is executed at runtime
  • integration with getEntry and getCollection, allowing developers to use a familiar, common API to fetch data
  • optional integration with route caching, allowing loaders to define cache tags, TTL etc associated with the data, which the user could decide to use
  • loader-specific query options

Non-goals

  • server-side caching of the data. Instead it would integrate with the route cache and HTTP caches to cache the full page response
  • updating the content layer data store. Live loaders return data directly and do not update the store
  • support for existing loaders. They will have a different API. Developers could in theory use shared logic, but the loader API will be different

Example

It may need to be a different content config file, depending on what compiler magic we can do.

// src/livecontent.config.ts
import { defineLiveCollection } from "astro:content";
// Possibly a different name, or maybe use the same `defineCollection`

import { storeLoader } from "@mystore/astro-loader";


const products = defineLiveCollection({
    loader: storeLoader({ field: "products", key: process.env.STORE_KEY })
})

export const collections = { products };

Define loaders in the site or in a shared library.

// storeloader.ts

import type { LiveLoader } from "astro/loaders";
import { loadStoreData } from "./lib/api.ts";

interface StoreCollectionFilter {
    price: {
        min?: number;
        max?: number;
    }
}

interface StoreEntryFilter {
    slug?: string;
}

// Generic allows typing of the `filter` param
export function storeLoader({ field, key }): LiveLoader<StoreEntryFilter, StoreCollectionFilter> {
    return {
        loadCollection: async ({ logger, filter }) => {
            // load from API 
            const entries = await loadStoreData({ field, key, filter });
            return entries.map((entry) => ({
                id: entry.id,
                data: entry,
                cache: {
                    tags: [field, `field:${entry.id}`]
                }
            }))
        },
        loadEntry: async ({ logger, id, filter }) => {
            const entry = await loadStoreData({ field, key, filter: filter ? filter : { id }});
            if (!entry) {
                return
            }
            return {
                id,
                data: entry,
                cache: {
                    tags: [field, `field:${entry.id}`]
                }
            }
        },
        schema: //...
    }
}

Then use the data in pages.

---
// src/pages/products/[slug].astro

import { getEntry } from "astro:content";

// The `filter` prop allows getting by fields other than id. The avilable options will depend on the API
// and will be typed in the loader definition
const entry = await getEntry('products', { slug: Astro.params.slug });

// Possible API for setting cache headers from entry metadata
Astro.cache.fromEntry(entry);

// This will work in prerendered pages, but is designed for on-demand
export const prerender = false;
---

<h1>{ entry.data.title }</h1>

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
Stage 2: Accepted Proposals, No RFC

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions