Next.js の SSG と SSR

App Router

サーバーコンポーネント

Next.js の App Router では、デフォルトですべてのコンポーネントが React Server Components (RSC) です。RSC では SWR から SWRConfig とキーシリアライゼーション API をインポートできます。

import { unstable_serialize } from 'swr' // ✅ サーバーコンポーネントで利用可能
import { unstable_serialize as infinite_unstable_serialize } from 'swr/infinite' // ✅ サーバーコンポーネントで利用可能

import { SWRConfig } from 'swr' // ✅ サーバーコンポーネントで利用可能

SWR の hook API は RSC では利用できないためインポートすることはできません。

import useSWR from 'swr' // ❌ これはサーバーコンポーネントでは利用できません
import useSWRInfinite from 'swr/infinite' // ❌ これはサーバーコンポーネントでは利用できません
import useSWRMutation from 'swr/mutation' // ❌ これはサーバーコンポーネントでは利用できません

クライアントコンポーネント

コンポーネントに 'use client' ディレクティブを付けるか、クライアントコンポーネントから SWR をインポートすることで、SWR クライアントのデータ取得フックを使用できます。

'use client'

import useSWR from 'swr'

export default function Page() {
  const { data } = useSWR('/api/user', fetcher)
  return <h1>{data.name}</h1>
}

サーバーコンポーネントでデータをプリフェッチする

デフォルトデータを使ったプリレンダリング パターンと同様に、React Server Components (RSC) を使用すればさらに先へ進むことができます。

サーバーサイドでデータのプリフェッチを 開始 し、<SWRConfig> provider の fallback オプションを介してクライアントコンポーネントツリーに promise を渡すことができます:

import { SWRConfig } from 'swr'

export default async function Layout({ children }: { children: React.ReactNode }) {
  // サーバーサイドでデータフェッチを開始します
  const userPromise = fetchUserFromAPI()
  const postsPromise = fetchPostsFromAPI()

  return (
    <SWRConfig
      value={{
        fallback: {
          // promise をクライアントコンポーネントに渡します
          '/api/user': userPromise,
          '/api/posts': postsPromise,
        },
      }}
    >
      {children}
    </SWRConfig>
  )
}

2つのデータ取得関数呼び出し fetchUserFromAPI()fetchPostsFromAPI() は、即座に await しないため、サーバーサイドで並列に実行されます。

React Server Components では、"use client" 境界を越えて promise を渡すことができ、SWR はサーバーサイドレンダリング中にそれらを自動的に解決します:

'use client'

import useSWR from 'swr'

export default function Page() {
  // SWR はサーバーコンポーネントから渡された promise を解決します
  // SSR とクライアント hydration 中に、`user` と `posts` の両方が準備完了します
  const { data: user } = useSWR('/api/user', fetcher)
  const { data: posts } = useSWR('/api/posts', fetcher)

  return (
    <div>
      <h1>{user.name}'s Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}

その後、クライアントサイドでは SWR が引き継ぎ、通常通りの動作を維持します。

サーバーコンポーネントからクライアントコンポーネントに promise を渡すことで、データフェッチをできるだけ早くサーバーサイドで開始できます。そして、データを実際に使用する UI 境界(最も近い Suspense 境界または Next.js layout)のみが、ストリーミング SSR 中にブロックされます。

アプリケーションでこのプリフェッチパターンを段階的に採用するには、strictServerPrefetchWarning オプションを有効にすることができます。これにより、キーに事前入力されたデータが提供されていない場合にコンソールに警告メッセージが表示され、どのデータフェッチ呼び出しがサーバーサイドプリフェッチの恩恵を受けられるかを特定するのに役立ちます。

クライアントサイドでのデータフェッチ

ページが頻繁に更新されるデータを含み、それをプリレンダリングする必要がない場合、useSWR は最適です。特別な設定は不要で、データを使用するコンポーネント内で useSWR をインポートしてフックを使用するだけです。

以下のように動作します:

  • まず、データなしでページを即座に表示します。データがない場合のローディング状態を表示できます。
  • 次に、クライアントサイドでデータをフェッチし、準備ができたら表示します。

このアプローチは、例えばユーザーのダッシュボードページに適しています。ダッシュボードはプライベートかつユーザー固有のページであるため、SEO は関係なく、ページを事前にレンダリングする必要もありません。データは頻繁に更新されるため、リクエスト時のデータフェッチが必要です。

デフォルトデータを使ったプリレンダリング

ページを事前にレンダリングする必要がある場合、Next.js は 2 つのプリレンダリング形式: Static Generation (SSG)Server-side Rendering (SSR) をサポートしています。

SWR と組み合わせることで、SEO のためにページをプリレンダリングし、さらにキャッシュ、再検証、フォーカストラッキング、クライアントサイドで行われる一定間隔での再フェッチなどの機能も利用できます。

SWRConfigfallback オプションを使用して、事前に取得したデータをすべての SWR フックの初期値として渡すことができます。

getStaticProps の例:

 export async function getStaticProps () {
  // `getStaticProps` はサーバーサイドで実行されます
  const article = await getArticleFromAPI()
  return {
    props: {
      fallback: {
        '/api/article': article
      }
    }
  }
}

function Article() {
  // `data` は `fallback` 内にあるため常に利用可能です
  const { data } = useSWR('/api/article', fetcher)
  return <h1>{data.title}</h1>
}

export default function Page({ fallback }) {
  // `SWRConfig` 内の SWR フックはそれらの値を使用します
  return (
    <SWRConfig value={{ fallback }}>
      <Article />
    </SWRConfig>
  )
}

このページはプリレンダリングされます。SEO に優れ、迅速に応答しますが、クライアントサイドでは完全に SWR によって駆動されています。データは動的で、時間の経過とともに自動更新されます。

Article コンポーネントは最初は事前に生成されたデータをレンダリングし、ページがハイドレートされた後、最新のデータを再度フェッチして更新します。

複雑なキー

useSWR は、array 型および function 型のキーとともに使用できます。これらの種類のキーで事前にフェッチしたデータを利用するには、fallback キーを unstable_serialize でシリアライズする必要があります。

import useSWR, { unstable_serialize } from 'swr'

export async function getStaticProps () {
  const article = await getArticleFromAPI(1)
  return {
    props: {
      fallback: {
        // unstable_serialize() を使用した配列スタイルのキー
        [unstable_serialize(['api', 'article', 1])]: article,
      }
    }
  }
}

function Article() {
 // 配列スタイルのキーを使用しています
  const { data } = useSWR(['api', 'article', 1], fetcher)
  return <h1>{data.title}</h1>
}

export default function Page({ fallback }) {
  return (
    <SWRConfig value={{ fallback }}>
      <Article />
    </SWRConfig>
  )
}