Next.jsでのレンダリングを理解してSSRを効果的に活用する

この記事ではNext.jsでのレンダリングについてまとめ、実際にNext.jsでSCR、SSRを実装します。

  • CSRとSSRについて
  • Next.jsのルーティングとコンポーネントについて
  • Next.jsでSSRを実装する

の順に説明します。

CSRとSSR

まず前提としてCSR(Client Side Rendering)とSSR(Server Side Rendering)についてあまりわからないという方は、他にわかりやすくまとめている方がいるので、下記の記事を参考にしてください。

CSR、SSR、SSGの違い

CSR,SSR,SSG,ISRについて理解する

Page RouterとApp Router

Next.jsではアプリケーション作成時にルーティング方法を選択できます。
最新バージョン(Next14)での公式のおすすめはApp Routerです。
どちらもファイルシステムベースルーティングなので、該当フォルダにコンポーネントを作成するだけで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は使えません。

使用例:

  1. ブログの記事: SEOに敏感なコンテンツであり、初期ページロード時にすべての内容が利用可能であるべきです。サーバーコンポーネントが適してると言えます。
  2. 静的なランディングページ:ユーザーにすばやく表示される必要があり、ページのコンテンツは頻繁に変更されないため、こちらもサーバーコンポーネントが適していると言えます。

クライアントコンポーネント

クライアントコンポーネントはブラウザで動的にレンダリングされます。これは、ユーザーインタラクションやリアルタイムのデータ更新が必要な場合に適しています。 Next12までやcreate-react-appで作成されたアプリケーションはクライアントコンポーネントになります。

使用例:

  1. インタラクティブなダッシュボード: リアルタイムで更新されるデータやインタラクティブな要素が含まれます。これらはクライアント側でレンダリングすることで、動的なユーザー体験を提供するためクライアントコンポーネントが適しています。
  2. コメントセクション:ユーザーからのリアルタイムのフィードバックを受け付ける場合、クライアント側で動的にレンダリングすると効果的です。こちらもクライアントコンポーネントが適しています。

When to use Server and Client Components?

簡単な使用例を記述しましたが、具体的に、いつ、どちらのコンポーネントを使用した方が良いかを判断するのは難しいと思います。

Rendering: Composition Patterns | Next.js

↑公式に使用例とパターンが記載されているので、こちらを参考にすると良いと思います。

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();
}

実際にターミナル(node.jsのログ)にも表示されます

同じコンポーネントで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対策やパフォーマンスの向上には有効な手段だと思うので、うまく活用して、より良いアプリケーションが作れるといいと思いました。

Next.jsのレンダリングでお困りですか?お気軽にご相談ください。

ABOUT US
m-akira0924
新卒で株式会社アイスリーデザインに入社。ECサイトのネイティブアプリ開発案件においてAWSによるクラウドインフラの構築、React Nativeによるフロントエンド開発を担当。日々スキルの向上とプロジェクトの成功に努めている。