サーバーコンポーネントのレンダリング戦略

profile image

サーバーコンポーネントのレンダリング戦略を理解し、注意点を確認します。また、Parallel Routesと併用する際に発生する可能性のある問題とその解決策についても説明します。

この記事は Jetbrains's Coding Agent Junie junie logoによって翻訳されました。誤訳があれば教えてください!

サーバーコンポーネントのレンダリング

Reactの伝統的なコンポーネントであるクライアントコンポーネントは、自身の**状態(state)**または親の状態が変化するとリレンダリングが発生します。しかし、サーバーコンポーネントではフックを使用できません。つまり、サーバーコンポーネントは状態を持たないということです。以下の例のように、親の状態が変わってもサーバーコンポーネントはリレンダリングされません。では、サーバーコンポーネントはいつリレンダリングされるのでしょうか?

server-component-rendering-strategies-example1

サーバーコンポーネントのレンダリング戦略

Next.jsの公式ドキュメントによると、Static、Dynamic、Streamingの3つの戦略でレンダリングが行われます。

Static Rendering

Static RenderingはNext.jsのデフォルトのレンダリング戦略です。この戦略の主な特徴は、ビルド時にコンポーネントを一度レンダリングし、その結果を再利用するという点です。

  • ビルド時レンダリング
    • サーバーコンポーネントはアプリケーションのビルド時にレンダリングされます
    • レンダリング結果はCDNにキャッシュされ、すべてのユーザーに提供されます
  • データの不変性
    • 一度レンダリングされたコンポーネントのデータは基本的に変更されません
    • ページを更新しても同じデータが表示されます
    • クライアントの状態変更はサーバーコンポーネントのデータに影響を与えません
  • Revalidationメカニズム
    • revalidateTag()またはrevalidatePath()を通じて手動でリレンダリングが可能です
  1. 画面を更新した場合

    server-component-rendering-strategies-example2

    stateを変更してリロードしても、データが変わらないことがわかります。

  2. revalidateを行った場合

    server-component-rendering-strategies-example3

    Next.jsのrevalidateTagを実行すると、サーバーコンポーネントのキャッシュを無効化し、データを再取得してリレンダリングすることがわかります。

Dynamic Rendering

Dynamic Renderingは、各ユーザーリクエストごとにサーバーで新たにコンポーネントをレンダリングするNext.jsのレンダリング戦略です。

サーバーコンポーネントは、次の2つの条件のいずれかを満たすと自動的にDynamic Renderingに切り替わります。

  1. Dynamic APIの使用
  2. fetchオプションに{ cache: 'no-store' }が見つかる

レンダリング戦略決定表

Dynamic APIの使用データキャッシングレンダリング結果
❌ いいえ✅ キャッシュありStatic
✅ はい✅ キャッシュありDynamic
❌ いいえ❌ キャッシュなしDynamic
✅ はい❌ キャッシュなしDynamic

つまり、Dynamic APIを使用せず、fetchにキャッシングを適用した場合のみStatic Renderingとなります。

Dynamic APIs

Dynamic APIとは、リクエスト時点のコンテキストに依存するAPIを指します。主なDynamic APIを見てみましょう。

1. cookies()

typescript
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()

typescript
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

typescript
// app/page.tsx
export default function Page({
  searchParams,
}: {
  searchParams: { query: string };
}) {
  return <div>Search query: {searchParams.query}</div>;
}

Parallel Routesと併用する際の注意点

以下の例のようにsearchParamsを使用してモーダルを制御する場合、Dynamic Renderingによる問題が発生する可能性があります。

  • searchParamsの変更時にサーバーコンポーネントがリレンダリングされる
  • データフェッチによるモーダルの遅延
  • モーダルの開閉と同時にデータが変更される
typescript
// 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;

server-component-rendering-strategies-example4

解決策

最も簡単で効果的な方法は、Next.jsのルーティングシステムを迂回し、ブラウザの組み込みHistory APIを直接使用することです。

typescript
'use client';

function ProductList() {
  const openModal = () => {
    // Next.jsのルーティングを経由せずにURLを直接変更
    history.pushState(null, '', `?modal=true`);
  };

  return (
    <div>
      <button onClick={openModal}>モーダルを開く</button>
    </div>
  );
}

LinkuseRouterを使用すると、変更されたURLに対するナビゲーションが発生し、新たにレンダリングロジックが実行されます。しかし、History APIを使用するとNextのルーティングシステムを経由せずにURLのみを変更するため、レンダリングロジックは実行されませんが、NextではuseSearchParamsusePathnameとの同期をサポートしているため、URL変更を検知してモーダルを表示することができます。

まとめ

サーバーコンポーネントはNext.jsにおいて重要なレンダリング最適化戦略ですが、誤って使用すると意図しないリレンダリングやパフォーマンス低下を引き起こす可能性があります。サーバーコンポーネントを効果的に使用するには、これらのレンダリング戦略をよく理解し、適切に活用することが重要だと思われます。

❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0