Next.js では、レンダリング手法を CSR・SSR・SSG・ISR の中から選ぶことができます。
本記事では、それぞれの特徴に触れつつ、実装方法を紹介します。
CSR (Client Side Rendering)
クライアントからのリクエストに対して、サーバーは空の HTML と JavaScript を返します。
この JavaScript がブラウザ上で実行されることにより、実際に表示する HTML をレンダリングします。
メリット
- サーバーとの通信が初期遷移時のみに抑えられる
- ページ遷移が高速
デメリット
- 初回読み込み時に全てのデータを一括して取得するので、ページが表示されるまでの時間が長い(アプリケーションの規模が大きくなればなるほど、時間が長くなる)
- JavaScript はブラウザで実行されているため、ページ表示までの時間(JavaScript の実行時間)が使用しているマシンスペックに依存し、全てのユーザーに安定した速度を提供できない
- サーバーから返却される HTML 自体は空のため、Web クローラーからページの内容が認識されず、SEO で不利になる
条件
- ファイルの先頭に
"use client"が記述されている
実装例
"use client";
import React, { useEffect, useState } from "react";
export default function CsrPage() {
const [products, setProducts] = useState([]);
const fetchData = async () => {
const res = await fetch("https://dummyjson.com/products");
const data = await res.json();
setProducts(data.products);
};
useEffect(() => {
fetchData();
}, []);
return (
<>
<h3>Built with CSR</h3>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
}
SSR (Server Side Rendering)
クライアントからのリクエストがあると、サーバーはサーバー側で JavaScript を実行して表示する HTML を生成(レンダリング)します。そして生成済みの HTML をクライアントに返し、クライアントはブラウザにそのまま HTML を表示します。
CSR と大きく異なる点は、ブラウザ側の仕事量です。CSR では、クライアント側に「JavaScript を実行して表示する HTML を生成する」という仕事がありますが、SSR では HTML を生成する仕事はサーバー側にあるため、ブラウザは返ってきた HTML を表示するのみの仕事になります。
メリット
- サーバー側で必要なページのみ生成し、ブラウザはそれを表示するだけなので、ページが表示されるまでの速度が速い
- ブラウザで JavaScript を実行する必要がないため、使用しているマシンスペックに依存しない。そのため、全てのユーザーに安定した表示速度を提供できる
- サーバーからはレンダリングが完了した HTML を渡されるため、CSR と比較して SEO で有利
- 常に最新のデータを元にページを生成することができる
デメリット
- ページ遷移の度にサーバーへの通信が発生する
条件
-
fetch()の第二引数に、{ cache: "no-store" }を指定している -
useEffect、useState、onClickなどのクライアントサイドでのみ使用可能な機能を使用していない(ファイルの先頭に"use client"が記述されていない)
実装例
import React from "react";
export default async function SsrPage() {
const res = await fetch("https://dummyjson.com/products", {
cache: "no-store",
});
const data = await res.json();
return (
<>
<h3>Built with SSR</h3>
<ul>
{data.products.map((product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
}
SSG (Static Site Generation)
アプリケーションビルド時にページを生成して静的ページとしてサーバーに保管し、リクエストがあるとその静的ファイルを返します。
JavaScript を実行してレンダリングするプロセスを省くことができるため、サーバー側の仕事はリクエストがあったページを返すだけで、クライアント側の仕事はそれを表示するだけになります。
これにより、画面表示までの時間が高速になります。
メリット
- ページ表示速度が最速
デメリット
- 更新が入った場合に、再度手動でビルドする必要がある
条件
-
fetch()の第二引数に、{ cache: "force-cache" }を指定している(デフォルトで設定されているオプションのため、省略可) -
useEffect、useState、onClickなどのクライアントサイドでのみ使用可能な機能を使用していない(ファイルの先頭に"use client"が記述されていない)
実装例
import React from "react";
export default async function SsgPage() {
const res = await fetch("https://dummyjson.com/products");
const data = await res.json();
return (
<>
<h3>Built with SSG</h3>
<ul>
{data.products.map((product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
}
以下のようにデータフェッチを行っていない(fetch 関数を使用していない)場合も、デフォルトで SSG として認識されます。
import React from "react";
export default function SsgPage() {
return <h3>Built with SSG</h3>;
}
ISR (Incremental Static Regeneration)
SSR と SSG を組み合わせたレンダリング手法です。
アプリケーションビルド時にページを生成して静的ページとしてサーバーに保管し、リクエストがあるとその静的ファイルを返します。
ここまでは SSG と同様の挙動ですが、ISR では、特定の期間が経過したらバックグラウンドでページを再生成することができます。
たまに更新されるような場合、手動で再ビルドを実行する必要がある SSG の弱点の解決策として誕生しました。
メリット
- 表示速度が最速
- 自動再生成により、たまに更新がある場合にも画面に反映することができる
デメリット
- 再生成のタイミングによっては、最新の状態の画面を反映することができない
条件
-
fetch()の第二引数に、{ next: { revalidate: 10 } }(例)を指定している -
useEffect、useState、onClickなどのクライアントサイドでのみ使用可能な機能を使用していない(ファイルの先頭に"use client"が記述されていない)
実装例
import React from "react";
export default async function IsrPage() {
const res = await fetch("https://dummyjson.com/products", {
next: {
revalidate: 30,
},
});
const data = await res.json();
return (
<>
<h3>Built with ISR</h3>
<ul>
{data.products.map((product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
}
動作確認・ビルド
SSR・SSG・ISR の挙動は、アプリケーションをビルドすることで確認できます。なお、CSR はクライアントサイドでレンダリングされるため、ビルド時の出力では静的コンテンツとして扱われますが、実際のレンダリングはブラウザ上で行われます。
以下コマンドでビルドが実行されます。
npm run build
ビルドが正常終了すると、以下のようなメッセージが表示されます。
それぞれのページ URL と、そのページがどのレンダリング方式でビルドされたかが表示されます。
ページの左側に ○ が表示されているものは、SSG・ISR でビルドされています。CSR のページも ○ マークが表示されますが、これは静的アセットとして出力されることを示しており、実際のレンダリングはクライアントサイドで行われます。
λ が表示されているものは、SSR でビルドされています。
Route (app) Size First Load JS
┌ ○ / 5.29 kB 89.5 kB
├ ○ /_not-found 885 B 85.1 kB
├ ○ /csr 439 B 84.6 kB
├ ○ /isr 144 B 84.3 kB
├ ○ /ssg 144 B 84.3 kB
└ λ /ssr 144 B 84.3 kB
+ First Load JS shared by all 84.2 kB
├ chunks/69-593824af3050bb4a.js 28.9 kB
├ chunks/fd9d1056-c50cb70e7323352b.js 53.4 kB
└ other shared chunks (total) 1.9 kB
○ (Static) prerendered as static content
λ (Dynamic) server-rendered on demand using Node.js
Next.js は、ビルド時にそれぞれのページの**データフェッチを行っている箇所(fetch 関数を使用している箇所)**をチェックします。
データフェッチを実行していない(fetch 関数を使用していない)ページは、SSG としてページを生成します。
データフェッチを実行している(fetch 関数を使用している)ページは、fetch 関数に指定されているオプションをチェックし、それぞれ以下のようにビルドします。
-
{ cache: "force-cache" }を指定している、または何も指定されていない:SSG -
{ cache: "no-store" }を指定している:SSR -
{ next: { revalidate: 10 } }(例)を指定している:ISR
