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"
},
実行が完了すると以下のようなログが表示される。
また、○
は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が正常に完了すると、以下のようなログが出力される。
これで生成完了。
生成されたHTMLはoutディレクトリ
内に格納されている。
ビルド・エクスポート時の注意点
【 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を使用し、アクセスしてみる。すると、
となってしまう。理由はリンクが違うからである。(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;
この設定はリンクの末尾にスラッシュを追加するという設定である。
この状態で再度ビルド+エクスポートを実行すると、
設定の追加前と比べ、index.htmlにディレクトリ階層が発生する。
この状態で先ほどと同じようにアクセスすると、
アクセスが可能になっている。
これはは、サーバーの挙動に理由がある。
/
がつくと、システムはまずリクエストの値に該当するファイル
もしくはフォルダ
を探し出し、フォルダ
があった場合は、次の/
の後に続く値に該当するファイルを探し出す。
そしてファイルの指定がない場合は、デフォルトで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" },
};
}
またこのgetStaticProps
はgetServerSideProps
とは違い、基本的にはビルド時に
実行される。
【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で渡した値がファイル名になっていることがわかる。
◆ 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/ でアクセスしてみると、
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側で新しくページの作成を開始する。
この時fallback
はfalse
の状態であり、router.isFallback
がfalse
になるため、Loading...が画面に表示される。
作成されている間、随時getStaticProps
がNode.js上で実行されている。
ページの作成が完了すると、fallback
がtrue
になるため、router.isFallback
がtrue
に変わり、作成されたページが表示される。
また、一度作成されたページはプロジェクト内の.next
フォルダ上に保存されるため、次回以降は作成される処理は流れない(getStaticPropsは実行されない
)。
【 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,
};
}
この状態でビルド + スタートし、画面表示をしてみる。
初期表示ではこの値だが、先ほどのrevalidate
の値が5
なので、
5秒後に再度リクエストを送信すると、新しい数値が表示されるようになる。
↓ 5秒後