🏅

Next.js レンダリング方法(CSR,SSR,SSG,ISR,PPR)とウェブアプリケーションの構築手法(SPA)

2024/07/04に公開

はじめに

レンダリング方法(CSR,SSR,SSG,ISR)とウェブアプリケーションの構築手法(SPA)の説明について記述し、Next.jsの場合はどのように実装するのか簡単に書いています。

Next.jsのApp Routerにおいて静的生成を行う場合以外、特に開発環境では、'use client;'の有無に応じてCSRとSSRが組み合わせられたレンダリングが行われます。

基本的には、App Directory構造の話のみ記述していますが、一部Pages Directory構造での話も書いています。

レンダリングの方法

CSR(Client-Side Rendering)

JavaScriptを使用してブラウザ上に動的にWebページを生成する方法

Webページ表示までの流れ

[ブラウザ]ユーザーがURLにアクセスすると、ブラウザはWebページを生成(レンダリング)するためにサーバーにページデータのリクエストを送信

→[サーバー]基本的なマークアップのみで詳細なコンテンツやスタイルは含まれていないHTMLをレスポンス

→[ブラウザ]この時点では画面に何も表示されません。受け取ったHTMLを上から実行、<link>や<script>タグがあった場合、サーバーへCSSやJavaScriptファイルをダウンロードするための追加リクエストをサーバーに送信

→[サーバー]リクエストされたファイルをレスポンス

→[ブラウザ]ダウンロードされたJavaScriptとCSSファイルを読み込みます。このダウンロードには、アプリケーションの全てのページを生成・管理するのに必要なスクリプトとCSSが含まれています。

  • CSSの適用: ブラウザはダウンロードされたCSSファイルを読み込み、HTMLに対してスタイルを適用します。これにより、ページの見た目が形成されます。

  • JavaScriptの実行: JavaScriptファイルが読み込まれると、ブラウザはそのスクリプトを実行します。これにより、DOMを動的に操作してページに新しい要素を追加したり、既存の内容を変更したりします。

メリット

  • サーバーの負荷軽減
    レンダリング処理がクライアント側で完結するため、サーバーのリソース負担が削減

  • ページ間の遷移が高速
    JavaScriptがクライアント側でページ内容を動的に生成するため、サーバーからの新しいページ全体のロードが不要です。これにより、ページ間遷移が高速に行われます。

デメリット

  • SEO
    ブラウザ側でJavaScriptが実行されるまで、基本的なマークアップのみで詳細なコンテンツやスタイルは含まれていないHTMLが表示され、そのタイミングでクローラーはコンテンツを確認してしまうので不利になることがあります。

  • 初回読み込みが遅い
    必要なJavaScriptファイルなどが多い場合、これらをすべてダウンロードして解析、実行する必要があるため、ページの初回表示に時間がかかってしまう

ユースケース

  • リアルタイムで頻繁に更新が必要なアプリケーション
    CSRはリアルタイムで頻繁に更新が必要なインタラクティブな(ユーザーがアプリケーションと対話できる)ウェブアプリケーションに最適です。ユーザーのアクションに応じてクライアント側で即座にレスポンスを更新することができ、ページ遷移なしに滑らかなインタラクションを提供します。

Next.jsでは

ファイルの最初に'use client';と記述している場合はCSRとして扱われます。

SSR(Server-Side Rendering)

CSRと異なり、サーバー側でHTMLが生成されます。
ページの遷移ごとにサーバーにリクエストを送信し、新しいHTMLが生成されます。

Webページ表示までの流れ

[ブラウザ] ユーザーがURLにアクセスすると、ブラウザはサーバーにページデータのリクエストを送信

→[サーバー] サーバー上で動作するスクリプト(PHP、Node.js、Rubyなど)を使用して、完成されたリクエストされたページのHTMLを生成して、ブラウザに送信

→[ブラウザ] HTMLの表示

