Server Componentsについて、あまりキャッチアップできていなかったので勉強を兼ねて記事を書きます!
Next.js Server Components (RSC) は、より高速で効率的、そして安全な現代的なWebアプリケーションを構築する新しい方法です。JavaScriptバンドルサイズを削減し、初期ロード時間を改善します。
この記事では以下を学習します:
- Server Componentsとは何か
- ServerコンポーネントとClientコンポーネントをいつ使うか
- Server ComponentsとServer-Side Rendering(SSR)の違い
- Server ComponentsがCDNとどのように連携するか
- Server FunctionsとActionsがデータハンドリングを改善する方法
📦 Server Componentsとは?
Server Componentsは、サーバー上でのみ実行されるReactコンポーネントです。ブラウザには送信されません。
これはどういう意味でしょうか?
- ブラウザはコンポーネントのコードをダウンロードしません
- レンダリングされたHTMLまたはデータのみがブラウザに送信されます
- これにより、アプリケーションがより高速で安全になります
✅ Server Componentsの利点
クライアントサイドレンダリング(CSR)の問題点
CSRでは:
- JavaScriptバンドルが大きくなります
- ブラウザは大量のJavaScriptをダウンロード、解析、実行する必要があります
- 初期表示(FCP:First Contentful Paint)が遅くなることがよくあります
Server Componentsが優れている理由
- ✅ 小さなJavaScriptバンドル: Server Componentsはクライアントバンドルに含まれません
- ✅ 高速なFCP: JavaScript量が少ない = 初期ロードが高速
- ✅ プログレッシブレンダリング: 他の部分がまだロード中でもコンテンツが表示開始されます
- ✅ 安全なデータフェッチング: サーバー上でAPIキーやデータベース呼び出しを安全に使用できます
- ✅ CDNキャッシュ: レンダリングされた出力をCDNに保存して超高速配信が可能
🌐 Server ComponentsとCDN
Server Componentsの最大の利点の一つは、レンダリング結果をCDNにキャッシュできることです。
動作の仕組み:
- Server Componentsはサーバー上で実行され、HTMLまたはRSC Payload(Reactの特別な形式)を生成します
- この出力はCDN(Content Delivery Network)にアップロードできます
- CDNは最も近いエッジロケーションからユーザーにコンテンツを配信します
ブラウザが受け取るもの:
- ブラウザは最終的なHTMLまたはRSC Payloadのみを受け取ります
- 元のServer Componentのロジックは決してクライアントに送信されません
これが素晴らしい理由:
- ⚡ 静的サイトのような高速ロード速度
- 🔒 セキュア: APIキーと秘密情報はサーバーに留まります
- 📦 サーバーロジックが含まれないため軽量バンドル
🖥️ Server Components vs Client Components
機能 | Server Components | Client Components |
---|---|---|
実行場所 | サーバー | ブラウザ |
JavaScriptバンドル | 含まれない | 含まれる |
使用例 | データフェッチング、セキュアなAPI呼び出し | UI相互作用、状態管理、ブラウザAPI |
例 | Markdownレンダリング、データベースフェッチ | クリック処理、フォーム入力 |
Client Componentsを使用する場合:
- 状態管理(例:
useState
、onClick
) - ライフサイクルフック(例:
useEffect
) - ブラウザAPI(例:
window
、localStorage
)
Server Componentsを使用する場合:
- データベース/APIフェッチング
- セキュアなデータハンドリング(APIキー、トークン)
- 小さなバンドルと高速な初期ペイント(FCP)
🛠️ Server Components vs Server-Side Rendering (SSR)
機能 | Server Components | Server-Side Rendering (SSR) |
---|---|---|
レンダリング | サーバー、RSC Payloadとしてストリーミング | サーバー、完全なHTML |
JavaScript | Client Componentsのみ送信 | 完全なJavaScriptを送信 |
初期HTML | 高速プレビュー + プレースホルダー | 完全にレンダリング済み |
目的 | JSを最小化、ロード速度向上 | ページの完全なハイドレーション |
主な違い:
Server ComponentsはJavaScriptバンドルの削減とストリーミングの改善に焦点を当てています。SSRは完全なHTMLの配信とすべてのハイドレーションに焦点を当てています。
🔍 例:Server ComponentsでのMarkdownレンダリング
import marked from 'marked'; // ブラウザには送信されない
import sanitizeHtml from 'sanitize-html'; // ブラウザには送信されない
async function Page({page}) {
const content = await file.readFile(`${page}.md`);
return <div>{sanitizeHtml(marked(content))}</div>;
}
✔️ markedやsanitize-htmlなどの重いライブラリはサーバーに留まります
✔️ クライアントは最終的なHTMLのみを受け取ります
📡 例:Server Componentsでのデータフェッチング
import db from './database';
async function Note({id}) {
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note.content}</p>
</div>
);
}
async function Author({id}) {
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
✔️ データは直接サーバー上でフェッチされます
✔️ ブラウザはレンダリングされた結果のみを見ます
⏳ 非同期Server Componentsとストリーミング
Server Componentsは直接async/awaitをサポートします。
// Server Component
async function Page({id}) {
const note = await db.notes.get(id);
const commentsPromise = db.comments.get(note.id);
return (
<div>
{note.content}
<Suspense fallback={<p>コメントを読み込み中...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</div>
);
}
// Client Component
"use client";
import {use} from 'react';
function Comments({commentsPromise}) {
const comments = use(commentsPromise);
return comments.map(comment => <p>{comment}</p>);
}
✔️ 高優先度のデータ(ノートなど)が最初にフェッチされます
✔️ 低優先度のデータ(コメントなど)は後でロードでき、体感速度が向上します
⚙️ Server FunctionsとActions
Server Functionsを使用すると、Client Componentsからサーバー上で実行される関数を呼び出せます。
// Server Component
import Button from './Button';
function EmptyNote() {
async function createNoteAction() {
"use server";
await db.notes.create();
}
return <Button onClick={createNoteAction} />;
}
例:Client Actions
// actions.js
"use server";
export async function updateName(name) {
if (!name) return {error: '名前は必須です'};
await db.users.updateName(name);
}
// Client Component
"use client";
import {updateName} from './actions';
function UpdateName() {
const [name, setName] = useState('');
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const submitAction = async () => {
startTransition(async () => {
const {error} = await updateName(name);
if (error) setError(error);
else setName('');
});
};
return (
<form action={submitAction}>
<input type="text" name="name" disabled={isPending} />
{error && <span>失敗: {error}</span>}
</form>
);
}
✔️ サーバーロジックはサーバーに留まります
✔️ クライアントは秘密情報を公開することなく、これらの関数を安全にトリガーできます
⚡ useActionStateによる簡単なServer呼び出し
useActionStateを使用してServer Function呼び出しを簡略化できます。
"use client";
import {updateName} from './actions';
function UpdateName() {
const [state, submitAction, isPending] = useActionState(updateName, {error: null});
return (
<form action={submitAction}>
<input type="text" name="name" disabled={isPending}/>
{state.error && <span>失敗: {state.error}</span>}
</form>
);
}
✔️ よりクリーンな構文
✔️ 内蔵のロードとレスポンス管理
まとめ
機能 | 目的 |
---|---|
Server Components | サーバー上で実行、JS削減、セキュアなデータ、CDNキャッシュ |
Client Components | UI、状態管理、ブラウザAPIの処理 |
Server Functions | クライアントからトリガーされるサーバーサイドロジック |
Actions | サーバー呼び出しとロード状態の安全な管理 |