
Linkコンポーネントによる画面遷移
はじめに 前回までにNext.jsのページコンポーネント作成方法を学びました。今回は、作成したページ間をシームレスに移動するための「Linkコンポーネント」につ […]
前回は静的生成(SSG)について学びましたが、今回は「サーバーサイドレンダリング(SSR)」について詳しく解説します。SSRはユーザーがページをリクエストするたびにサーバー側でHTMLを生成する手法で、動的なデータを扱う場合に特に有効です。
SSR(Server-Side Rendering)は、ユーザーのリクエストごとにサーバーでページを生成する手法です。主な特徴は:
ユーザーがページをリクエスト
↓
サーバーでデータ取得(getServerSideProps)
↓
サーバーでHTML生成
↓
生成したHTMLをクライアントに送信
↓
クライアントでHydration(インタラクティブ化)
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オブジェクトを受け取り、以下の情報を取得できます:
params
: 動的ルートのパラメータreq
: HTTPリクエストオブジェクト(クッキー、ヘッダーなど)res
: HTTPレスポンスオブジェクトquery
: クエリ文字列preview
: プレビューモードかどうかpreviewData
: プレビュー用データresolvedUrl
: 解決されたURLlocale
: 国際化用のロケールSSRはサーバー負荷が高いため、可能な限りキャッシュを活用しましょう。
export async function getServerSideProps({ req, res }) {
// キャッシュ設定(1時間)
res.setHeader(
'Cache-Control',
'public, s-maxage=3600, stale-while-revalidate=3600'
);
// データ取得処理...
}
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,
// };
}
}
export async function getServerSideProps(context) {
// クエリパラメータから必要な情報だけ取得
const { page = '1' } = context.query;
const res = await fetch(`https://api.example.com/data?page=${page}`);
// ...
}
特徴 | SSR | SSG |
---|---|---|
生成タイミング | リクエストごと | ビルド時 |
データ鮮度 | 常に最新 | ビルド時のデータ |
パフォーマンス | サーバー負荷が高い | 超高速 |
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;
// 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;
export async function getServerSideProps(context) {
// クッキーから認証トークンを取得
const token = context.req.cookies.token;
if (!token) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
// トークンを使ってAPI呼び出し
// ...
}
export async function getServerSideProps() {
// 条件に応じてリダイレクト
return {
redirect: {
destination: '/new-location',
permanent: false, // 永続的かどうか(SEOに影響)
},
};
}
export async function getServerSideProps() {
// データが存在しない場合
return {
notFound: true,
};
}
const [user, orders, notifications] = await Promise.all([
fetchUser(),
fetchOrders(),
fetchNotifications(),
]);
// 必要なフィールドだけ指定
const res = await fetch('https://api.example.com/data?fields=id,name,image');
res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=120');
Next.jsのサーバーサイドレンダリング(SSR)について学びました。主なポイントは:
getServerSideProps
を使用してデータ取得SSRは強力な機能ですが、必ずしもすべてのページで必要というわけではありません。静的生成(SSG)やクライアントサイドデータ取得と組み合わせて、適材適所で使い分けることが重要です。
次回はNext.jsの画像最適化機能について詳しく解説します。next/image
コンポーネントを使用すると、簡単に最適化された画像を表示できます。
Next.js公式ドキュメントも参考にしてください:
https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props