メリット

  • SEO
    検索エンジンがHTML内容を容易にクロールできるため、SEOが向上します。

  • 初回表示速度の向上
    完成されたHTMLが送信されるため、特にネットワーク速度が遅い場合にページが早く表示されます。

デメリット

  • サーバーの負荷
    ページごとにサーバーでのレンダリングが必要なため、サーバーに高い負荷がかかることがあります。

  • ページ遷移
    新しいページへの遷移が行われるたびに、ブラウザはそのページの完全なHTMLをサーバーに再度要求します。CSRに比べて遷移が遅くなる可能性があります。

ユースケース

  • SEOが重要なコンテンツベースのウェブサイト
    例: ニュースサイト、ブログ、企業のマーケティングサイト
    サーバーで完全にレンダリングされたページがブラウザに送信されるため、検索エンジンがコンテンツをより効果的にクロールし、インデックスすることができます。これにより、検索エンジン結果ページでのランキングが向上します。

  • 初回訪問時のパフォーマンスが重要なウェブサイト
    例: イーコマースサイト、ランディングページ
    ユーザーが最初に訪れたときに速くページを表示することができ、特にモバイルユーザーや低速なインターネット接続を持つユーザーにとって重要です。完成されたページをすぐにロードすることで、ユーザーの離脱率を低下させ、変換率を向上させる効果があります。

Next.jsでは

App Directory構造の場合は、ファイルの最初に'use client';と記述していない場合、すなわちデフォルトではそのファイルはSSRとして扱われます。

SSG(Static Site Generation、静的サイト生成)

ページのコンテンツをビルド時に静的なHTMLファイルとして生成する方法です。
SSRやCSRとは異なり、ページが訪問者によってリクエストされるたびに生成されるのではなく、全ページがあらかじめ生成される点が特徴です。

Webページ表示までの流れ

[開発者]サーバー上に置かれたあらかじめ生成されたHTMLを配置する

[ブラウザ] ユーザーがURLにアクセスすると、ブラウザはサーバーにページデータのリクエストを送信

→[サーバー] サーバー上に置かれたあらかじめ生成されたHTMLをブラウザに送信

メリット

  • パフォーマンス向上
    静的ファイルは高速に提供できるため、ウェブサイトのパフォーマンスが向上します。また、CDNとの組み合わせで更に配信速度を高めることができます。

  • セキュリティ
    動的なサーバーサイドの処理がないため、SQLインジェクションなどの攻撃リスクが低減します。

  • サーバーの負荷
    静的ファイルの提供はサーバー資源をほとんど消費しないため、多数のリクエストがあってもサーバーの負荷が増加しにくいです。

  • SEO
    静的ファイルはすぐにロードされるため、検索エンジンがクロールしやすく、SEOに有利です。

デメリット

  • 動的機能の制限
    リアルタイムでのデータ更新やユーザー固有のインタラクションには追加でクライアントサイドJavaScriptやAPIの利用が必要になります。

  • ビルド時間
    大規模なサイトや頻繁に更新が必要なコンテンツの場合、全ページを再ビルドする時間がかかる可能性があります。

ユースケース

更新頻度が少ないサイト

Next.jsでは

下記でビルドすると基本的にはSSGになると思います。

npm run build

https://zenn.dev/nenenemo/articles/082ac7dcffe308#レンダリング方法の確認

動的ルーテイングがある場合は、そのままではビルドできないのでディレクトリ構造ごとに処理を記述してください。

Pages Directory構造の場合

getStaticPropsgetStaticPathsという二つの関数を用いて実装してください。
https://nextjs-org.translate.goog/docs/pages/building-your-application/rendering/static-site-generation?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=sc&_x_tr_hist=true

App Directory構造の場合

下記を参考にgenerateStaticParamsを使用してください。

is missing "generateStaticParams()" so it cannot be used with "output: export" config.

