Next.jsサーバーサイドレンダリング(SSR)の基本

2025-08-13

はじめに

前回は静的生成(SSG)について学びましたが、今回は「サーバーサイドレンダリング(SSR)」について詳しく解説します。SSRはユーザーがページをリクエストするたびにサーバー側でHTMLを生成する手法で、動的なデータを扱う場合に特に有効です。

サーバーサイドレンダリング(SSR)とは?

SSR(Server-Side Rendering)は、ユーザーのリクエストごとにサーバーでページを生成する手法です。主な特徴は:

  • リクエスト時に最新のデータでページを生成
  • 常に最新のコンテンツを表示可能
  • SEOに有利(検索エンジンに完全なHTMLを提供)
  • 初期表示が速い(クライアントサイドでレンダリングする必要がない)
  • サーバー負荷が高い(SSGと比較して)

SSRの基本的な仕組み

ユーザーがページをリクエスト
↓
サーバーでデータ取得(getServerSideProps)
↓
サーバーでHTML生成
↓
生成したHTMLをクライアントに送信
↓
クライアントでHydration(インタラクティブ化)

getServerSidePropsの基本

SSRを実現するには、getServerSideProps関数を使用します。この関数は毎回のリクエストごとにサーバーで実行されます。

基本的な実装例

// pages/user-profile.js
function UserProfile({ user }) {
  return (
    <div>
      <h1>ユーザープロファイル</h1>
      <p>名前: {user.name}</p>
      <p>メール: {user.email}</p>
    </div>
  );
}

// リクエストごとに実行
export async function getServerSideProps(context) {
  // contextには以下の情報が含まれます
  // - params: 動的ルートのパラメータ
  // - req: HTTPリクエストオブジェクト
  // - res: HTTPレスポンスオブジェクト
  // - query: クエリ文字列

  // 認証情報をリクエストヘッダーから取得
  const token = context.req.headers.cookie?.replace('token=', '');

  // APIからユーザーデータを取得
  const res = await fetch('https://api.example.com/user', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  const user = await res.json();

  return {
    props: {
      user,
    },
  };
}

export default UserProfile;

getServerSidePropsのcontextオブジェクト

getServerSidePropsはcontextオブジェクトを受け取り、以下の情報を取得できます:

  • params: 動的ルートのパラメータ
  • req: HTTPリクエストオブジェクト(クッキー、ヘッダーなど)
  • res: HTTPレスポンスオブジェクト
  • query: クエリ文字列
  • preview: プレビューモードかどうか
  • previewData: プレビュー用データ
  • resolvedUrl: 解決されたURL
  • locale: 国際化用のロケール

SSRの使用が適しているケース

  1. 常に最新データが必要なページ
  • 株価表示
  • 天気情報
  • リアルタイムチャート
  1. ユーザーごとに内容が変わるページ
  • ダッシュボード
  • ユーザープロファイル
  • パーソナライズされた推薦
  1. 認証が必要なページ
  • 会員限定コンテンツ
  • 管理画面
  1. SEOが必要でかつ頻繁に更新されるページ
  • ニュースサイト
  • 商品在庫情報

SSRのベストプラクティス

1. キャッシュの活用

SSRはサーバー負荷が高いため、可能な限りキャッシュを活用しましょう。

export async function getServerSideProps({ req, res }) {
  // キャッシュ設定(1時間)
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=3600, stale-while-revalidate=3600'
  );

  // データ取得処理...
}

2. エラーハンドリング

export async function getServerSideProps() {
  try {
    const res = await fetch('https://api.example.com/data');

    if (!res.ok) {
      throw new Error('データ取得に失敗しました');
    }

    const data = await res.json();

    return {
      props: {
        data,
      },
    };
  } catch (error) {
    // エラーページにリダイレクト
    return {
      redirect: {
        destination: '/error',
        permanent: false,
      },
    };

    // またはエラーステータスを返す
    // return {
    //   notFound: true,
    // };
  }
}

3. 必要なデータのみ取得

export async function getServerSideProps(context) {
  // クエリパラメータから必要な情報だけ取得
  const { page = '1' } = context.query;

  const res = await fetch(`https://api.example.com/data?page=${page}`);
  // ...
}

SSRとSSGの比較

特徴SSRSSG
生成タイミングリクエストごとビルド時
データ鮮度常に最新ビルド時のデータ
パフォーマンスサーバー負荷が高い超高速
SEO優れている優れている
ユースケース頻繁に更新/ユーザーごとに変更変更頻度が低いコンテンツ

