動的ルート
paths() を使って [slug].tsx や [...slug].tsx ページがビルドすべき具体的な URL を列挙し、URL ごとの props をコンポーネントへ渡す。
このページで扱う内容
paths() が、動的ページやキャッチオールページが出力すべき URL をどう 列挙するか、それが返す { params, props } の契約、そしてページ コンポーネントがそのデータをどう受け取るかを解説します。静的ルートの 基礎については Routing を参照してください。
動的ルート — pages/blog/[slug].tsx — は単一の URL にマッピングされません。 角かっこで囲まれたセグメントはパラメータであり、zfb はビルド時にそれを どの具体的な値で埋めればよいかを知る必要があります。それが paths() の 仕事です。
キャッチオールルート — pages/docs/[...slug].tsx — も同じように動作しますが、 その slug パラメータは単一のセグメントではなく、末尾の 1 つ以上の セグメントをキャプチャします。
paths() の契約
paths() は { params, props } オブジェクトの配列を返します。同期的に返しても、 Promise として返しても構いません。ルーターはその配列を消費し、1 エントリが 1 つの レンダリング済み URL になります。
type PathEntry<P = Record<string, unknown>> = {
/** Values for the bracketed segments, keyed by parameter name. */
params: Record<string, string | string[]>;
/** Optional per-URL data threaded to the page component as `props`. */
props?: P;
};
export function paths(): PathEntry[] | Promise<PathEntry[]>;paramsのキーは、ファイル名の角かっこ内の名前と一致する必要があります。[slug].tsxならキーはslugです。[lang]/[slug].tsxならlangとslugの両方を指定します。キャッチオール[...slug].tsxでは、slugは 末尾セグメントのstring[]です。propsはオプションで、エンジンにとっては不透明な、あなたが管理する データです。ただしpropsという名前のネストしたオブジェクトとして コンポーネントへ渡されるわけでは ありません。各キーはparamsと並んで、コンポーネントの props の トップレベルに展開(spread) されます。paths()のエントリがprops: { title: "..." }を返した 場合、コンポーネントが受け取るのは{ params, title }であり、{ params, props: { title } }ではありません。propsのキーがparamsと衝突した場合はprops側の値が優先されます。
ソースとコンパイル済み entrypoint
動的ルートには、プロジェクトページでも injectRoute で登録したコンパイル済み .js モジュールでも、ESM の paths export が必要です。 コンパイル出力では、ローカル binding を export clause で公開する形がよく使われます。
function paths() {
return [{ params: { slug: "intro" } }];
}
export { paths };zfb はこのローカル export-clause 形式を認識します。戻り値が静的にリテラルなら 直接 URL を列挙できます。paths() が import、collection、非同期処理、その他の 非リテラルなコードに依存するときは、ルート列挙中に実行時の export を評価します。 そのためパッケージはビルド済みの dist/ ルートを直接注入でき、元の .tsx entrypoint を並行して用意する必要はありません。これはファイルシステムの検出を 変更しません。pages/ に置いた .js ファイルはユーザーページとしてスキャンされません。
ブログ記事ページ
paths() の典型的な用途は、コンテンツコレクションから slug を列挙する ことです。
// pages/blog/[slug].tsx
import { getCollection } from "zfb/content";
export const frontmatter = { title: "Blog post" };
export function paths() {
const posts = getCollection("blog");
return posts.map((post) => ({
params: { slug: post.slug },
props: { title: post.data.title },
}));
}
export default function BlogPost({ params, title }) {
const post = getCollection("blog").find((e) => e.slug === params.slug);
if (!post) return <p>Not found.</p>;
return (
<article>
<h2>{title}</h2>
<post.Content />
</article>
);
}いくつか注目すべき点があります。
getCollectionは同期的です。完全なコンテンツのスナップショットは、 どの TSX が実行されるよりも前に Rust 側で事前構築されています。そのため、 この例ではasyncは不要です。ルート列挙に async module loading や Promise を返す別のヘルパーが必要な場合は、export async function paths()を使ってください。params.slugが URL に現れるものです。slug: "hello-zfb"の記事は/になります。blog/ hello- zfb titleは —propsに入れた他のキーと同様に — エンジンにとって 不透明です。propsというキーの下にネストされるのではなく、上で 説明したとおりparamsと並んでコンポーネントのトップレベル props に展開されて届きます。シリアライズ可能なものなら何でも入れられます。
キャッチオール: ツリー全体のドキュメントページ
キャッチオールルートは末尾セグメントを任意の数だけキャプチャします。 同じテンプレートが 1 つのプレフィックスの下で多くの深さをレンダリングする 場合に便利です。slug パラメータは string[] として到着します。
// pages/docs/[...slug].tsx
import { getCollection } from "zfb/content";
export const frontmatter = { title: "Docs" };
export function paths() {
const entries = getCollection("docs");
return entries.map((entry) => ({
// entry.slug looks like "guides/setup" or "concepts/routing"
params: { slug: entry.slug.split("/") },
}));
}
export default function DocsPage({ params }) {
const slugPath = params.slug.join("/");
const entry = getCollection("docs").find((e) => e.slug === slugPath);
if (!entry) return <p>Not found.</p>;
return <entry.Content />;
}/ は params.slug === ["concepts", "routing"] で マッチします。/ は params.slug === ["guides", "setup"] でマッチします。エントリをスラッシュ区切りの形で検索する必要があるときは、 ルーターがその形(slug.)を再構築します。
静的・動的・キャッチオール — どう組み合わさるか
| ファイル名 | 種類 | URL の例 | params の形 |
|---|---|---|---|
pages/ | 静的 | / | なし |
pages/blog/[slug].tsx | 動的 | / | { slug: string } |
pages/docs/[...slug].tsx | キャッチオール | / | { slug: string[] } |
pages/docs/[[...slug]].tsx | オプショナルキャッチオール | / と / | { slug: string[] }(素の URL では []) |
pages/[lang]/[slug].tsx | 動的 × 2 | / | { lang: string, slug: string } |
2 つのパターンが同じ URL にマッチしうる場合は、より具体的なほうが 勝ちます。静的が動的に勝ち、動的がキャッチオールに勝ちます。 ルーターはルートテーブルを構築するときにこれを強制します。完全な ソート順については Routing を参照してください。
ルールと落とし穴
すべての
paramsキーには値が必要です。キーが欠けていると、HTML が 1 つでも書き出される前にビルドエラーが発生します。キャッチオールセグメントでは、
params.slugは(要素が 1 つでも)string[]でなければなりません。素の文字列を渡すと型エラーになります。必須キャッチオール(
[...slug])は空配列を拒否します。常に少なくとも 1 つのセグメントが必要です。素のディレクトリ URL(/)をビルド するには、ファイルをオプショナル形(docs [[...slug]])にリネームし、 明示的に{ params: { slug: [] } }エントリを返してください。[""]と""はどちらの形でも無効のままです。paths()は 同期的にも async にも できます。コンテンツのスナップショット全体は、どの ページが評価されるよりも前に読み込まれるため、コンテンツだけのルートはたいてい同期のままで 十分です。Promise を返すpaths()export もサポートされています。結果は純粋なデータで 決定論的に保ってください。ルーターはレンダーのはるか前、ルート列挙の段階でこれを呼び出します。ルート列挙はルートごと・ビルドごとに 1 回だけ行われます。静的にリテラルな
pathsの結果はモジュールを実行せずに読み取れます。実行時のpaths()の結果は 1 回評価され、その後メモ化されて同じルートテンプレートからレンダリングされる すべてのページで再利用されます。paths()内のエントリごとの処理(コレクションの マッピングなど)は安全で、出力 URL ごとに繰り返されることはありません。同じテンプレートに解決される 2 つのルートは、ビルド時に
RouterError::AmbiguousRouteを発生させます。また、2 つのルートが パラメータ名だけ異なり、同じ URL にマッチする場合(たとえばdocs/[a].tsxとdocs/[b].tsx)にはRouterError::AmbiguousShapeを、 オプショナルキャッチオールが同じ位置で別のルートと重なる場合にはRouterError::OptionalCatchallConflictを発生させます。ルーターが黙って 勝者を選ぶことは決してありません。
関連項目
Routing — 静的ルートの基礎。
Content Collections — ほとんどの
paths()呼び出しがデータソースとして参照するもの。同期的なgetCollection/getEntryAPI についても解説しています。静的ルートの props —
paths()は動的ルート・キャッチオールルートの URL を列挙するもの で、getStaticProps()はそれに相当する、角かっこの区切りを持たない 単一の静的ルートがビルド時データを必要とする場合の仕組みです。