zfb
GitHub repository

Type to search...

to open search from anywhere

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>
  );
}

Revision History

CreatedUpdated