はじめに
Reactはクライアントサイドレンダリングを得意とするライブラリで、サーバーサイドレンダリング(SSR)も実装可能です。しかし、ReactでSSRを実装しようとするとたくさんの設定や追加のコードが必要になってきます。
そこで登場したのがNext.jsです。
Next.jsはReactをベースにしたフレームワークで、以下の特徴があります。
-
簡単なSSR実装
-
パフォーマンス最適化
-
ファイルベースのルーティング
-
静的サイト生成(SSG)とインクリメンタル静的再生成(ISR)
-
開発環境の向上
-
ビルトインのCSS/Sass支援
これらの機能により、Next.jsはReactのみを使用する場合と比べて、開発効率、パフォーマンス、SEOの面で大きな利点があり、そのため多くの実務プロジェクトではReactとNext.jsが一緒に使用されています。
つまり、React開発者にとってNext.jsの理解が非常に重要になっています。
そこで最初に初学者がぶち当たる壁がこのCSR/SSR/SSG/ISRという4つのレンダリング方法です。今回はこれらを頑張って図解してみましたので、React初学者の参考になればと思います。
※APIまで記載してますが、データをフェッチしない場合はServerとClient部分のみで理解すればOKです。
1. クライアントサイドレンダリング (CSR)
CSRでは、最初にブラウザが最小限のHTMLとJavaScriptを受け取り、その後JavaScriptがブラウザで実行されてページの内容を描画します。
特徴:
- 初期ロードが遅い
- SEOに不利
- インタラクティブ性が高い
- サーバー負荷が低い
-
初期ロード時:
- サーバーは最小限のHTML(通常は空の
<div>
)とJavaScriptバンドルを送信します。 - クライアントはJavaScriptをロードし、実行します。
- サーバーは最小限のHTML(通常は空の
-
レンダリング時:
- JavaScriptがAPIからデータをフェッチします。
- データを受け取った後、Reactコンポーネントがレンダリングされます。
- ページの内容が表示され、インタラクティブになります。
2. サーバーサイドレンダリング (SSR)
SSRではリクエストごとにサーバー上でページのHTMLが生成され、クライアントに送信されます。
特徴:
- 初期表示が速い
- SEOに有利
- サーバー負荷が高い
- ページが完全にインタラクティブになるまで遅くなる可能性がある
-
サーバーサイド:
- リクエストを受け取ると、サーバーはAPIからデータをフェッチします。
- フェッチしたデータを使用してReactコンポーネントをレンダリングし、完全なHTMLを生成します。
-
クライアントサイド:
- クライアントは完全なHTMLを受け取り、即座に表示します。
- その後、JavaScript/CSSファイルをロードし、Hydrationを行ってページをインタラクティブにします。
ここでCSRとSSRのデータフェッチAPIの違いのまとめ
CSRのデータフェッチ
CSRでは、ブラウザ上でJavaScriptを使用してデータをフェッチします。
1. useEffect フック(React)
Reactコンポーネント内でuseEffect
フックを使用してデータをフェッチします。
'use client';
import { useState, useEffect } from 'react';
export default function ClientComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
};
fetchData();
}, []);
if (!data) return <div>Loading...</div>;
return <div>{/* データを表示 */}</div>;
}
2. SWR(React向けデータフェッチライブラリ)
SWRを使用すると、キャッシュ、再検証、フォーカス追跡、インターバルでの再フェッチなどの機能を簡単に実装できます。
'use client';
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args).then(res => res.json());
export default function ClientComponent() {
const { data, error } = useSWR('https://api.example.com/data', fetcher);
if (error) return <div>Failed to load</div>;
if (!data) return <div>Loading...</div>;
return <div>{/* データを表示 */}</div>;
}
SSRのデータフェッチ
SSRでは、サーバー上でデータをフェッチし、レンダリングされたHTMLとともにクライアントに送信します。
1. サーバーコンポーネント内での非同期データフェッチ
Next.js 13以降では、サーバーコンポーネント内で直接非同期関数を使用してデータをフェッチできます。
// app/page.js
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
export default async function Page() {
const data = await getData();
return <main>{/* データを使用してコンテンツをレンダリング */}</main>;
}
2. fetch APIの拡張機能
Next.js 13以降では、組み込みのfetch
関数に追加のオプションが用意されています。
async function getData() {
const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } });
return res.json();
}
この例では、データを60秒ごとに再検証します。
3. 動的ルートでのデータフェッチ
動的ルートにおけるデータフェッチも、サーバーコンポーネント内で直接行えます。
// app/posts/[id]/page.js
async function getPost(id) {
const res = await fetch(`https://api.example.com/posts/${id}`);
return res.json();
}
export default async function Post({ params }) {
const post = await getPost(params.id);
return <div>{/* 投稿データを表示 */}</div>;
}
他の主な違い
- 実行環境: CSRはブラウザで、SSRはサーバーで実行されます。
- 初期ロード時間: SSRは初期HTML内にデータを含めるため、通常CSRより初期ロードが速いです。
- SEO: SSRはデータを含むHTMLを提供するため、SEOに有利です。
- インタラクティブ性: CSRはデータ更新時の再レンダリングが容易です。
- サーバー負荷: SSRはサーバーでのデータフェッチと処理が必要なため、サーバー負荷が高くなる可能性があります。
- 実装の複雑さ: CSRは通常、実装がより直感的です。SSRは状態管理やハイドレーションの考慮が必要です。
適切なアプローチの選択は、アプリケーションの要件、パフォーマンスの目標、SEOの重要性などに応じて決定します。
3. 静的サイト生成 (SSG)
SSGはビルド時にページのHTMLを生成し、その後のリクエストに対してはあらかじめ生成された静的ファイルを提供します。静的なファイルになりますのでSSGだけはデータのフェッチは行いません。
特徴:
- 非常に高速な表示
- SEOに最適
- 動的コンテンツには不向き
-
ビルド時:
-
generateStaticParams
関数が実行され、生成すべき静的ページのパラメータが決定されます。 - 各パラメータに対して静的なHTMLファイルが生成されます。
- 生成された静的ファイル(HTML、JS、CSS)がCDNにデプロイされます。
-
-
クライアントリクエスト時:
- クライアントがページをリクエストすると、CDNは事前に生成された静的HTMLを即座に返します。
- クライアントはHTMLをレンダリングし、必要なJS/CSSファイルを追加でリクエストします。
- JS/CSSファイルを受け取った後、クライアント側でHydrationが行われ、ページが完全にインタラクティブになります。
4. インクリメンタル静的再生成 (ISR)
ISRはSSGの利点を維持しつつ、定期的にページを再生成する方法です。これにより、静的サイトの高速性と動的コンテンツの新鮮さを両立できます。
特徴:
- SSGの利点を維持しつつ、定期的に更新可能
- キャッシュと最新性のバランスが取れる
- 大規模サイトに適している
-
初期ビルド時:
- SSGと同様に静的ページが生成され、CDNにデプロイされます。
-
リクエスト時:
- クライアントがページをリクエストすると、CDNはキャッシュされたHTMLを返します。
- キャッシュが古い場合、サーバーでページが再生成され、新しいHTMLが返されます。
-
バックグラウンド更新:
- 設定された間隔で、サーバーがページを再生成し、CDNのキャッシュを更新します。
まとめ
各レンダリング方法には長所と短所があります。アプリケーションの要件、更新頻度、パフォーマンス要件、SEOの重要性などを考慮して、最適な方法を選択しましょう。Next.jsの柔軟性により、1つのアプリケーション内で複数のレンダリング方法を組み合わせることも可能です。
レンダリング方法と最新のデータフェッチング
Next.jsではApp Router以降、getStaticPaths
の廃止とgenerateStaticParams
が導入され、さらにgetStaticProps
, getServerSideProps
は廃止されて、データベースへの直接アクセスや、JavaScriptのfetch()でデータ取得が行えるようになりました。
この章では各種レンダリング方法とデータのフェッチについて解説していきます。
各レンダリング時におけるデータフェッチについて
もう一度おさらいしますが、レンダリング方法は下記の4つです
- クライアントサイドレンダリング (CSR)
- サーバーサイドレンダリング (SSR)
- 静的サイト生成 (SSG)
- インクリメンタル静的再生成 (ISR)
新しいデータフェッチ方法
そして先に述べた通り、App Routerでは、getStaticProps
、getServerSideProps
、getStaticPaths
が廃止され、新しいデータフェッチ方法が導入されました。
これらを踏まえて、各レンダリング時におけるデータフェッチ方法をコードベースで見ていきましょう。
※最も基本的な例を紹介しています。やり方はたくさんあるので、初学者はまず基本を頭にいれておきましょう。
1. クライアントサイドレンダリング (CSR)
クライアントサイドでのデータフェッチには、Reactの useState
と useEffect
フックを使用します。
'use client'
import { useState, useEffect } from 'react'
function ClientComponent() {
const [data, setData] = useState(null)
useEffect(() => {
async function fetchData() {
const res = await fetch('/api/data')
const json = await res.json()
setData(json)
}
fetchData()
}, [])
if (!data) return <div>Loading...</div>
return <div>{data.content}</div>
}
export default ClientComponent
2. サーバーコンポーネントでのデータフェッチ(SSR)
サーバーコンポーネントでは、直接非同期関数を使用してデータをフェッチできます。
async function Page() {
const res = await fetch('https://api.example.com/data')
const data = await res.json()
return <div>{data.content}</div>
}
export default Page
この方法はデフォルトでSSRとして動作します。
3. 静的サイト生成 (SSG)
静的生成を行うには、ページコンポーネントに generateStaticParams
関数を追加します。
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({
slug: post.slug,
}))
}
async function Page({ params }) {
const post = await getPost(params.slug)
return <div>{post.content}</div>
}
export default Page
4. インクリメンタル静的再生成 (ISR)
ISRを実装するにはfetch
関数に revalidate
オプションを追加します。
このrevalidate
に指定した時間を経過すると再ビルドが行われ、最新のHTMLが返されます。
async function Page() {
const res = await fetch('<https://api.example.com/data>', { next: { revalidate: 60 } })
const data = await res.json()
return <div>{data.content}</div>
}
export default Page
パフォーマンスの比較
各レンダリング方法のパフォーマンス特性
初期ロード時間: ページを開いてから全て表示されるまでの時間
TTFB (Time to First Byte): サーバーからの最初の応答が届くまでの時間
FCP (First Contentful Paint): 画面に最初の内容が表示されるまでの時間
TTI (Time to Interactive): ページが完全に操作可能になるまでの時間
【補足】 選択の基準
適切なレンダリング方法を選択する際の考慮点
-
コンテンツの更新頻度
→ ウェブサイトの内容がどのくらい頻繁に変わるか
例:ニュースサイトは毎日更新されますが、会社の紹介ページはめったに変わりません。 -
SEOの重要性
→ ウェブサイトが検索エンジンで見つけやすいかどうかの重要度 -
ユーザーインタラクションの複雑さ
→ ウェブサイト上でユーザーがどれだけ多くの操作をするか
例:SNSアプリは投稿、いいね、コメントなど多くの操作がありますが、ブログは読むだけです。 -
サーバーリソースの制約
→ ウェブサイトを動かすためのコンピューターの能力や予算の限界 -
ターゲットユーザーのネットワーク環境
→ ウェブサイトを見る人たちのインターネット接続の速さや安定性
。
Next.js App Routerの強みは、これらの方法を柔軟に組み合わせられることです。一つのアプリケーション内で、ページやコンポーネントごとに最適なレンダリング方法を選択できます。
おわりに
図解部分の細かな箇所がミスっているかもしれませんので、間違いがあればコメントいただけると幸いです