サーバーコンポーネントとクライアントコンポーネント
Next.js App Routerにおけるサーバーコンポーネントとクライアントコンポーネントは、ReactとNext.jsが提供する新しいレンダリングの概念で、それぞれレンダリングされる場所が異なります。
サーバーコンポーネント
App Routerでは、特別な指定がない限りすべてのコンポーネントはデフォルトでサーバーコンポーネントとして扱われます。
サーバーコンポーネントでできること
- データのフェッチング
- サーバーでHTMLが生成されるため、パフォーマンスが改善
- SEOの強化
- JavaScriptバンドルサイズの削減:サーバーで実行されるため、そのコンポーネントのJavaScriptコードはブラウザにダウンロードされない
サーバーコンポーネントでできないこと
- ユーザーインタラクション:onClick, onChangeなどのイベントハンドラを直接使用できない
- 状態管理:useState, useReducerなどのReactのState Hooksは使用できない
- ブラウザ専用APIへのアクセス:window, document, localStorageなどのブラウザAPIに直接アクセスはできない
クライアントコンポーネント
明示的に 'use client' ディレクティブを書くことでクライアント側でレンダリングされます。
クライアントコンポーネントでできること
- ユーザーインタラクション:onClick, onChangeなどのイベントハンドラを直接使用できる
- 状態管理:useState, useReducerなどのReactのState Hooksは使用できる
- ブラウザ専用APIへのアクセス:window, document, localStorageなどのブラウザAPIに直接アクセスはできる
- ライフサイクル管理:useEffectなどのReactのEffect Hooksを使用して、コンポーネントのライフサイクルイベントに対応できる
クライアントコンポーネントでできないこと
- サーバーサイトのロジックの直接実行:データベースへの直接アクセスやAPIキーなどの秘密の情報を含むデータフェッチはできない
Next.js App Routerの最大の強みは、この2つのコンポーネントタイプを組み合わせて使用できる点です。
例えば、コンテンツの大部分やデータフェッチをサーバーサイドで処理し、インタラクティブな機能が必要な部分のみクライアントコンポーネントに切り替えることができます。
これにより、アプリケーション全体のパフォーマンスを最大化しつつ、立地でインタラクティブなユーザー体験を提供できます。
レンダリング方法
Next.js App Routerでのレンダリング方法は全部で4種類サポートされています。それぞれコンポーネントによって、レンダリング方法が異なります。
サーバーコンポーネント
- SSG( Static Rendering / 静的レンダリング )
Webサイトをビルド時にすべてのページをHTMLファイルとして事前に生成するレンダリング方法です。生成されたHTMLファイルはCDN( Contents Delivery Network )に配置され、ユーザーのリクエストに応じて瞬時に配信されます。
メリット:
- 圧倒的な表示速度:事前に生成されたHTMLがCDNから配信されるため。
- SEO効果
- セキュリティの向上:動的なサーバー処理が少ないため。
デメリット:
- コンテンツ更新時の手間:コンテンツを更新したり、新しいページを追加したりするたびに、アプリケーション全体を再ビルド・再デプロイする必要がある。
用途:
- ブログサイトやポートフォリオサイトなど、コンテンツの更新頻度が比較的低いサイト
- 高い表示速度とSEOが最優先されるサイト
- ISR( Incremental Static Regeneration )
SSGのメリット(高速性、SEO)を享受しつつ、コンテンツ更新の柔軟性も兼ねそろえたハイブリッドなレンダリング方法です。事前に静的なページを生成しますが、指定した時間が経過するか、または特定のイベントがトリガされた際に、バックグラウンドでページを再生成し、最新のコンテンツに更新します。
メリット:
- SSGとSSRのいいとこ取り:高速な表示速度とSEO効果を保ちながら、コンテンツの鮮度を維持。
- 再ビルド不要
デメリット:
- 完全なリアルタイム性ではない:設定した時間が経過するまで、または再生成がトリガ―されるまで、更新が反映されないタイムラグがある。
用途:
- ブログサイトやニュースサイト、ECサイトの商品詳細ページなど、コンテンツが頻繁に更新されるが、必ずしもミリ単位のリアルタイム性は不要な場合。
- CMSと連携して、コンテンツが公開・更新された際に自動的にWebサイトを更新したい場合
- SSR( Dynamic Rendering / 動的レンダリング )
ユーザーがページをリクエストするたびに、サーバーが動的にHTMLを生成してクライアントに送信するレンダリング方法です。データベースからの最新データを取り込んで、完全に内容が詰まったHTMLを生成し、それをブラウザに届けます。
メリット:
- 常に最新の情報を表示できる:リクエストごとにデータを取得する
- SEOフレンドリー
- 初回表示が速い
デメリット:
- サーバー負担が高い:リクエストごとにHTMLを生成するため、アクセス数が増えると負担となる
用途:
- リアルタイム性が非常に重要で、常に最新のデータを表示したいページ
- ユーザーごとにパーソナライズされたコンテンツを表示するページ(例:ログイン後のダッシュボード)
クライアントコンポーネント
- CSR( Client-Side Rendering )
Webサイトの初期レンダリングをクライアント側で行う方法です。サーバーは最低限のHTMLと、ページを構築するためのJavaScriptを送信し、ブラウザでJavaScriptが実行されて初めてUIが表示されます。
メリット:
- 高いインタラクティブ性:ユーザー操作(クリック、入力など)に瞬時に反応するリッチなUIを構築しやすい。
- サーバー負担の軽減
- 動的なデータ表示:ユーザーの入力や状態に応じて、UIを柔軟に更新できる。
デメリット:
- 初回表示が遅い可能性
- SEOへの影響
- JavaScriptバンドルサイズ:大量のJavaScriptをクライアントに送る必要があるため、バンドルサイズが大きくなりがち。
用途:
- ログイン後の管理画面やダッシュボード、高度なユーザーインタラクションが求められるアプリケーション
- リアルタイムなチャットアプリやゲームなど、常にクライアント側でUIが更新され続けるもの
これら4種類のレンダリング方法がありますが、基本はサーバーコンポーネントでSSGやISRを活用して、高速な初期表示とSEOを確保するのがいいと思います。
実装方法
- SSGの実装
基本的にデフォルトでSSGでレンダリングされるので特にすることはありません。
しかし、例えばブログサイトの詳細ページ(/blog/[記事のタイトルslug])などの動的ページではSSGを設定する必要があります。
①動的ルートの作成
ブログ詳細ページのURLは/blog/記事のタイトルのように表示したいので、app/blog/[slug]/page.jsというフォルダとファイルを作成します。
②generateStaticParamsの追加
ビルド時に存在するすべてのブログ記事のslugのリストをNext.jsに教えてあげる必要があります。そこでgenerateStaticParams関数を使って全slugリストを取得します。
export async function generateStaticParams() {
const posts = await fetch(ここにurlなどを記載).then((response) => response.json());
//Next.jsには{params: {slug: '...'}}の配列の形式で返す
return posts.map((post) => ({
slug: post.slug,
}));
}
③データ取得の関数を定義
getBlogPostのような非同期関数を定義して、実際にslugに基づいて記事を取得します。
async function getBlogPost(slug) {
const res = await fetch(`url/${slug}`);
if(!res.ok) {
throw new Error("データの取得に失敗しました。")
}
return res.json();
}
このコード内のres.json()を返すことで、この中には、{ id, slug, title, content, category }などそのslugの全データを含んでいると思います。
④ページコンポーネントの定義
paramsを受け取って、そのslugの取得したデータを用いて、ページを作ります。
export default async function BlogPostPage({ params }) {
const { slug } = params;
const post = await getBlogPost(slug);
if(!post) {
return <div>記事が見つかりませんでした。</div>;
}
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
- ISRの実装
App Routerでは、ISRの設定は複数レベルで行うことができます。
①個々のfetchリクエストごとの設定
これは、特定のfetchリクエストに対して直接revalidateオプションを設定する方法です。
fetch関数の第二引数に{ next: { revalidate: 30 } }のように記述することで設定ができます。※30であれば30秒ごとに再生成します。
async function getSpecificData() {
const res = await fetch(url, {
next: {revalidate: 30 },
}):
return res.json();
}
②ルートセグメントレベルの設定
この設定は、主にルートセグメントレベル、つまり特定のpage.jsファイルやlayout.jsファイル内で使用する場合に有効です。
export const revalidate = 30という風にルート内全体で再生成の時間を設定します。
export const revalidate = 30;
async function getRouteData() {
const res = await fetch(url);
//revalidateは自動で30に設定される
return res.json();
}
あるページ内でデータフェッチが1つだけで、そのデータに対して一律の再検証頻度を設定したいのであれば、②ルートセグメントレベルの設定、で十分だと思います。
- SSRの実装
申し訳ありませんが、SSRはまだあまり使用した経験が少ないため一旦は未記載にします。後々、SSRを実装した際に更新しようと思います。
- CSRの実装
ファイルの先頭に "use client" をつけることでクライアントコンポーネントになります。
"use client"
参考: