RRelayer
ホーム/機能

Defer コンポーネント#

ログイン名・カート個数・A/B バケットなど「ユーザーごとに違うが、それ以外はキャッシュに乗せたい」部分は、コンポーネントを 2 つに分割します。

  1. ベースコンポーネント — 実際の描画担当。インラインでも再利用できる。
  2. defer ラッパーDefer 設定を持つだけのラッパー。

ページはラッパーを参照し、SSR ではフォールバックだけが埋め込まれます。本体はページ読み込み後に専用エンドポイント GET /_defer/{name} で取得され、プレースホルダがその場で置き換わります。メイン HTML はユーザー状態に依存しないので CDN エッジでキャッシュできます。

<?php
// src/Components/UserHeader.psx — 本体(インラインでも再利用可)
return fn (array $props) => (
    <header>こんにちは {$_SESSION['user']['name'] ?? 'ゲスト'} さん</header>
);
<?php
// src/Components/UserHeaderDeferred.psx — defer ラッパー
use Polidog\UsePhp\Component\Defer;
use function Polidog\UsePhp\Runtime\fc;

return fc(
    fn (array $props) => <UserHeader />,
    defer: new Defer(name: 'user-header', cacheControl: 'private, no-store'),
);
{/* ページ側 — fallback は普通の prop として渡す */}
<UserHeaderDeferred fallback={<HeaderSkeleton />} />

クラスコンポーネントなら属性で同じことができます。

#[Component(name: 'UserHeaderDeferred')]
#[Defer(name: 'user-header', cacheControl: 'private, no-store')]
final class UserHeaderDeferred extends BaseComponent
{
    public function render(): Element { /* 実際のコンテンツ */ }
}

手動の登録・ルート設定は不要#

defer エンドポイントを使うために、ルートを定義したり DI コンテナに登録したりマニフェストを手で編集したりする作業は一切不要です。 usephp compileDefer 設定を自動で発見し、 deferred-manifest.php を書き出します。ルーターは GET /_defer/{name} をページ/レイアウト処理より前に UsePHP::handleDeferred() へ振り分けるので、ラッパーを書くだけでエンドポイントが立ち上がります。エンドポイントは登録時の Cache-Control を返すため、共通のお知らせバーは public, s-maxage=60、セッション依存の UserHeader は private, no-store と、コンポーネント単位で キャッシュ戦略を決められます。

親 → 子へ値を渡す#

fallback 以外の prop は自動でクエリ文字列になります。

<PostCommentsDeferred fallback={<Skeleton />} post_id={$postId} sort="new" />

これは GET /_defer/post-comments?post_id=123&sort=new になり、ラッパー内側のクロージャでは $props['post_id'] で受け取れます。 スカラ(int / string / float / bool)のみで、URL 経由のため値は文字列化されます。配列・Element・Closure は渡せません。

クライアントキャッシュ#

usephp.js は defer 取得の前に 2 段のキャッシュを置きます。

  • L1 — インメモリ。 ページ生存期間中のみ。常に有効で、

フルリロードで消えます(従来挙動)。

  • L2 — localStorage リロード/タブをまたぐ。完全に opt-inで、

Defer::$localCache = true を指定したコンポーネントだけ保存します(usephp.js は HTTP の Cache-Control を見ません)。共有端末で前ユーザーの defer 内容が漏れないための設計です。

#[Defer(name: 'announcement-bar', localCache: true)]
// または .psx で
fc($render, defer: new Defer(name: 'announcement-bar', localCache: true));

既定では時間失効しません。無効化は DEFER_CACHE_VERSION のバンプ(デプロイ時)か clearDeferCache()(実行時)で行います。経過時間で失効させたい場合は localCacheTtl(秒)を足します(localCache: true 必須。正の TTL を単独指定すると例外)。

#[Defer(name: 'feed', localCache: true, localCacheTtl: 60)]
window.usePHP.clearDeferCache();                // 両層を全消去
window.usePHP.clearDeferCache('post-comments'); // 特定 defer 名

明示的リロード#

既定では defer フラグメントは一度だけ取得され、ラッパーごと消えます。Defer::$reloadable を opt-in すると、再取得可能なラッパーを DOM に残せます(フォーム更新後の一覧再取得などに)。

#[Defer(name: 'todo-list', reloadable: true)]
window.usePHP.reloadDefer('todo-list'); // 命令的に再取得
{/* フォーム送信後に自動再取得 / 任意要素のクリックで再取得 */}
<form data-usephp-form data-usephp-reload-defer="todo-list"> … </form>
<button data-usephp-reload-defer="todo-list">更新</button>

リロードは毎回その URL の両キャッシュ層を先に破棄するので、常に最新のサーバー状態を反映します。

制約#

  • モードごとに別コンポーネント。 インライン=ベース、defer=

Defer 付きラッパー。同一コンポーネントのコールサイト切替は非対応。

  • defer 名は URL-safe([A-Za-z0-9_-]+)かつアプリ内で一意。

重複するとコンパイル時エラー。

  • params はスカラのみ。 クエリ文字列経由のため。
  • 認可はコンポーネント側の責務。 名前と params は URL に露出する

ので、機微なデータを返すなら自分でセッション/権限を確認する。

  • 入れ子の defer も動作。 解決後の出力にさらに defer があれば

再帰的に hydrate されます。

  • JS なしのユーザーは fallback しか見えません。 仕様上のトレードオフ。

このドキュメントサイト自体は Defer コンポーネントを使っていません(サーバーレンダリングのみ)。なお $ctx->js($src, defer: true)<script defer> 属性のことで、ここでいう Defer コンポーネントとは別物です(→「ページ単位のスクリプト」)。

最終更新: 2026-05-19
変更履歴 (2)
  • 「配線は不要」を「手動の登録・ルート設定は不要」に。直訳の配線(wiring)が不明瞭なため何が不要か具体化
  • 新規作成