💭
さよなら getServerSideProps、こんにちは async/await コンポーネント
さよなら getServerSideProps、こんにちは async/await コンポーネント
Next.js 16(App Router)への移行は、単なる記述の変更ではありません。
データの「バケツリレー」を終わらせ、コンポーネントを自立させるアーキテクチャの進化です。
1. 最大のパラダイムシフト:Push型からPull型へ
これまでの Pages Router では、ページ最上部の getServerSideProps ですべてのデータを取得し、Props を通じて下流へ「Push」していました。これが「バケツリレー」の正体です。
Pages Router(Push型)
- ページトップで全データを抱え込む
- 子・孫へ Props で流し込む
Next.js 16(Pull型)
- 必要なコンポーネントが、必要な場所で、必要なデータを直接
awaitする。
2. 実践:Layout と Container の役割分担(境界線)
getServerSideProps を解体する際に最も重要なのが「どこで await するか」の判断基準です。
ポイントは URLとの連動性 にあります。
| 取得場所 | Layout(枠) | Container(主役) |
|---|---|---|
| データの性質 | 共通・普遍的 | 固有・具体的 |
| URLとの関係 | ページ遷移しても維持される | URLが変わるたびに更新される |
| API例 |
getCategories(), getMe()
|
getDiaryDetail(id), getMemos(q)
|
3. コードで見るリファクタリング
【Before】 Pages Router:巨大な Props バケツ
ページトップの関数が、すべてのコンポーネントのデータ依存を知っている必要がありました。
// pages/diary/[id].tsx
export const getServerSideProps = async (ctx) => {
const [diary, categories] = await Promise.all([
getDiary(ctx.params.id),
getCategories()
]);
return { props: { diary, categories } };
};
export default function Page({ diary, categories }) {
return (
<Layout categories={categories}>
<DiaryContent diary={diary} />
</Layout>
);
}
【After】 Next.js 16:自立したコンポーネント
各階層が「自分に必要なデータ」だけを取得します。
// app/admin/diary/[id]/layout.tsx (Server Component)
export default async function Layout({ children }) {
// 共通のサイドバー用データのみ取得
const categories = await getCategories();
return <Sidebar categories={categories}>{children}</Sidebar>;
}
// features/diary/components/DiaryContainer.tsx (Server Component)
export async function DiaryContainer({ id }) {
// そのページ固有のメインデータのみ取得
const diary = await getDiaryDetail(id);
return (
<article>
<h1>{diary.title}</h1>
<p>{diary.content}</p>
{/* インタラクションが必要な島(Island)だけ Client Component を呼ぶ */}
<DiaryActions diaryId={id} />
</article>
);
}
4. なぜ「重複通信」を心配しなくていいのか?
ITエンジニアが懸念する「Layout と Page で同じ API を叩いたら二重リクエストになる」問題。Next.js 16 はこれを Request Memoization で解決しました。
- 同一レンダリング内
- 同一の fetch(URL + options)
であれば、自動的に 1 回にまとめられます。エンジニアは「誰かが取得済みか」を気にする必要はなく、「このコンポーネントを表示するのにこのデータが必要だ」と思ったら、迷わず await すればいいのです。
※注:
headersやcache設定が異なる場合は別リクエストとして扱われます。
5. クライアントコンポーネント(動き)の連携例
サーバーがデータを await し、クライアントが「動き(Interaction)」を担当します。
// features/diary/components/DiaryActions.tsx
'use client';
import { useRouter } from 'next/navigation';
export function DiaryActions({ diaryId }) {
const router = useRouter();
const handleLike = async () => {
await updateLike(diaryId);
// ★重要:router.refresh() を呼ぶと、
// Layoutを含むサーバーコンポーネントのみが再実行され、画面が最新化される
router.refresh();
};
return <button onClick={handleLike}>❤️ いいね</button>;
}
6. エンジニアが得る「自由」
- バケツリレーの廃止: Props の受け渡し地獄から解放され、コンポーネントの配置変更や再利用が容易になります。
-
Streaming UX: Layout は先に表示。重いデータ(Container)は
Suspenseやloading.tsxで待つ。これが今の最適解です。 - 真のカプセル化: 「その機能のデータ取得は、その機能の Container を見ればいい」という直感的な開発が実現します。
結論
getServerSideProps を捨てることは、単なる書き方の変更ではなく、設計思想の進化です。
ルールはシンプル。
- 共通データ = Layout
- 固有データ = Container
- 動き = Client
これだけ守れば、あなたの Next.js アプリは劇的にシンプルで、保守性の高い構造になります。
Discussion