実際のプロジェクトでの使用例

認証付きダッシュボードページ

// pages/dashboard.js
import { getSession } from 'next-auth/react';

function Dashboard({ user, recentActivities }) {
  return (
    <div>
      <h1>ようこそ、{user.name}さん</h1>
      <h2>最近のアクティビティ</h2>
      <ul>
        {recentActivities.map((activity) => (
          <li key={activity.id}>{activity.description}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getServerSideProps(context) {
  // セッションを確認
  const session = await getSession(context);

  // 未認証の場合はログインページへリダイレクト
  if (!session) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    };
  }

  // ユーザーに応じたデータを取得
  const [userRes, activitiesRes] = await Promise.all([
    fetch(`https://api.example.com/users/${session.user.id}`),
    fetch(`https://api.example.com/activities?userId=${session.user.id}&limit=5`),
  ]);

  const [user, recentActivities] = await Promise.all([
    userRes.json(),
    activitiesRes.json(),
  ]);

  return {
    props: {
      user,
      recentActivities,
    },
  };
}

export default Dashboard;

動的なEコマース商品ページ

// pages/products/[id].js
function Product({ product, recommendations }) {
  if (!product) {
    return <div>商品が見つかりませんでした</div>;
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <p>価格: {product.price}円</p>
      <p>在庫: {product.stock > 0 ? 'あり' : '売り切れ'}</p>

      <h2>おすすめ商品</h2>
      <div className="recommendations">
        {recommendations.map((item) => (
          <ProductCard key={item.id} product={item} />
        ))}
      </div>
    </div>
  );
}

export async function getServerSideProps(context) {
  const { id } = context.params;

  try {
    const [productRes, recRes] = await Promise.all([
      fetch(`https://api.example.com/products/${id}`),
      fetch(`https://api.example.com/products/${id}/recommendations`),
    ]);

    // 商品が存在しない場合
    if (productRes.status === 404) {
      return {
        notFound: true,
      };
    }

    const [product, recommendations] = await Promise.all([
      productRes.json(),
      recRes.json(),
    ]);

    return {
      props: {
        product,
        recommendations,
      },
    };
  } catch (error) {
    console.error('Error fetching product data:', error);
    return {
      redirect: {
        destination: '/error',
        permanent: false,
      },
    };
  }
}

export default Product;

よくある質問とトラブルシューティング

Q1: SSRでクッキーを扱うには?

export async function getServerSideProps(context) {
  // クッキーから認証トークンを取得
  const token = context.req.cookies.token;

  if (!token) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    };
  }

  // トークンを使ってAPI呼び出し
  // ...
}

Q2: SSRでリダイレクトする方法は?

export async function getServerSideProps() {
  // 条件に応じてリダイレクト
  return {
    redirect: {
      destination: '/new-location',
      permanent: false, // 永続的かどうか(SEOに影響)
    },
  };
}

Q3: SSRで404ページを表示するには?

export async function getServerSideProps() {
  // データが存在しない場合
  return {
    notFound: true,
  };
}

パフォーマンス最適化のヒント

  1. データ取得の並列化:
   const [user, orders, notifications] = await Promise.all([
     fetchUser(),
     fetchOrders(),
     fetchNotifications(),
   ]);
  1. 必要なデータのみ取得:
   // 必要なフィールドだけ指定
   const res = await fetch('https://api.example.com/data?fields=id,name,image');
  1. CDNキャッシュの活用:
   res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=120');

まとめ

Next.jsのサーバーサイドレンダリング(SSR)について学びました。主なポイントは:

  1. SSRはリクエストごとにサーバーでページを生成
  2. getServerSidePropsを使用してデータ取得
  3. 常に最新データが必要なページや認証ページに適している
  4. サーバー負荷が高いため、キャッシュや最適化が重要
  5. クライアントサイドのインタラクティブ性も維持可能

SSRは強力な機能ですが、必ずしもすべてのページで必要というわけではありません。静的生成(SSG)やクライアントサイドデータ取得と組み合わせて、適材適所で使い分けることが重要です。

次回はNext.jsの画像最適化機能について詳しく解説します。next/imageコンポーネントを使用すると、簡単に最適化された画像を表示できます。

Next.js公式ドキュメントも参考にしてください:
https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props