LoginSignup
6
3

【React / Next.js】Next.jsにおけるレンダリングの基礎

Last updated at Posted at 2023-06-22

Next.jsにおけるレンダリングの種類


【 主な種類 】


種類 正式名称
CSR クライアントサイドレンダリング
SSR サーバーサイドレンダリング
SG 静的サイト生成
ISR インクリメンタル静的再生成

Next.jsでは、赤文字の2種が主に用いられることが多い。



CSR(クライアントサイドレンダリング)

データフェッチ(外部APIに対してデータをとりにいく処理)や、ルーティングの全てがクライアント上で行われる。

Next.jsでは、クライアント側でのみ行いたい処理はuseEffectで囲む。

※Next.jsではあまり利用されない概念。



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

サーバーにリクエストがきたタイミングで動的にHTMLを生成する手法。

外部APIへのデータの取得や、コンポーネントのPropsの値を決定する処理を行い、HTMLを生成してクライアント側に返却する。



SG(静的サイト生成)

ビルド時にデータフェッチやPropsの値の決定を行い、HTMLを構築する。

クライアントからリクエストが来たら、サーバー側で構築せず、生成済みのHTMLを返却する。



ISR(インクリメンタル静的再生成)

ビルド時にHTMLを構築。

一定時間後にアクセスがあった場合、生成済みHTMLを返却しつつ、サーバー側でHTMLを構築。

次のアクセス時に最新のHTMLを返却する。

Next.jsの基本構成

静的ページはSGで行い、
動的に生成する必要があるページはSSRで行うという構成にするのが一般的。



【SSR】 getServerSideProps関数 について

SSRの時に実行される関数のこと。

実行はNode.js上で実行される。

export async function getServerSideProps(context) {
    // 処理内容
    // 引数のcontextには、requestとresponseの情報が載ってくる。
}


// 例
 export default function SSR({ message }) {
   return <h3>{message}</h3>;
 }

 export async function getServerSideProps(context) {
   return { props: { message: "hello from server side props" } };
 }

【SG】 静的サイトを生成してみる

◆ jsファイルの作成 + 関数コンポーネントの定義



【 ディレクトリ階層 】

root/
 ├ start/
     └ pages/
        └ 020_SG/
             └ index.js


【 index.js 】

export default function IndexPage() {
  return <h3>SG</h3>;
}


◆ ビルド

ディレクトリ階層のstartフォルダ地点で、ビルドを実行。


【 実行コマンド 】


package.jsonにbuildコマンドがあること。

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "json-server": "npx json-server -w ./mock/db.json -p 4030"
  },


実行が完了すると以下のようなログが表示される。



スクリーンショット 2023-06-20 20.39.48.png

また、 はSGが実行され、 はSSRが実行されたことを示している。



◆ エクスポート

ビルドが完了したら、exportコマンドを実行する。

そのためにまず、exportコマンドをpackage.jsonに設定。



  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",

    "export": "next export", // ← 新規追加

    "json-server": "npx json-server -w ./mock/db.json -p 4030"
  },


追加したら、先ほどと同様にstartフォルダ地点でコマンド実行。

npm run export


exportが正常に完了すると、以下のようなログが出力される。



スクリーンショット 2023-06-20 20.47.30.png

これで生成完了。

生成されたHTMLはoutディレクトリ内に格納されている。



スクリーンショット 2023-06-20 20.50.19.png



ビルド・エクスポート時の注意点

【 getServerSideProps関数がある場合のエラー 】


以下のようなエラーが表示された場合。

Error occurred prerendering page "/010_SSR". Read more: https://nextjs.org/docs/messages/prerender-error

Error: Error for page /010_SSR: pages with `getServerSideProps` can not be exported. See more info here: https://nextjs.org/docs/messages/gssp-export
    at /Users/start/node_modules/next/dist/export/worker.js:209:27
    at async Span.traceAsyncFn (/Users/start/node_modules/next/dist/trace/trace.js:79:20)
info  - Exporting (5/5)
Error: Export encountered errors on following paths:
        /010_SSR
    at /Users/start/node_modules/next/dist/export/index.js:395:19
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Span.traceAsyncFn (/Users/start/node_modules/next/dist/trace/trace.js:79:20)


getServerSidepropsは、Node.js上(サーバー上)でしか実行されないものなので、この設定が残っているとビルドで失敗する。

よって、この設定があるファイルは、getServerSidePropsを削除 or コメントアウトで消さなければいけない。




【 Imageタグのエラー 】


以下のようなエラーがビルド時に表示された場合。

