Custom Source
Load pages from anywhere.
A custom source lists your pages, and a plugin renders them.
Setup
List Pages
A source is an object with files. The path of a file sets its URL, and its data becomes page.data. When files is a function, the source is dynamic: by default, pages are read on the server and render on demand, so the site needs a server.
const data = { title: "Home", load: async () => "Hello", plainText: () => "Hello" };
export default createPress({
files: [{ type: "page", path: "index.mdx", data }],
}).plugins(acmePlugin());Render Pages
A plugin renders the pages through content hooks, like getPageBody. Return undefined for pages it doesn't handle.
AppShape and Plugin come from @fumapress/astro/config.
interface AcmePageData {
load: () => Promise<string>;
plainText: () => string;
}
function isAcmePage(data: object): data is AcmePageData {
return "load" in data && "plainText" in data;
}
export default function acmePlugin<C extends AppShape = AppShape>(): Plugin<C> {
return {
name: "acme",
async getPageBody(page) {
if (!isAcmePage(page.data)) return;
return { node: <div>{await page.data.load()}</div> };
},
getPageText(page) {
if (isAcmePage(page.data)) return page.data.plainText();
},
};
}To make your plugin work on every framework Fumapress runs on, see Publishing a Package.
Content Hooks
Only getPageBody is required. Add other hooks for the features you need:
| Hook | Needed for |
|---|---|
getPageToc | the table of contents |
getPageText | llms.txt, <page>.md, AI chat, MCP |
getPageStructuredData | full text search |
getPageCreatedAt | blog, RSS |
getPageLastModified | the page, sitemap, RSS |
Without structured data, search only matches the title of a page. To search its text, return the headings and the text under each:
getPageStructuredData(page) {
return {
headings: [{ id: "intro", content: "Intro" }],
// `heading` is the `id` of the heading above the text
contents: [{ heading: "intro", content: "The text under the heading." }],
};
},Refreshing Content
The source is read once, so new content doesn't appear on its own. Choose when to read it again:
createPress(source, {
alwaysRevalidate: true,
});Reading on every request works for low traffic. On a busier site, call the webhook when your content changes.
Last updated on