Next.jsでのレンダリングを理解してSSRを効果的に活用する
この記事ではNext.jsでのレンダリングについてまとめ、実際にNext.jsでSCR、SSRを実装します。
- CSRとSSRについて
- Next.jsのルーティングとコンポーネントについて
- Next.jsでSSRを実装する
の順に説明します。
目次
- CSRとSSR
- PageRouterとAppRunter
- サーバーコンポーネントとクライアントコンポーネント
- When to use Server and Client Components?
- Next.jsでCSRとSSRを実装する
- CSRの実装
- SSRの実装
- getServerSidePropsで実装
- サーバーコンポーネントによるSSRの実装
- まとめ
CSRとSSR
まず前提としてCSR(Client Side Rendering)とSSR(Server Side Rendering)についてあまりわからないという方は、他にわかりやすくまとめている方がいるので、下記の記事を参考にしてください。
Page RouterとApp Router
Next.jsではアプリケーション作成時にルーティング方法を選択できます。
最新バージョン(Next14)での公式のおすすめはApp Runterです。
どちらもファイルシステムベースルーティングなので、該当フォルダにコンポーネントを作成するだけでNextがいい感じにルーティングしてくれます。
Page Router
src/pages フォルダ配下にファイルやフォルダを追加することで、ルーティングが定義されます。
- getStaticPropsやgetServerSideProps等でCSR,SSRを実現します。(後述します)
App Router
- App RunterはNext13から導入されました。
- Pages Router とは違い、ルーティングに利用するファイル名には、
page
をつける必要があります。 - 原則として、 <span style="color:#d70910;">全てのReactコンポーネントをサーバー側でレンダリングします。</span>(デフォルトがサーバーコンポーネント)(次のセクションで説明します)
- getStaticPropsやgetServerSidePropsが使えなくなります。
サーバーコンポーネントとクライアントコンポーネント
Next.js は、サーバーサイドレンダリング (SSR) とクライアントサイドレンダリング (CSR) をサポートする React フレームワークです。つまり、どのコンポーネントがサーバーでレンダリングされ、どのコンポーネントがクライアントでレンダリングされるかを適切に選択することが重要になります。
ここではNext13から導入されたサーバーコンポーネントとクライアントコンポーネントの概念について説明します。
サーバーコンポーネント
- サーバーコンポーネントは、ページの初期ロード時にサーバー上でレンダリングされます。(SSR)
- SEO最適化や初期ページロードのパフォーマンス改善に有効です。
- コンポーネントを返す関数自体を非同期関数として扱うことが可能になりました。
- コンポーネント内でデータの取得を行ったり、コンポーネント内の処理が完了する前に描画を行うといったことができるようになりました。
- Next12でもサーバー側でレンダリングする(プリレンダリング)方式が取られていたのですが、コンポーネントのJSコードは、サーバーでプリレンダリング時に実行された後、必ずクライアントで再度実行されていました。サーバーコンポーネントではJSをクライアントで実行せず、クライアントではHTMLとCSSのみとなるコンポーネントとして扱うことができます。
-
useState
,useEffect
等のHooksは使えません。
使用例:
- ブログの記事: SEOに敏感なコンテンツであり、初期ページロード時にすべての内容が利用可能であるべきです。サーバーコンポーネントが適してると言えます。
- 静的なランディングページ::ユーザーにすばやく表示される必要があり、ページのコンテンツは頻繁に変更されないため、こちらもサーバーコンポーネントが適していると言えます。
クライアントコンポーネント
クライアントコンポーネントはブラウザで動的にレンダリングされます。これは、ユーザーインタラクションやリアルタイムのデータ更新が必要な場合に適しています。 Next12までやcreate-react-app
で作成されたアプリケーションはクライアントコンポーネントになります。
使用例:
- インタラクティブなダッシュボード: リアルタイムで更新されるデータやインタラクティブな要素が含まれます。これらはクライアント側でレンダリングすることで、動的なユーザー体験を提供するためクライアントコンポーネントが適しています。
コメントセクション:ユーザーからのリアルタイムのフィードバックを受け付ける場合、クライアント側で動的にレンダリングすると効果的です。こちらもクライアントコンポーネントが適しています。
When to use Server and Client Components?
簡単な使用例を記述しましたが、具体的に、いつ、どちらのコンポーネントを使用した方が良いかを判断するのは難しいと思います。
↑公式に使用例とパターンが記載されているので、こちらを参考にすると良いと思います。Next.js では、同一ページ内で SSR と CSR を組み合わせることが可能です。例えば、ランディングページの静的なコンテンツはサーバーでレンダリングし、ユーザーのフィードバックフォームはクライアントでレンダリングすることができます。
ページの性質や必要な機能に基づいて、サーバーとクライアントのコンポーネントを適切に使い分けることが重要です。
Next.jsでCSRとSSRを実装する
ここまで、ルーティングやコンポーネントについて説明してきましたが、実際にCSR、SSRをNextで実装してみます。現時点(2023/3)でNext14までリリースされており、AppRunterを使う場合が主流かと思いますが、Next12までのSSRの実現方法としてgetServerSideProps
を使用したSSRも実装しました。
<span style="font-size:150%;">CSR</span>
Next13以降(App Router) でクライアントコンポーネントを使用する場合は、ファイルの先頭に"use client"
を記載します。これでこのコンポーネントがクライアントコンポーネントとして認識されます。以下の実装はCSRでデータを取得し表示する実装です。通常のReactアプリケーションと同じなので馴染みああると思います。
- ReactHooksを使用
- useEffectで初回レンダリング時にデータ取得の非同期関数を呼んでいる
- 普段Reactを書いている人なら当然かもしれませんが、コンポーネントの関数自体を非同期関数にすることはできません。初回レンダリング時にデータフェッチしたい場合は
useEffect
の副作用で非同期関数を呼び出す必要があります。
"use client";
import { useState, useEffect } from "react";
interface Data {
userId: number;
id: number;
title: string;
completed: boolean;
}
const TestPage: React.FC = () => {
const [state, setState] = useState<Data | null>(null);
const getData = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data: Data = await res.json();
setState(data);
};
useEffect(() => {
getData();
}, []);
return (
<>
<h1>Test Page</h1>
<h2>
{state?.userId}:{state?.title}
</h2>
</>
);
};
export default TestPage;
SSR
Next.jsでSSRをする場合はNext13以降とそれまで(〜Next12)までで実装方法が変わります。Next13以降はデフォルトがサーバーコンポーネントでコンポーネントの関数自体を非同期関数として取り扱うことができます。Next12まではgetServerSideProps
を使用してSSRします。
getServerSidePropsで実装
getServerSideProps
を使用したSSRの実装です。
- ページ(コンポーネント)のリクエストがあるたびに呼ばれる関数で、サーバー側でデータ取得をサポートするNext.jsのAPIです。
- Next12まで、もしくはPage Routerを使用した方法では
GetServerSideProps
を使用したサーバー側でのデータ取得が可能でした。
import { GetServerSideProps } from "next";
interface Data {
userId: number;
id: number;
title: string;
completed: boolean;
}
interface DataProps {
data: Data;
}
export default function Profile({ data }: DataProps) {
return (
<div>
<h1>{data.userId}</h1>
<p>{data.title}</p>
</div>
);
}
export const getServerSideProps: GetServerSideProps<DataProps> = async (
context
) => {
const data = await fetchData();
console.log(data);
return { props: { data } };
};
async function fetchData(): Promise<Data> {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
return res.json();
}
同じコンポーネントでReactHooksも使えます。
export default function Profile({ data }: DataProps) {
const [state, setState] = useState<Data>(data);
console.log(state);
return (
<div>
<h1>{state.userId}</h1>
<p>{state.title}</p>
</div>
);
}
公式では下記のようになっており、
When should I use getServerSideProps?
You should use getServerSideProps if you need to render a page that relies on personalized user data, or information that can only be known at request time. For example, authorization headers or a geolocation.
パーソナライズされたユーザデータや、リクエスト時にしかわからない情報に依存するページをレンダリングする必要がある場合は、getServerSideProps を使うべきだそうです。
しかしNext13以降のApp Routerを使用した場合のアプリケーションでは同じコードを書くとis not supported
のエラーになります。
getServerSideProps
はサポートされていないようです。同じ実装をApp Routerで実装する場合は以下のようになります。
サーバーコンポーネントによるSSRの実装
interface Data {
userId: number;
id: number;
title: string;
completed: boolean;
}
const TestPage = async () => {
const res: Data = await getData();
console.log(res);
return (
<>
<h1>Test Page</h1>
<h2>
{res.userId}:{res.title}
</h2>
</>
);
};
async function getData() {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
return res.json();
}
export default TestPage;
App Routerの場合はデフォルトがサーバーコンポーネントになるので、そこに直接非同期処理を書けば済む話です。なのでconst TestPage = async () => {}
と関数自体を非同期関数にしてコンポーネントレベルで非同期処理ができます。
まとめ
今回はNext.jsにおけるレンダリングについてまとめて実際にSCRとSSRを実装してみました。実際のアプリケーション開発で使う場合は、切り分けと管理が難しくなりそうだなと感じました。ただ、SEO対策やパフォーマンスの向上には有効な手段だと思うので、うまく活用して、より良いアプリケーションが作れるといいと思いました。
Discussion