Page module exports
Reference for named exports zfb reads from page modules.
Every file under pages/ must default-export the page component. zfb also reads a small set of named exports from page modules to decide which URLs exist, which props are passed to the component, whether a route is prerendered, and which response metadata to use.
export default function Page(props: Record<string, unknown>) {
return <main>{/* ... */}</main>;
}The shipped named exports are paths(), getStaticProps(), prerender, contentType, and headings.
Warning
export const meta is not a shipped page-module API. Do not rely on it for head tags, layout selection, Open Graph data, or page metadata. Put head markup in your component/layout code, or pass data through paths() / getStaticProps() props.
paths()
export function paths():
| Array<{ params: Record<string, string | string[]>; props?: Record<string, unknown> }>
| Promise<Array<{ params: Record<string, string | string[]>; props?: Record<string, unknown> }>>;Dynamic routes such as pages/blog/[slug].tsx, pages/docs/[...slug].tsx, and pages/docs/[[...slug]].tsx use paths() to enumerate concrete URLs. Each entry must include a params object with one key for every dynamic segment in the route. Optional props are spread into the page component's top-level props alongside params.
For SSG routes, zfb first tries to statically extract literal paths() results. Non-literal functions are evaluated through the bundled runtime during the build. A dynamic route with prerender = false is served at request time and does not need a concrete build-time URL list.
import { getCollection } from "@takazudo/zfb/content";
export async function paths() {
const posts = getCollection<{ title: string }>("blog");
return posts.map((post) => ({
params: { slug: post.slug },
props: { title: post.data.title },
}));
}
export default function PostPage({
params,
title,
}: {
params: { slug: string };
title: string;
}) {
return <h1>{title}</h1>;
}getStaticProps()
export function getStaticProps():
| { props: Record<string, unknown> }
| Promise<{ props: Record<string, unknown> }>;Static routes can export getStaticProps() to compute props before the page component renders. It is intended for static, non-dynamic routes. For dynamic routes, return per-entry props from paths() instead.
import { getCollection } from "@takazudo/zfb/content";
export function getStaticProps() {
const posts = getCollection<{ title: string }>("blog");
return {
props: {
postCount: posts.length,
latestTitle: posts.at(-1)?.data.title ?? "No posts",
},
};
}
export default function BlogIndex({
postCount,
latestTitle,
}: {
postCount: number;
latestTitle: string;
}) {
return (
<main>
<h1>Blog</h1>
<p>{postCount} posts</p>
<p>Latest: {latestTitle}</p>
</main>
);
}prerender
export const prerender: boolean;Routes are prerendered to disk by default. Set prerender = false to opt a page out of SSG and serve it through the runtime SSR adapter instead. Projects without an SSR-capable adapter fail fast when they contain prerender = false routes.
Only a literal boolean export is honored by the static extractor. Computed values are treated as the default (true).
export const prerender = false;
export default function PreviewPage() {
return <h1>This route is rendered at request time</h1>;
}contentType
export const contentType: string;contentType overrides the response Content-Type for a TSX page. Use it for non-HTML pages or when the filename extension is not enough. If omitted, zfb derives a type from the output extension, falling back to text/html; charset=utf-8.
export const contentType = "application/xml";
export default function Sitemap() {
return (
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
</url>
</urlset>
);
}headings
export const headings: readonly Array<{
depth: number;
slug: string;
text: string;
}>;MDX compilation emits a headings export for every compiled Markdown/MDX module. It is a document-order table of contents derived from headings in the source. Empty documents still export [], so consumers can import it unconditionally.
You normally do not hand-author headings in TSX pages; import it from an MDX module or read it from the page module namespace when building framework-level UI.
import DocsPage, { headings } from "../content/docs/intro.mdx";
export function getStaticProps() {
return {
props: {
tableOfContents: headings.filter((heading) => heading.depth <= 3),
},
};
}
export default function IntroPage({
tableOfContents,
}: {
tableOfContents: typeof headings;
}) {
return (
<main>
<nav>
{tableOfContents.map((heading) => (
<a href={`#${heading.slug}`}>{heading.text}</a>
))}
</nav>
<DocsPage />
</main>
);
}