RRelayer
ホーム/ルーティング

ルーティングとページ#

ルーターは src/Pages/ をファイルシステムどおりにマッピングします(Next.js App Router 互換の規約)。

ファイル役割
page.psxそのルートを描画(1 ディレクトリに 1 つ)
layout.psx子ページをラップ(ルート→末端へ積み重ね)
error.psxエラーページ(abort()/notFound() 受け。404/フォールバック)
route.phpJSON API エンドポイント
[param]/動的セグメント($ctx->params['param'] で取得)
(group)/ルートグループ。URL を増やさずファイル整理/個別 layout.psx
_private/先頭 _ のフォルダ。配下ごとルーティング対象外

1 つのディレクトリは ページroute.php のどちらか一方です(両立不可)。

ページの 2 つの書き方#

関数スタイル#

<?php
use App\Service\UserRepository;
use Polidog\Relayer\Router\Component\PageContext;

return function (PageContext $ctx, UserRepository $users): Closure {
    $ctx->metadata(['title' => 'Users']);

    return fn () => (
        <ul>
            {array_map(fn ($u) => <li>{$u->name}</li>, $users->all())}
        </ul>
    );
};

2 段(factory がレンダー用クロージャを返す)にすると、描画前にメタデータやキャッシュポリシーを宣言できます。1 段で Element を直接返しても構いません。

クラススタイル#

<?php
namespace App\Pages\Users;

use Polidog\Relayer\Router\Component\PageComponent;
use Polidog\UsePhp\Runtime\Element;

final class UserDetailPage extends PageComponent
{
    public function __construct(private readonly UserRepository $users) {}

    public function render(): Element
    {
        $user = $this->users->find($this->getParam('id'));

        return <h1>{$user->name}</h1>;
    }
}

引数は 型で オートワイヤされます。PageContextRequestIdentity (null 許容なら任意、非 null なら認証必須を意味する)、そしてコンテナのサービス。 $_GET / $_POST / $_SERVER を直接読まず、必ず Request を受け取ってください。

動的セグメント#

src/Pages/docs/[slug]/page.psx/docs/:slug にマッチし、値は $ctx->params['slug'] で取得します(このサイトのドキュメント表示ページが実例)。セグメントは 1 区切りで、スラッシュは含みません。[name]name は英字/アンダースコア始まりの英数字です。Next.js のような キャッチオール [...slug] や省略可能 [[param]] は非対応(単一セグメントのみ)。複数階層を受けたいときは階層ぶんの [param] を切ります。

ルートグループ (group)/ と除外フォルダ _private/#

ルートグループ — 丸括弧で囲んだフォルダ (name)/ は URL セグメントを増やしません。URL を変えずにファイルを整理したり、一部のルートにだけ別の layout.psx を着せたりできます(括弧名そのものは URL に出ません)。

src/Pages/
  (marketing)/
    layout.psx          ← (marketing) 配下だけのレイアウト
    page.psx            → "/"
    about/page.psx      → "/about"
  (app)/
    layout.psx          ← 別レイアウト
    dashboard/page.psx  → "/dashboard"

異なる物理パスが同じ URL へ解決する衝突(例: (a)/about(b)/about がどちらも /about)は、初回リクエストではなく スキャン/ routes:compile 時にエラーになります。

除外フォルダ — 先頭が _ のフォルダ(_private_components など)は、そのフォルダと配下すべてがルーティング対象から外れます。URL を持たせたくない断片や下書きを src/Pages/ 配下へ同居させたいときに使います。

404 とエラー#

ルックアップが空のときは http_response_code() を手で立てず、 $ctx->notFound()(=$ctx->abort(404))で「無い」を表明します。 HttpException が投げられ、ルーターが error.psx(または組み込みのエラーページ)を描画します。

return function (PageContext $ctx, UserRepository $users): Closure {
    $user = $users->find($ctx->params['id']) ?? $ctx->notFound();
    return fn () => <h1>{$user->name}</h1>;
};

任意の 4xx/5xx は $ctx->abort(403) のように。3xx は abort() ではなく $ctx->redirect() です(→ サーバーアクション)。 error.psx はルート直下に 1 つ。404 以外はステータス/メッセージを受け取れます(404 は常に統一の Not Found 表示)。

レイアウト#

layout.psxLayoutComponent を継承したクラスで、$this->getChildren() に子ページが入ります。ネストした各階層のレイアウトがルートから順に積み重なります。レイアウトはコンテナ DI を介さず new で生成されるため、依存のないチャームに留めるのが定石です(このサイトのヘッダ/フッタがそれ)。

ルートの確認とコンパイル#

vendor/bin/relayer routes          # 検出された全ルートを一覧
vendor/bin/relayer routes:compile  # 本番用スナップショットを書き出し

routes は検出された全ページ・API エンドポイントとメソッドを一覧表示します。

既定ではルーターはリクエストごとに src/Pages/ を走査します。デプロイ時に routes:compile を実行すると var/cache/routes/routes.php(ポータブルで OPcache 済み)へ書き出し、本番はツリー走査の代わりにそれ 1 つを読みます。ファイルの有無だけがゲートで、無ければライブ走査に戻ります(dev は常に最新で stale になりません)。ルートグループによる URL 衝突や、page と route.php の競合は、初回リクエストではなく routes:compile(デプロイ時)で失敗します。

最終更新: 2026-05-19
変更履歴 (2)
  • バージョン差分表記(vX.Y.Z 追加/破壊的変更/依存バージョン注記)を削除し現在形に整理
  • 新規作成