Next.jsでReactを強化!SSRとRSCの基礎を学ぶ
最近Reactを勉強し始めて、ようやくアプリを一つ作成できるようになりました。そんな時に最近よく聞く単語「SSRって何?」「Next.jsって必要なの?」「RSC??」そんな疑問を最近感じ改めて調べることにしました
Web開発で感じていた課題
あるWebサービスを作ることになり、Reactを使って開発を始めましたが、いくつかの問題に直面します:
- 表示が遅い!
// よくある従来のReactコンポーネント
function ProductList() {
const [products, setProducts] = useState<Product[]>([]);
useEffect(() => {
// ユーザーは以下の3ステップを待たないといけない...
// 1. JavaScriptのダウンロード
// 2. Reactの起動
// 3. データの取得
fetch('/api/products').then(res => res.json()).then(setProducts);
}, []);
return <div>{/* ... */}</div>;
}
-
Googleから見つかりにくい!
- なぜなら、最初はからのページしかないから...
- JavaScriptを実行しないとコンテンツが見えない...
-
コードが複雑になっていく!
- データ取得のコードがあちこちに散らばる
- エラー処理が大変
- パフォーマンスの改善が難しい
レンダリング方式をSSRに
SSR(サーバーサイドレンダリング)は、「サーバーで完成したページを作って送る」という考え方です。
CSRとSSRの違い
Next.jsで解決
Next.jsって何?
Next.jsは、ReactをベースにしたフルスタックWebフレームワークです。シンプルなReactよりも多くの機能を提供し、モダンなWebアプリケーション開発をサポートします。
Next.jsの主な特徴
- フォルダとファイル構造に基づいて自動的にルートが生成される
- APIエンドポイントを簡単に作成できる
- 画像、フォント、スクリプトの最適化
Next.jsとViteの違い
以前React Viteでの実装方法について紹介しました。
Viteは高速な開発環境を提供するビルドツールであるのに対し、Next.jsはフルスタックのフレームワークです。主な違いは以下の通りです:目的の違い
Vite: 主に開発サーバーとビルドツールとして機能し、高速な開発体験を提供します。
Next.js: ルーティング、SSR、SSG、APIルートなど、アプリケーション全体の機能を提供するフレームワークです。
レンダリング戦略
Vite: 基本的にはクライアントサイドレンダリング(CSR)向けのツールで、SSRには追加の設定が必要です。
Next.js: SSR、SSG、CSR、ISRなど、様々なレンダリング方法を簡単に実装できます。
ファイル構造とルーティング
Vite: ルーティングは別途ライブラリ(react-router-domなど)を導入する必要があります。
Next.js: ファイルシステムベースのルーティングが組み込まれており、ファイル構造がそのままURLパスになります。
用途に応じた選択
Viteを選ぶとき:
既存のReactアプリの開発環境を高速化したい
シンプルなSPAを作成したい
特定のフレームワークに縛られたくない
Next.jsを選ぶとき:
SEO対策が重要なWebサイトを作る
サーバーサイドの機能も含めた完全なWebアプリを構築したい
フォルダ構造によるルーティングを活用したい
React Server Components(RSC)を使いたい
RSC(React Server Components)って何?
RSCは「サーバーで動くReactコンポーネント」です。React Server Componentsは、Next.js 13から導入された新しいReactの機能です。これまでのReactは基本的にブラウザで動いていましたが、RSCはサーバーで動きます。
RSCの利点
-
データアクセスの簡素化
サーバー上で直接データベースやAPIにアクセスできる
クライアントとサーバー間の往復が減る -
バンドルサイズの削減
サーバーコンポーネントのコードはクライアントに送信されない
クライアントが受け取るJavaScriptが少なくなる -
セキュリティの向上
重要なデータやAPIキーをクライアントに公開せずに済む
まずはプロジェクトを作ってみる
npx create-next-app@latest my-nextjs-app
cd my-nextjs-app
npm run dev
ファイル構造
my-nextjs-app/
├── app/ # ここにページを作っていきます
│ ├── page.tsx # トップページ
│ └── about/ # aboutページのフォルダ
│ └── page.tsx # aboutページ
├── components/ # 再利用するパーツ
└── public/ # 画像などの静的ファイル
サーバーコンポーネントとクライアントコンポーネント
Next.jsのApp Routerでは、デフォルトですべてのコンポーネントがサーバーコンポーネントになります。
サーバーコンポーネントの例
import { Post } from '@/types/blog';
async function BlogPage() {
// サーバー上で直接データを取得
const posts: Post[] = await db.posts.findMany();
return (
<div className="max-w-4xl mx-auto py-8">
<h1>ブログ記事一覧</h1>
<div className="grid gap-6">
{posts.map((post: Post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content.substring(0, 100)}...</p>
</article>
))}
</div>
</div>
);
}
export default BlogPage;
クライアントコンポーネントの例
'use client'; // この指示子でクライアントコンポーネントを宣言
import { useState } from 'react';
interface LikeButtonProps {
postId: string;
}
function LikeButton({ postId }: LikeButtonProps) {
const [likes, setLikes] = useState<number>(0);
return (
<button onClick={() => setLikes(likes + 1)}>
いいね! ({likes})
</button>
);
}
export default LikeButton;
動的ルーティングを使ったページ
// app/blog/[id]/page.tsx
async function BlogPost({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
return (
<article>
<h1>{post.title}</h1>
<Content content={post.content} />
{/* クリックイベントが必要な部分だけクライアントコンポーネントに */}
<LikeButton postId={post.id} />
</article>
);
}
ボタンクリックとかはどうするの?
ユーザーの操作が必要な部分は、従来通りクライアントコンポーネントとして書きます:
// components/LikeButton.tsx
'use client'; // これでクライアントコンポーネントになる
function LikeButton({ postId }: { postId: string }) {
const [likes, setLikes] = useState(0);
return (
<button onClick={() => setLikes(likes + 1)}>
いいね! ({likes})
</button>
);
}
まとめ
-
Next.jsの便利なところ
- ルーティングが簡単
- SSRが簡単
- 画像最適化も簡単
-
SSRで表示速度が爆速
- ユーザーの待ち時間を減らせる
- SEOも改善できる
-
RSCの考え方
- サーバーでできることはサーバーで
- クライアントは必要な部分だけに
Discussion