Skip to content

Override a default

Customize the Markdown versions, llms.txt indexes, and OG cards without giving up the shared routes.

AI-generated · awaiting reviewUpdated View as Markdown

The starter’s src/pages/[...slug]/index.md.ts and src/pages/[...slug]/index.mdx.ts routes serve the Markdown and source versions of every page. You can change what they serve at three levels:

  • Wrap the shared route to change the output for every page.
  • Replace the shared route with your own code.
  • Add a more specific route file to take over one collection.

The examples use the .md route. The .mdx route works the same way with markdownSourceRoute(). The llms.txt routes and the OG card route follow the same pattern; see Override an llms.txt index and Customize OG cards.

Wrap the shared route

Call the shared route’s GET, then change its response. This example adds a license line to every Markdown version:

src/pages/[...slug]/index.md.tsts
import type { APIRoute } from "astro";
import { markdownRoute } from "@cloudflare/nimbus-docs/agent-endpoints";

export const prerender = true;

const route = markdownRoute();
export const getStaticPaths = route.getStaticPaths;

export const GET: APIRoute = async (context) => {
  const response = await route.GET(context);
  if (!response.ok) return response;
  const body = await response.text();
  return new Response(`${body}\nLicensed under CC BY 4.0.\n`, {
    headers: response.headers,
  });
};

Replace the shared route

Write your own GET with the public helpers from @cloudflare/nimbus-docs/agent-endpoints. getMarkdownPayload() returns a page’s generated Markdown as body, and only the page content, without the frontmatter and index preamble, as content. Keep the shared route’s getStaticPaths: it lists every collection’s pages, skips pages that more specific routes own, and passes each page’s reference as a prop.

This example serves only the page content:

src/pages/[...slug]/index.md.tsts
import type { APIRoute } from "astro";
import {
  getMarkdownPayload,
  markdownRoute,
  type MarkdownEndpointReference,
} from "@cloudflare/nimbus-docs/agent-endpoints";

export const prerender = true;

export const { getStaticPaths } = markdownRoute();

export const GET: APIRoute = async ({ props, request }) => {
  const { reference } = props as { reference: MarkdownEndpointReference };
  const payload = await getMarkdownPayload({
    collection: reference.collection,
    surface: "markdown",
    reference,
    context: { request },
  });
  if (!payload) return new Response("Not found", { status: 404 });
  return new Response(payload.content, {
    headers: { "Content-Type": payload.mediaType },
  });
};

To list pages yourself, getMarkdownStaticPaths({ collection, surface }) returns one collection’s paths. A route built from it serves only the collections it lists.

Take over one collection

Astro gives a more specific route priority. Add a route file under a collection’s URL prefix, and it serves that collection’s Markdown versions. The shared route skips every URL the more specific route’s pattern matches.

markdownRoute() works in any route file and serves only the URLs its route matches. Wrap it to customize one collection:

src/pages/blog/[...slug]/index.md.tsts
import type { APIRoute } from "astro";
import { markdownRoute } from "@cloudflare/nimbus-docs/agent-endpoints";

export const prerender = true;

const route = markdownRoute();
export const getStaticPaths = route.getStaticPaths;

export const GET: APIRoute = async (context) => {
  const response = await route.GET(context);
  if (!response.ok) return response;
  const body = await response.text();
  return new Response(`${body}\nSubscribe at https://example.com/blog/rss.xml\n`, {
    headers: response.headers,
  });
};

Or write the route with the public helpers. The changelog recipe (nimbus-docs add changelog) adds src/pages/changelog/[...slug]/index.md.ts, which builds its own frontmatter with each entry’s date and tags:

src/pages/changelog/[...slug]/index.md.ts (abridged)ts
export const prerender = true;

export const getStaticPaths = async () =>
  getMarkdownStaticPaths({ collection: "changelog", surface: "markdown" });

export async function GET({ params, props, request }: SlugContext) {
  const payload = await getMarkdownPayload({
    collection: "changelog",
    surface: "markdown",
    slug: params.slug,
    reference: props.reference,
    context: { request },
  });
  if (!payload) return new Response("Not found", { status: 404 });
  // Build the frontmatter from the entry's data, then append payload.content.
}

A more specific route must generate every page its pattern matches. If it skips some, those pages have no Markdown version while llms.txt still links to them. Nimbus lists the missing paths and the route that owns them as a build warning. The build fails instead when Astro’s prerenderConflictBehavior is "error".

Override an llms.txt index

The starter’s llms.txt routes call llmsRoute(), llmsFullRoute(), and llmsSectionRoute(). Wrap the returned GET the same way. This example adds a support link to /llms.txt:

src/pages/llms.txt.tsts
import type { APIRoute } from "astro";
import { llmsRoute } from "@cloudflare/nimbus-docs/agent-endpoints";

export const prerender = true;

const route = llmsRoute();

export const GET: APIRoute = async (context) => {
  const response = await route.GET(context);
  if (!response.ok) return response;
  const body = await response.text();
  return new Response(`${body}\n## Support\n\n- [Contact support](https://example.com/support)\n`, {
    headers: response.headers,
  });
};

For section indexes, keep llmsSectionRoute()’s getStaticPaths, which lists every section, and wrap its GET. To build a body from scratch, call getLlmsPayload() with a reference such as { scope: "section", surface: "index", section: "writing" }; it returns null for an unknown index.

Customize OG cards

The starter’s src/pages/og/[...slug].ts passes getOgImagePages() to astro-og-canvas. The map has one entry per page, keyed so each card is written at that page’s ogImageUrl, and each value is the page’s IndexedEntry. Change the card’s look in src/pages/og/_og-card-config.ts, or per page in getImageOptions. This example labels changelog cards:

src/pages/og/[...slug].tsts
import { getOgImagePages } from "@cloudflare/nimbus-docs/runtime";
import { OGImageRoute } from "astro-og-canvas";
import { ogCardConfig } from "./_og-card-config";

export const prerender = true;

export const { getStaticPaths, GET } = await OGImageRoute({
  pages: await getOgImagePages(),
  getImageOptions: (_path, page) => ({
    title: page.collection === "changelog" ? `Changelog: ${page.title}` : page.title,
    description: page.description ?? "",
    ...ogCardConfig,
  }),
});

Keep the route at src/pages/og/[...slug].ts, the map’s keys, the default getSlug, and PNG output: page routes link to ogImageUrl, so a changed location, key, slug, or format leaves pages pointing at cards that don’t exist. A page whose frontmatter sets socialImage uses that image instead, because the page route passes entry.data.socialImage ?? ogImageUrl.

What an override changes

An override changes only what that route serves. llms-full.txt and hosted MCP keep the Markdown Nimbus generates.

The shared routes and routes that use markdownRoute() or markdownSourceRoute() must keep export const prerender = true. The build fails if one is rendered on request.

Call the factory in the route file itself, as the examples above do. Like Astro’s own prerender detection, Nimbus finds it by reading the route file, so a factory re-exported from another module is not checked when the build starts. At the end of the build, Nimbus warns about every Markdown version and llms.txt index that no prerendered route generated, and names the route that serves it.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close