getStaticPathsgetStaticPropsを使用せずに、動的なURL([id],[slug])などを使用している場合にビルドを実行すると表示されるエラーです。

"getStaticProps" is not supported in app/. Read more: https://nextjs.org/docs/app/building-your-application/data-fetching

App Directory構造では、従来のpagesディレクトリ内で行っていたgetStaticPropsgetStaticPathsnext.config.mjsexportPathMapのようなデータフェッチングメソッドの使用が非推奨になっています。
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating

getStaticPathsは、generateStaticParamsに変更されています。
https://nextjs.org/docs/app/api-reference/functions/generate-static-params

こちらの方法を試してください。
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating

https://github.com/vercel/next.js/issues/50424

generateStaticParams

動的ルートを持つページに対して静的なパス(パラメータの組み合わせ)を生成します。
https://nextjs.org/docs/app/api-reference/functions/generate-static-params#returns

この関数は、静的サイト生成(SSG)のためにビルド時に特定のルート(ページ)の動的パラメータを生成するために使用されます。

Next.jsのレンダリングモデルがサーバーサイドとクライアントサイドのロジックを分離するので、generateStaticParamsとクライアントサイドのロジック('use client')を同時に使用することはできません。
https://stackoverflow.com/questions/76824224/in-next-js-13-using-app-router-why-cant-i-export-dynamic-routes-with-use-clie
https://github.com/vercel/next.js/issues/48022
https://github.com/vercel/next.js/discussions/55393
https://github.com/vercel/next.js/issues/56253
https://zenn.dev/hiromu617/articles/1ed6811dc6cf26

またビルド時には、ポート3000が動作しないため、Next.jsの内部APIルートをエンドポイントにすることはできません。

ISR(Incremental Static Regeneration)

静的サイト生成(SSG)の拡張版です。
ISRは、ページを事前に生成し、必要に応じて背後でページを定期的に再生成してページの内容を最新の状態に保つことが可能です。

メリット

  • ページは定期的に更新されるため、コンテンツは常に最新の状態を保つことができます。

デメリット

  • コンテンツの非同期更新
    ページが訪問された時点で最新ではない可能性があるため、訪問者に古い情報が表示されることがあります。バックグラウンドでページが再生成されるため、更新のタイミングによっては一部のユーザーが古いコンテンツを見ることになる可能性があります。

ユースケース

  • ブログやニュースサイト
    定期的にコンテンツが更新されるブログやニュースサイトで非常に有効です。記事を即座に静的に生成し、後で内容が変更された場合には背後で静的ファイルを再生成して更新することができます。

Next.jsでは

Pages Directory構造の場合

getStaticPropsgetStaticPaths内でrevalidateプロパティを設定することで、一定時間ごとにページの再生成をトリガーできます。
https://zenn.dev/akino/articles/78479998efef55
https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration

App Directory構造の場合

fetchのオプションとしてrevalidateを指定してください。
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating

ウェブアプリケーションの構築手法

SPA(シングルページアプリケーション)

CSR技術を用いたウェブアプリケーションやウェブサイトの設計パターンの一つです。

ユーザーが異なるページ間を移動しても、新たにページ全体をロードするのではなく、必要なデータのみをサーバーから取得し、ページの一部だけを更新します。

一度ロードされた共通の要素(ヘッダーやフッターなど)は再度ロードされることなく、ページ間で維持されます。これにより、ユーザーが異なるページに移動する際に、共通のレイアウトを保持しつつ、必要なコンテンツのみが動的に更新されることが特徴です。
https://qiita.com/shinkai_/items/79e539b614ac52e48ca4

メリットとデメリット

基本的にCSRと同じです。

ユースケース

  • 共通のレイアウトがあるアプリケーション
    ページ遷移した際、共通のレイアウト以外の差分がレンダリングされます。

Next.jsでは

next/linknext/navigationを使用することで、ページ間の遷移をクライアントサイドで処理することができます。これにより、新しいページのHTML全体をサーバーからロードする代わりに、必要なデータのみをフェッチしてビューを更新することが可能です。