Error: Image Optimization using Next.js' default loader is not compatible with `next export`.
  Possible solutions:
    - Use `next start` to run a server, which includes the Image Optimization API.

この場合の対処法は以下の通り。


◆ next.config.jsに設定を追加

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
 // 以下の設定を追加
  images: {
    loader: "custom",
  },
};

module.exports = nextConfig;



◆ Imageタグを使用しているファイルで、Imageタグに以下を記載

<Image
    loader={({ src }) => src }
/>

詳しいことは不明。

ImageタグのデフォルトはNodejs サーバーを使用し、画像最適化 API のもと行われるため、SGでは処理できない。



そのため、next.config.jsでimagesの設定をcustomに設定し、クラウドプロバイダー(?)という別の取得方法に変更することで解決するということらしい...。



Imageタグにloaderをpropsとして設定することで、デフォルトのloaderを上書きできるらしく、上記はその上書きをおこなっている。



デフォルトのloader関数は以下の通り。



import Image from 'next/image'

const myLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

const MyImage = (props) => {
  return (
    <Image
      loader={myLoader}
      src="me.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}



◆ 作成したHTMLで遷移ができない。

作成したHTMLに試しにアクセスをしてみる。

VSCodeのLive Serverを使用し、アクセスしてみる。すると、



スクリーンショット 2023-06-20 21.48.53.png



となってしまう。理由はリンクが違うからである。(http://127.0.0.1:5500/020_SG.html が正しく、リンクに拡張子がない。)

TOPページで遷移できたのは、HTMLを取得しているのではなく、ブラウザ上でJavascriptが実行されて遷移できているだけ。

この問題を解決するには、next.config.jsに以下の設定を追加する。



/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,

  // 追加
  trailingSlash: true,

  images: {
    loader: "custom",
  },
};

module.exports = nextConfig;



この設定はリンクの末尾にスラッシュを追加するという設定である。

この状態で再度ビルド+エクスポートを実行すると、



スクリーンショット 2023-06-20 22.01.50.png



設定の追加前と比べ、index.htmlにディレクトリ階層が発生する。

この状態で先ほどと同じようにアクセスすると、



スクリーンショット 2023-06-20 22.04.15.png



アクセスが可能になっている。
これはは、サーバーの挙動に理由がある。

/がつくと、システムはまずリクエストの値に該当するファイルもしくはフォルダを探し出し、フォルダがあった場合は、次の/の後に続く値に該当するファイルを探し出す。
そしてファイルの指定がない場合は、デフォルトでindex.htmlを表示する。

この挙動により、表示が可能になる。



【SG】 getStaticProps関数について

SGで関数コンポーネントにPropsを渡すには、getStaticProps関数を使用する。



export async function getStaticProps() {
    // 処理内容
    // 引数のcontextには、requestとresponseの情報が載ってくる。
}

// 例
export default function IndexPage({ message }) {
  return <h3>SG: {message}</h3>;
}

export async function getStaticProps() {
  return {
    props: { message: "Hello" },
  };
}


またこのgetStaticPropsgetServerSidePropsとは違い、基本的にはビルド時に実行される。



【SG】 getStaticPaths関数について

この関数は、SGかつダイナミックルーティングを利用するときに用いられることが多い。



【 ディレクトリ階層 】

root/
 ├ start/
     └ pages/
        └ 020_SG/
             └ [id].js
             └ index.js


【 [id].js 】

export default function Page({ id }) {
  return <h3>このページは{id}</h3>;
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: false,
  };
}

export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
  };
}


getStaticPaths関数のpathは、getStaticPropsのcontext内に格納されている。
以下はビルド実行した結果。



[    ] info  - Generating static pages (0/7){
  params: { id: '2' },
  locales: undefined,
  locale: undefined,
  defaultLocale: undefined
}
{
  params: { id: '1' },
  locales: undefined,
  locale: undefined,
  defaultLocale: undefined
}
info  - Generating static pages (7/7)
info  - Finalizing page optimization  

Page                                       Size     First Load JS
┌ ○ /                                      1.1 kB         84.5 kB
├   └ css/adf6895ec356eb64.css             653 B
├   /_app                                  0 B            83.4 kB
├ ○ /010_SSR                               276 B          83.7 kB
├ ● /020_SG                                301 B          83.7 kB
├ ● /020_SG/[id] (302 ms)                  320 B          83.7 kB
├   ├ /020_SG/1
├   └ /020_SG/2
├ ○ /404                                   194 B          83.6 kB
└ λ /api/hello                             0 B            83.4 kB
+ First Load JS shared by all              83.4 kB
  ├ chunks/framework-4556c45dd113b893.js   45.2 kB
  ├ chunks/main-d4e32c6306a72f6e.js        30.8 kB
  ├ chunks/pages/_app-01dced7ad17e1737.js  6.6 kB
  ├ chunks/webpack-69bfa6990bb9e155.js     769 B
  └ css/3a770aacb0f6edfc.css               652 B


