🐥

Next.js PPRの罠

に公開

レンダリングについて日々意識して実装している方にとっては当たり前のことかもしれないのですが、ビルド結果と実装内容に乖離が出ることがあったので紹介

PPRとは

静的にできるところは先にプリレンダリングしといて、
データが必要なところはあとから動的に描画するやつ。
要するに「静的と動的のいいとこ取り」っぽいレンダリング手法。

設定方法などは割愛
ppr - next.config.js

試してみたら・・・

page.tsxにexport const experimental_ppr = trueを入れるだけで実装内容関係なくビルド結果がppr判定されてしまっていた

├ ◐ /ppr_test                       9.01 kB         167 kB

実装イメージ(抽象化してますmm)

// app/page.tsx
export const experimental_ppr = true

export default async function Page() {
  const data = await fetch('https://example.com/api/items').then((res) => res.json());

  return (
    <div>
      <h1>お知らせ一覧</h1>
      <ul>
      {data.map((item: any) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
    </div>
  );
}

これSSRじゃん

実際には await fetch() が完了するまで描画されないため、普通にSSRと変わらない挙動になる。

ちゃんと実態もPPRにしましょう

見た目が ◐(PPR)って出てても、fetch が完了するまで真っ白ならそれはもう実質SSR。
PPR判定されるだけじゃ不十分なはずで、ちゃんと「描画を分けて動的データ取得部分は遅らせる」構成じゃないとPPRの恩恵は受けられない。

たとえば Suspense を使って、重たい部分だけ後から読み込む構成にすればOK。
そのほかフィルターの際などは useTransition などを使用することも有効。

// app/page.tsx
import { Suspense } from 'react';
import ItemList from './ItemList';

export const experimental_ppr = true;

export default function Page() {
  return (
    <div>
      <h1>お知らせ一覧</h1>
      <Suspense fallback={<p>読み込み中…</p>}>
        <ItemList />
      </Suspense>
    </div>
  );
}

// app/ItemList.tsx
export default async function ItemList() {
  const data = await fetch('https://example.com/api/items').then((res) => res.json());

  return (
    <ul>
      {data.map((item: any) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

デモ

demo(動作イメージ)

Discussion