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

🌐

この記事は DeepL によって翻訳されました。誤訳があれば教えてください!

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

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

AnimatedImage.gif

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

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

Static Rendering

Static RenderingはNext.jsの基本的なレンダリング戦略です。この戦略の核心的な特徴は、ビルド時にコンポーネントを一度レンダリングして、その結果を再利用することです。

  • ビルドタイムレンダリング

    • サーバーコンポーネントは、アプリケーションのビルド時にレンダリングされます。
    • レンダリングされた結果はCDNにキャッシュされ、すべてのユーザーに提供されます。
  • データの不変性

    • 一度レンダリングされたコンポーネントのデータは基本的に変更されません。
    • ページを更新しても同じデータが表示される。
    • クライアントの状態を変更しても、サーバーコンポーネントのデータには影響しない
  • Revalidationメカニズム

    • revalidateTag()またはrevalidatePath()を使って手動で再レンダリング可能
  1. 画面をリロードした時

    AnimatedImage.gif

    stateを変えて、reloadしてもデータが変わらないことが確認できます。

  2. revalidateをした時

    AnimatedImage.gif

    Next.jsのrevalidateTagを実行した時、サーバーコンポーネントのキャッシュを無効にして、データを再呼び出ししてレンダリングすることが確認できます。

Dynamic Rendering

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

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

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

レンダリング戦略決定テーブル

|Dynamic APIの使用データキャッシュレンダリング結果||レンダリング結果|||ダイナミックAPIの使用 | -------------------- | --------------- | --------------- ❌ No | ✅ Cached | Static | ✅ Yes | ✅ Cached | ✅ No ✅ Yes | ✅ Cached | Dynamic | | ❌ No | ❌ Not No | ❌ No | ❌ Not Cached | Dynamic | ✅ Yes | ✅ Cached | Dynamic | Yes | ✅ Yes | ❌ Not Cached | Dynamic | ❌ No | ❌ Not Cached | Dynamic |

つまり、Dynamic APIを使わずにfetchにキャッシュも適用しなければStatic Renderingになります。

Dynamic APIs

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

1. cookies()

import { cookies } from 'next/headers'async function Component() { const
  const cookieStore = cookies();
  const theme = cookieStore.get('theme');
  return <div>Current theme: {theme?.value}</div>;
}

2.headers()

import { headers } from 'next/headers';

非同期関数 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: 文字列
  searchParams: { query: string };
}){ return <div> searchParams: { searchParams.query}</div>; }
  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 Default

AnimatedImage.gif

解決方法

最も簡単で効果的な方法は、Next.jsのルーティングシステムをバイパスして、ブラウザの内蔵History APIを直接使うことです。

'use client'function ProductList() { {
  const openModal = () => { // Next.jsのルーティングを通さずにURLを直接変更する。
    // Next.jsのルーティングを通さずにURLを直接変更します。
    history.pushState(null, '', `?modal=true`);
  };

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

Linkや useRouterを使うと、変更されたURLに対するナビゲーションを発生させ、新しくレンダリングロジックを回すことになります。しかし、History APIを使うとNextのルーティングシステムを通さずにURLだけ変更するため、レンダリングロジックが回転しませんが、NextでuseSearchParams,usePathnameと同期するようにサポートするため、URLの変化を検出してモーダルを表示することができます。

終わりに

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