ルーティング
pages/ 配下のファイルシステムルーティングが zfb で URL にどうマッピングされるか。
zfb は pages/ ディレクトリ配下で ファイルシステムルーティング を採用しています。ルーターはビルド時(および dev では変更のたびに)pages/ をスキャンし、各ページのソースファイルをルートへと変換します。この規約は Next.js や Astro で見覚えのあるものと一致します。
ファイルからルートへのマッピング
| File | Route | Notes |
|---|---|---|
pages/ | / | |
pages/ | / | |
pages/ | / | SSG 専用。MDX パイプライン |
pages/ | / | SSG 専用。静的アセットのコピー |
pages/ | / | |
pages/blog/[slug].tsx | /(動的) | |
pages/docs/[...slug].tsx | /(catchall) | |
pages/docs/[[...slug]].tsx | /(オプショナル catchall) | ベース URL の / にもマッチ |
pages/[lang]/[slug].tsx | / |
この表は pages/ からスキャンされるユーザー作成ファイルを説明しています。プラグインは 同じ URL パターン文法で、コンパイル済み ESM .js モジュールを含むツリー外のルートを 注入できます。一致するユーザーページは常に優先されます。注入ルートの詳細は プラグイン を参照してください。
最初に押さえておきたいルールがいくつかあります。
_で始まるファイル(例:_app.tsx)は無視されます。ルートの隣に置きたいが公開はしたくない共有ヘルパーには、このプレフィックスを使ってください。ページとして受け付けられる拡張子は
.tsx・.ts・.jsx・.js・.mdx・.md・.htmlです。スクリプトページの形は.tsxだけではありません。.jsxは TypeScript を使わないだけの同じ形で、.tsxと同様に JSX の記述をそのまま書けます。JSX を使わない形が.tsと.jsです。.tsでは<…>が型アサーションの構文として予約されているため JSX は書けず、そうしたページは要素ツリーをフレームワークのファクトリ関数(Preact ならh(...)、React ならcreateElement(...))を直接呼んで組み立て、その関数を明示的に import します。これは自動 JSX ランタイムと矛盾するものではありません。自動ランタイムが補ってくれるのは JSX 構文 に対するファクトリであり、これらのページにはその構文自体が存在しないからです。4 つのスクリプト形式はいずれも同じ規則でルーティングされます。pages/内のそれ以外の拡張子のファイルはスキップされます(警告がログに出ます)。そのため README やメモを置いても問題ありません。ルーティング対象の拡張子を持っていても、ページになることが決してない次の 2 種類のファイルは警告なしでスキップされます。TypeScript の型定義ファイル(
pages/)と、隣に置いたテストファイル(env. d. ts pages/、index. test. ts pages/)です。それ以外でページ拡張子を持つファイルは、すべてページとして扱われます。about. spec. tsx pages/のような単なるヘルパーモジュールも例外ではなく、default export が無いためビルドが失敗します。正確な条件とヘルパーの置き場所については、後述のhelpers. ts pages/配下のヘルパーモジュール を参照してください。同じルートに解決される 2 つのファイルがあると、ビルド時に
RouterError::AmbiguousRouteが発生します。ルーターが暗黙のうちに勝者を選ぶことはありません。ルーターはまた、2 つのルートがパラメータ名だけ異なり同じ URL にマッチする場合(例:docs/[a].tsxとdocs/[b].tsx)にRouterError::AmbiguousShapeを、オプショナル catchall が同じ位置で別のルートと重なる場合にRouterError::OptionalCatchallConflictを発生させます。
.md と .html のページエントリの完全なコントラクトと v1 の制限については Markdown and HTML Pages を参照してください。
スキャンは zfb-router クレートの Router::scan が行います。結果は 静的ルートが動的ルートより優先され、動的ルートが catchall より優先される ようにソートされます。より具体的なルートが先にマッチします。
pages/ 配下のヘルパーモジュール
pages/ はルートテーブルであって、汎用のソースディレクトリではありません。受け付ける拡張子に .ts と .js が加わった今、ここに置いたモジュールはほとんど何でもページに見えてしまいます。共有ヘルパーは pages/ の 外(src/ など)に置くか、前述の _ プレフィックスを使ってください。スキャンは名前が _ で始まるファイルに加えて、_ で始まるディレクトリを通るパスのファイルもスキップするので、pages/ も pages/ もルーティングからは見えないままになります。
警告なしでスキップされる慣用的なサイドカーは 2 種類だけで、その条件は見た目より狭くなっています。次のいずれかの名前を持つファイルだけが、ページではないサイドカーとして扱われます。
<stem>.test.<ext>または<stem>.spec.<ext>。<ext>はスクリプトページの 4 拡張子(.tsx・.ts・.jsx・.js)のいずれかで、<stem>が空でないものです。pages/やindex. test. ts pages/は隣に置いたテストであって、ルートではありません。about. spec. tsx <stem>が空でない<stem>.d.ts。pages/のような TypeScript の型定義ファイルの形です。env. d. ts
それ以外はすべてルーティングされます。スキップされそうに見えて実際はされない、次の 3 つのケースには注意してください。
名前全体が接尾辞そのものになっている、stem のない ファイル。
pages/や、前に何も付かない. test. ts .d.tsはサイドカーとは見なされず、名前は奇妙でも通常のページとしてルーティングされます。コンテンツ系の拡張子 に付いた
.test.*/.spec.*。pages/やapi. spec. md pages/はそのままルーティングされます。サイドカーの判定に参加するのはスクリプトページの 4 拡張子だけです。about. test. md about.spec.mdはコンテンツページの名前としても十分ありえるからです。pages/のような、ただのヘルパーモジュール。helpers. ts
最後のケースこそ覚えておきたいところです。ルーターから見れば pages/ はページなので、default export が無いことでビルドが失敗します。この失敗は意図されたものです。 zfb は pages/ 配下のヘルパーモジュールが何を意味するのかについて立場を取りません。書きかけのページだったかもしれないファイルを黙って捨てるくらいなら、ビルド時に問題をはっきり指摘するほうを選んでいます。ヘルパーを pages/ の外へ移すか、ファイル名(またはディレクトリ名)に _ を付ければ、ビルドは通るようになります。
静的・動的・catchall ルート
静的ルート(pages/)は単一の具体的な URL にマッチします。動的ルートはファイル名に [param] の角括弧を使って単一のパスセグメントをキャプチャし、catchall ルートは [...param] を使って末尾の任意個数のセグメントをキャプチャします。
catchall は オプショナル にもできます。二重角括弧の [[...param]] はディレクトリ直下のベース URL にもマッチします — pages/docs/[[...slug]].tsx は / に加えて /(slug = [])も配信します。必須形の [...param] は厳密なままで、ゼロセグメントには決してマッチしません。オプショナル catchall はルートの最後のセグメントでなければならず、同じ位置の兄弟 index.tsx(または同位置の [...param])とは共存できません — 同じ URL を取り合うため、ルーターはスキャン時にこの組み合わせを拒否します。
// pages/blog/[slug].tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return <article>Post for {params.slug}</article>;
}// pages/docs/[...slug].tsx
export default function DocsPage({ params }: { params: { slug: string[] } }) {
return <main>{params.slug.join("/")}</main>;
}上の 2 つのスニペットは、コンポーネントがエクスポートする形だけを 示しています。実際の動的ルートでは、ビルド時に具体的な URL を列挙 するための paths() エクスポート(次項で解説)も必要です。ただし export const prerender = false を指定してリクエストごとのレンダ リングにオプトインする場合はその限りではありません。いずれの場合も、 URL パラメータは常に params の下に届きます — トップレベルで分割 代入されることはありません。
paths() エクスポート
動的ルートと catchall ルートは、ビルド時にどの具体的な URL をレンダリングするかを知る必要があります。これは同じファイルから paths() 関数をエクスポートすることで実現します。
// pages/blog/[slug].tsx
export function paths() {
const posts = getCollection("blog");
return posts.map((p) => ({ params: { slug: p.slug } }));
}
export default function BlogPost({ params }: { params: { slug: string } }) {
return <article>Post {params.slug}</article>;
}zfb build は静的・動的・catchall ルートを検出し、動的・catchall ルートでは paths() を使ってレンダリングすべき具体的な URL を列挙します。静的にリテラルな 結果は、ローカル ESM export clause が paths を公開する場合を含め、直接認識できます。 非リテラルな paths() 実装は、列挙中に実行時の ESM export を使います。静的ルートに paths() export は不要です。ルート列挙はルートごと・ビルドごとに 1 回行われ、結果は 各出力 URL に再利用されます。完全なコントラクトは 動的ルート を参照してください。
角かっこの区切りを持たない静的ルートでも、ビルド時データが必要な場合は 代わりに getStaticProps() を使います — getStaticProps() を参照してください。まとめると、paths() は動的・catchall ルートの URL を列挙し、getStaticProps() は単一の静的ルートの props を計算します。
エラーページ
pages/ と pages/ は出力レイアウトにおける特別なケース です。他の HTML ルートはすべて上の表のとおりディレクトリ形式の <path>/ を書き出しますが、トップレベルの 404.tsx / 500.tsx(名前がちょうど 404 または 500 である単一の静的セグメント) は、404/ / 500/ ではなく、dist ルート直下に フラットな 404.html / 500.html を書き出します。これが当てはまるのは トップレベルだけです。pages/ は通常のディレクトリインデックス 規則に従い、foo/ を出力します。