サーバーコンポーネントのレンダリング
Reactの伝統的なコンポーネントであるクライアントコンポーネントは、自身の**状態(state)**または親の状態が変化するとリレンダリングが発生します。しかし、サーバーコンポーネントではフックを使用できません。つまり、サーバーコンポーネントは状態を持たないということです。以下の例のように、親の状態が変わってもサーバーコンポーネントはリレンダリングされません。では、サーバーコンポーネントはいつリレンダリングされるのでしょうか?
サーバーコンポーネントのレンダリング戦略
Next.jsの公式ドキュメントによると、Static、Dynamic、Streamingの3つの戦略でレンダリングが行われます。
Static Rendering
Static RenderingはNext.jsのデフォルトのレンダリング戦略です。この戦略の主な特徴は、ビルド時にコンポーネントを一度レンダリングし、その結果を再利用するという点です。
- ビルド時レンダリング
- サーバーコンポーネントはアプリケーションのビルド時にレンダリングされます
- レンダリング結果はCDNにキャッシュされ、すべてのユーザーに提供されます
- データの不変性
- 一度レンダリングされたコンポーネントのデータは基本的に変更されません
- ページを更新しても同じデータが表示されます
- クライアントの状態変更はサーバーコンポーネントのデータに影響を与えません
- Revalidationメカニズム
revalidateTag()
またはrevalidatePath()
を通じて手動でリレンダリングが可能です
-
画面を更新した場合
stateを変更してリロードしても、データが変わらないことがわかります。
-
revalidateを行った場合
Next.jsの
revalidateTag
を実行すると、サーバーコンポーネントのキャッシュを無効化し、データを再取得してリレンダリングすることがわかります。
Dynamic Rendering
Dynamic Renderingは、各ユーザーリクエストごとにサーバーで新たにコンポーネントをレンダリングするNext.jsのレンダリング戦略です。
サーバーコンポーネントは、次の2つの条件のいずれかを満たすと自動的にDynamic Renderingに切り替わります。
- Dynamic APIの使用
- fetchオプションに
{ cache: 'no-store' }
が見つかる
レンダリング戦略決定表
Dynamic APIの使用 | データキャッシング | レンダリング結果 |
---|---|---|
❌ いいえ | ✅ キャッシュあり | Static |
✅ はい | ✅ キャッシュあり | Dynamic |
❌ いいえ | ❌ キャッシュなし | Dynamic |
✅ はい | ❌ キャッシュなし | Dynamic |
つまり、Dynamic APIを使用せず、fetchにキャッシングを適用した場合のみStatic Renderingとなります。
Dynamic APIs
Dynamic APIとは、リクエスト時点のコンテキストに依存するAPIを指します。主なDynamic APIを見てみましょう。
1. cookies()
import { cookies } from 'next/headers';
async function Component() {
const cookieStore = cookies();
const theme = cookieStore.get('theme');
return <div>Current theme: {theme?.value}</div>;
}
2. headers()
import { headers } from 'next/headers';
async function Component() {
const headersList = headers();
const userAgent = headersList.get('user-agent');
return <div>Accessing from: {userAgent}</div>;
}
3. searchParams
// app/page.tsx
export default function Page({
searchParams,
}: {
searchParams: { query: string };
}) {
return <div>Search query: {searchParams.query}</div>;
}
Parallel Routesと併用する際の注意点
以下の例のようにsearchParamsを使用してモーダルを制御する場合、Dynamic Renderingによる問題が発生する可能性があります。
- searchParamsの変更時にサーバーコンポーネントがリレンダリングされる
- データフェッチによるモーダルの遅延
- モーダルの開閉と同時にデータが変更される
// app/@modal/default.tsx
'use client'
import {useSearchParams} from "next/navigation";
import {Modal} from "@/app/@modal/(.)post/[id]/modal";
function Default() {
const searchParam = useSearchParams();
const isModalOpen = searchParam.get('modal') === 'true';
if (!isModalOpen) {
return null;
}
return (
<Modal>modal!!</Modal>
);
}
export default Default;
解決策
最も簡単で効果的な方法は、Next.jsのルーティングシステムを迂回し、ブラウザの組み込みHistory APIを直接使用することです。
'use client';
function ProductList() {
const openModal = () => {
// Next.jsのルーティングを経由せずにURLを直接変更
history.pushState(null, '', `?modal=true`);
};
return (
<div>
<button onClick={openModal}>モーダルを開く</button>
</div>
);
}
Link
やuseRouter
を使用すると、変更されたURLに対するナビゲーションが発生し、新たにレンダリングロジックが実行されます。しかし、History APIを使用するとNextのルーティングシステムを経由せずにURLのみを変更するため、レンダリングロジックは実行されませんが、NextではuseSearchParams
やusePathname
との同期をサポートしているため、URL変更を検知してモーダルを表示することができます。
まとめ
サーバーコンポーネントはNext.jsにおいて重要なレンダリング最適化戦略ですが、誤って使用すると意図しないリレンダリングやパフォーマンス低下を引き起こす可能性があります。サーバーコンポーネントを効果的に使用するには、これらのレンダリング戦略をよく理解し、適切に活用することが重要だと思われます。