next/linkを使用することで、ブラウザは新しいページのJavaScriptとデータだけを取得し、ページのDOMを更新します。これはブラウザがフルページをリロードすることなく、ページ遷移を実現するため、アプリケーションのレスポンスが向上し、ユーザー体験がスムーズになります。

一方で、next/linknext/navigationを使用しない場合(<a>タグを使用する)、Next.js アプリケーションは各ページ遷移でブラウザが新しいページのHTMLをサーバーから全て取得し、ページ全体をリロードする必要があります。これはMPAのような挙動となり、クライアントサイドナビゲーションの利点を享受できません。

PPR(Partial Prerendering Regeneration)

Next.js 14で導入された実験的な機能で、静的レンダリングと動的レンダリングの利点を同一ルートで組み合わせた部分的な事前レンダリング
https://nextjs.org/learn/dashboard-app/partial-prerendering

next.config.mjs
/** @type {import('next').NextConfig} */
 
const nextConfig = {
  experimental: {
    ppr: 'incremental',
  },
};
 
export default nextConfig;

MPA(Multi-Page Application)

従来のウェブアプリケーションの設計パターンであり、ユーザーが異なるページ間を移動するたびに、ブラウザが新しいページをサーバーからロードする形式のアプリケーションです。

このタイプのアプリケーションは、それぞれのページが個別にサーバーで処理され、完全なHTMLページとしてブラウザに送信されます。MPAは、SSRを利用することが多いようです。

is missing "generateStaticParams()" so it cannot be used with "output: export" config.

私の場合は、本番用のエンドポイントやDynamic Routesが正しく定義されていないために表示されていました。

取り急ぎ本番用のエンドポイントは未設定だったため、モックデータを作成するとビルドできました。
これは、静的生成(SSG)実行時に、にDynamic Routesが含まれている場合には、静的に生成するパスを定義する必要があるためです。
https://zenn.dev/noko_noko/articles/ac79ec7febce9c

例、特定のスラッグ(URLの一部)に対応する記事の詳細ページを表示するコンポーネントを定義

Page コンポーネントは、paramsプロパティを受け取り、それを使用してArticleDetailコンポーネントをレンダリングします。paramsには記事のslugが含まれています。

src/app/technical-articles/[slug]/page.tsx
import { generateStaticParams } from '@/utils/generate-static-params';
import ArticleDetail from './ArticleDetail';

export { generateStaticParams };

export default function Page({ params }: { params: { slug: string } }) {
  return <ArticleDetail />;
}
utils/generate-static-params.ts
import axios from 'axios';

export async function generateStaticParams() {
  const API_GATE_WAY_URL = process.env.API_GATE_WAY_URL || '';

  try {
    const res = await axios.get(API_GATE_WAY_URL);
    const articles: { id: string; title: string; markdownContents: string }[] =
      res.data.body;

    return articles.map((article) => ({
      slug: article.id,
    }));
  } catch (err) {
    throw err;
  }
}

Page "/api/trpc/[trpc]" is missing "generateStaticParams()" so it cannot be used with "output: export" config.

https://trpc.io/docs/client/nextjs/ssg


Page "/api/auth/[...nextauth]" is missing "generateStaticParams()" so it cannot be used with "output: export" config.

https://stackoverflow.com/questions/77550513/error-page-api-auth-nextauth-is-missing-generatestaticparams-so-it

<NextAuthProvider> はサーバー側のセッションを扱うため、このコンポーネントが含まれるページは静的にエクスポートできません。

https://qiita.com/kage1020/items/e5b0053d7046a9b1f628
https://www.npmjs.com/package/next-auth

Dynamic Routes(動的ルート)

Next.jsなどのフレームワークで用いられる概念で、URLの一部が変動するルートを指します。

終わりに

何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉

Discussion