getStaticPropsは、getStaticPathsのリターンのpathの数だけ実行される。

※console.logの実行結果が2回表示されていることからわかる。

exportを実行すると、outフォルダに以下のようにpathで渡した値がファイル名になっていることがわかる。

スクリーンショット 2023-06-20 23.37.02.png

スクリーンショット 2023-06-20 23.40.04.png

スクリーンショット 2023-06-20 23.40.21.png



◆ getStaticPathsのfallbackについて

こちらの設定はダイナミックルーティングで作成されていないルーティングに対してアクセスした場合の処理についての設定である。

【 まず前提として 】

先ほどの説明で、getStaticPropsは「基本的にビルド時に実行される」と説明をしたが、ビルド時以外にも実行されるケースがある。

これは、SGを実行する方法によって変わる。



◆ 1つ目 build + export


こちらは先ほどから実行している方法。
この方法の特徴は、export後に出力されるoutフォルダの中のHTMLを参照しているところ。



◆ 2つ目 build + start

こちらはbuildした後にnpm run startコマンドを実行してSGを実行する方法。

この方法ではoutフォルダは参照されず、通常のNext.js通り、pagesを参照したルーティングになる。



◆ 挙動確認

【 fallback: falseの場合 】

ルートを存在しない http://localhost:3000/020_SG/1/ でアクセスしてみると、



スクリーンショット 2023-06-21 0.22.14.png



404ページを返す。



【 fallback: trueの場合 】

trueの場合、作成されていないリンクにアクセスした場合の処理を考えることができる。
この時、useRouterを使用する。



import { useRouter } from "next/router";

export default function Page({ id }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return <h3>このページは{id}</h3>;
  }
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: true,
  };
}

export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
  };
}


【 解説 】

export default function Page({ id }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return <h3>このページは{id}</h3>;
  }
}


作成されていないリンクにアクセスした場合、Node.js側で新しくページの作成を開始する。
この時fallbackfalseの状態であり、router.isFallbackfalseになるため、Loading...が画面に表示される。

作成されている間、随時getStaticPropsがNode.js上で実行されている。



スクリーンショット 2023-06-21 0.33.07.png


ページの作成が完了すると、fallbacktrueになるため、router.isFallbacktrueに変わり、作成されたページが表示される。



スクリーンショット 2023-06-21 0.33.37.png



また、一度作成されたページはプロジェクト内の.nextフォルダ上に保存されるため、次回以降は作成される処理は流れない(getStaticPropsは実行されない)。



スクリーンショット 2023-06-21 0.42.56.png



【 fallback: "blocking"の場合 】

true/false以外にも、blockingという設定がある。



import { useRouter } from "next/router";

export default function Page({ id }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return <h3>このページは{id}</h3>;
  }
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: "blocking",
  };
}

export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
  };
}


このblockingという設定では、ページの作成が完了するまでレスポンスを待機させるという設定になる。



ISRの使い方

ISRは最初の紹介の際、

ビルド時にHTMLを構築。一定時間後にアクセスがあった場合、生成済みHTMLを返却しつつ、サーバー側でHTMLを構築し、次のアクセス時に最新のHTMLを返却する。

という説明をした。これはつまり、
SG と SSR を組み合わせて利用するということである。

実際の利用方法



export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
    // 追加
    revalidate: 5,
  };
}

実際の設定方法はとても簡単で、``getStaticProps`のreturnで返すオブジェクトに、

revalidate:数値

というプロパティを数値を設定するだけ。

試しにpropsに日付を取得する変数を追加し、画面上で確認してみる。



import { useRouter } from "next/router";

export default function Page({ id, date }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return (
      <h3>
        このページは{id}
        <br />
        {date}
      </h3>
    );
  }
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: "blocking",
  };
}

export async function getStaticProps(context) {
  console.log(context);
  const date = new Date();
  return {
    props: {
      id: context.params.id,
      date: date.toJSON(),
    },
    revalidate: 5,
  };
}

この状態でビルド + スタートし、画面表示をしてみる。



スクリーンショット 2023-06-22 0.46.34.png

初期表示ではこの値だが、先ほどのrevalidateの値が5なので、
5秒後に再度リクエストを送信すると、新しい数値が表示されるようになる。

↓ 5秒後

スクリーンショット 2023-06-22 0.46.52.png

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3