server-side-rendering について
Web 開発者向けに、サーバーで HTML を生成する SSR の仕組みや利点・制約、React と Next.js での実装方法を解説します。SEO と早期表示を重視するページに適しています。
主な機能
- 01サーバーでページ HTML を生成
- 02クライアント描画に必要な JavaScript を削減
- 03React のハイドレーションに対応
- 04Next.js による SSR 実装を解説
活用シーン
- 01コンテンツページを検索エンジン向けに最適化
- 02コンテンツ量の多いページを早く表示
- 03React ページでサーバー描画とクライアント操作を組み合わせる
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ patternsdev/ skills --skill server-side-renderingインストール後、Claude Code で/server-side-renderingを入力して読み込まれたか確認します。
詳細
サーバーサイドレンダリング
目次
サーバーサイドレンダリング(SSR)は、Web コンテンツをレンダリングする最も古い手法の一つです。SSR は、ユーザーのリクエストに応じて、ページのコンテンツをレンダリングするための完全な HTML を生成します。このコンテンツには、データストアや外部 API からのデータが含まれる場合があります。
接続とデータ取得の処理はサーバー上で行われます。コンテンツのフォーマットに必要な HTML もサーバー上で生成されます。そのため、SSR ではデータ取得やテンプレート処理のために追加の往復通信を行う必要がありません。また、レンダリングコードはクライアント上で必要ないため、それに対応する JavaScript をクライアントに送信する必要もありません。
使用する場面
- SEO と高速な First Contentful Paint がアプリケーションにとって重要な場合に使用します
- ユーザーや検索エンジンにすばやく表示する必要がある、コンテンツの多いページに適しています
使用しない場面
- 静的レンダリング(SSG)で十分で、リクエストごとに発生するサーバーコストを避けられる、完全に静的なコンテンツの場合
- SEO が関係なく、CSR でよりシンプルなアーキテクチャを実現できる、社内向けのダッシュボードやツールの場合
- リクエストごとのサーバーサイドレンダリングの負荷が高すぎて、キャッシュも利用できない場合
手順
- SSR サポートが組み込まれた Next.js などのフレームワークを使用します
- Suspense をサポートするストリーミング SSR を利用するため、
renderToPipeableStream(React 18+)へのアップグレードを検討します - インタラクティブなページでは、SSR とクライアントサイドのハイドレーションを組み合わせます
- TTFB への影響に注意し、サーバーの応答時間を最適化してキャッシュを検討します
- SSR を補完し、クライアントサイドの JavaScript を削減する方法として、React Server Components を検討します
詳細
SSR ではリクエストごとに個別に処理され、サーバーによって新しいリクエストとして扱われます。連続する二つのリクエストの出力にほとんど違いがなくても、サーバーは毎回、一から処理して生成します。サーバーは複数のユーザーで共有されるため、ある時点でアクティブなすべてのユーザーが処理能力を共有します。
従来の SSR 実装
従来の SSR と JavaScript を使って、ページに現在時刻を表示し、更新する簡単な例を見てみましょう。
<!DOCTYPE html>
<html>
<head>
<title>Time</title>
</head>
<body>
<div>
<h1>Hello, world!</h1>
<b>It is <div id=currentTime></div></b>
</div>
</body>
</html>
function tick() {
var d = new Date();
var n = d.toLocaleTimeString();
document.getElementById("currentTime").innerHTML = n;
}
setInterval(tick, 1000);
同じ出力を提供する CSR のコードとは異なる点に注目してください。また、HTML はサーバーによってレンダリングされますが、ここに表示される時刻は、JavaScript 関数 tick() によって設定されたクライアントの現地時刻である点にも注意してください。サーバー固有の別のデータ、たとえばサーバー時刻を表示する場合は、レンダリング前に HTML に埋め込む必要があります。そのため、サーバーとの往復通信を行わない限り、自動的に更新されません。
長所と短所
レンダリングコードをサーバー上で実行し、JavaScript を削減することで、次のような利点があります。
JavaScript が少ないほど FCP と TTI が短縮されます
ページに複数の UI 要素やアプリケーションロジックが含まれる場合、SSR の JavaScript 量は CSR と比べて大幅に少なくなります。そのため、スクリプトの読み込みと処理にかかる時間が短縮されます。FP、FCP、TTI が短縮され、FCP と TTI が同じになります。SSR では、画面上のすべての要素が表示され、操作可能になるまでユーザーを待たせることがありません。
クライアントサイドの JavaScript に追加の予算を割けます
開発チームは、望ましいパフォーマンスを実現するため、ページ内の JavaScript 量を制限する JS バジェットに従う必要があります。SSR では、ページのレンダリングに必要な JavaScript を直接削減するため、アプリケーションに必要となる可能性があるサードパーティ製 JavaScript のために、追加の余地が生まれます。
SEO に対応できます
検索エンジンのクローラーは SSR アプリケーションのコンテンツを容易にクロールできるため、ページの検索エンジン最適化を高められます。
上記の利点から、SSR は静的コンテンツに適しています。しかし、いくつかの欠点もあるため、あらゆる状況に最適というわけではありません。
TTFB が遅い
すべての処理がサーバー上で行われるため、次のいずれかの状況が発生すると、サーバーからの応答が遅れる場合があります。
- 複数のユーザーが同時にアクセスし、サーバーに過剰な負荷がかかっている。
- ネットワークが遅い。
- サーバーのコードが最適化されていない。
一部の操作でページ全体の再読み込みが必要
すべてのコードがクライアント側にあるわけではないため、主要な操作を行うたびにサーバーとの往復通信が頻繁に発生し、ページ全体が再読み込みされます。ユーザーは操作の間に長く待つ必要があるため、操作間の時間が長くなる可能性があります。そのため、SSR ではシングルページアプリケーションを実現できません。
Next.js での SSR
Next.js フレームワークも SSR をサポートしています。リクエストごとにサーバー上でページを事前レンダリングします。ページから getServerSideProps() という非同期関数をエクスポートすることで実現できます。
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
};
}
コンテキストオブジェクトには、HTTP リクエストおよびレスポンスオブジェクト、ルーティングパラメーター、クエリー文字列、ロケールなどのキーが含まれます。
次の実装では、React を使ってフォーマットされたページにデータをレンダリングするための getServerSideProps() の使用方法を示します。
// data fetched from an external data source using `getServerSideProps`
const Users = ({ users, error }) => {
return (
<section>
<header>
<h1>List of users</h1>
</header>
{error && <div>There was an error.</div>}
{!error && users && (
<table>
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{users.map((user, key) => (
<tr key={key}>
<td>{user.username}</td>
<td>{user.email}</td>
<td>{user.name}</td>
</tr>
))}
</tbody>
</table>
)}
</section>
);
};
export async function getServerSideProps() {
try {
// Fetch data from external API
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
// Pass data to the page via props
return { props: { users: data, error: null } };
} catch (error) {
return { props: { users: null, error: true } };
}
}
export default Users;
サーバーでの React
React はアイソモーフィックにレンダリングできます。つまり、ブラウザーだけでなく、サーバーなどの他のプラットフォームでも動作します。そのため、React を使ってサーバー上で UI 要素をレンダリングできます。
React はユニバーサルコードと組み合わせて使用することもでき、同じコードを複数の環境で実行できます。これは、サーバーで Node.js を使用することで実現できます。
ReactDOMServer.renderToString(element);
この関数は、React 要素に対応する HTML 文字列を返します。その後、ページの読み込みを高速化するため、HTML をクライアントにレンダリングできます。
renderToString() 関数は hydrateRoot() と組み合わせて使用できます。これにより、サーバーでレンダリングされた HTML が保持され、クライアントでイベントハンドラーがアタッチされます。
これを実装するには、各ページに対応する .js ファイルをクライアントとサーバーの両方で使用します。サーバーの .js ファイルは HTML コンテンツをレンダリングし、クライアントの .js ファイルはそれをハイドレートします。
サーバーコード:
app.get("/", (req, res) => {
const app = ReactDOMServer.renderToString(<App />);
});
要素 App をハイドレートするためのクライアント側コード:
import { hydrateRoot } from "react-dom/client";
hydrateRoot(document.getElementById("root"), <App />);
React による SSR の完全な例はこちらで確認できます。