Routes
Add pages and API routes from a plugin.
Pages
createView() adds a page. view returns its content, and type tells ui.renderPage which page it is. data is the page data, like its title in <head>:
type Types = {
views: { "showcase.index": { items: string[] } };
};
export function showcasePlugin<C extends AppShape = AppShape>(options: {
items: string[];
}): Plugin<C, Types> {
return {
name: "acme:showcase",
routes({ createView }) {
createView({
path: "/showcase",
data: { title: "Showcase" },
view: () => ({
type: "showcase.index",
props: { items: options.items },
node: <p>{options.items.join(", ")}</p>,
}),
});
},
};
}A custom page at the same path takes priority over a plugin page.
Translated Pages
view is called once for each language, and receives its lang. The language prefix is added to the path for you.
createView({
path: "/showcase",
view: (_params, { lang }) => ({
type: "showcase.index",
props: { items: options.items },
node: <h1>{lang === "zh" ? "展示" : "Showcase"}</h1>,
}),
});When the path differs per language, set lang to create the page in that language only.
Dynamic Routes
Add [name] to the path, and list each page in staticPaths. view receives the params.
createView({
path: "/showcase/[slug]",
staticPaths: options.items,
view: ({ slug }) => ({
type: "showcase.item",
props: { slug: slug as string },
node: <h1>{slug}</h1>,
}),
});On-Demand Rendering
With render: "dynamic", view also receives the request. Return undefined for a 404:
createView({
path: "/account",
render: "dynamic",
data: { title: "Account", noindex: true },
view(_params, { request }) {
const user = /session=([^;]+)/.exec(request.headers.get("cookie") ?? "")?.[1];
if (!user) return;
return {
type: "acme.account",
props: { user },
node: <p>Signed in as {user}</p>,
};
},
});API Routes
createApi() takes a handler per HTTP method, or ALL for all of them. Add render: "static" to generate a file from the GET handler at build time:
routes({ createApi }) {
createApi({
path: "/api/ping",
POST: async (request) => Response.json(await request.json()),
});
},A param must be a whole path segment: use /data/[id]/info.json, not /data/[id].json.
A route at the path of a page in a static source fails the build. A dynamic source can add pages after the build, so the route takes priority over its page.
Content Render Mode
A route that lists your pages depends on them. With a dynamic source, like Notion, that list changes after the build, so the route must render on demand too. this.getContentRender() returns the render mode of your content, which follows the mode of the site:
routes({ createApi }) {
createApi({
path: "/pages.json",
render: this.getContentRender(),
GET: async () => {
const urls: string[] = [];
for (const page of (await this.getLoader()).getPages()) urls.push(page.url);
return Response.json(urls);
},
});
},Pass a source key to get the render mode of that source only, like this.getContentRender("blog").
A single route rendered on demand means the site needs a server. Add one only when your plugin needs it.
Derived Routes
deriveRoutes runs once per language, after the other routes are created. It receives the same functions as the routes hook, so use it for routes generated from your pages. This plugin adds a print version of each content page:
async deriveRoutes({ createView }, { lang }) {
const loader = await this.getLoader();
createView({
path: "/print/[...slugs]",
render: this.getContentRender(),
data: { noindex: true },
staticPaths: loader.getPages(lang).map((page) => page.slugs),
view: async ({ slugs }) => {
const page = loader.getPage(slugs as string[], lang);
if (!page) return;
const body = await this.getPageBody(page);
return {
type: "print",
props: { page },
node: body?.node,
};
},
});
},path has no language prefix. To type the props, use the page type of the site:
Plugin<C, { views: { print: { page: C["page"] } } }>;Reading Existing Routes
The routes argument has the custom pages and plugin pages of that language. This plugin adds a JSON file with the params of each page, at /meta<path>. For a prerendered route, it creates one file per entry in route.pages. For a route rendered on demand, it creates one API route:
deriveRoutes({ createApi }, { routes }) {
for (const route of routes) {
if (route.render === "dynamic") {
createApi({
path: `/meta${route.path}`,
GET: (_request, { params }) => Response.json(params),
});
continue;
}
for (const { path, params } of route.pages)
createApi({
path: `/meta${path}`,
render: "static",
GET: () => Response.json(params),
});
}
},Last updated on