Next.jsのAppRouterでSSGする
はじめに
当ブログをNext.jsのApp Routerを利用し作り直しました。
作り直し当初はmicroCMSさんのサンプルを少し参考にしていたのですが、サンプルでは基本的にはすべてSSRしており(2023/11時点)、当ブログでは動的に扱うべきコンテンツは特にない + URLからOGPデータを引っ張ってきてカードで表示する処理(以下みたいなやつ)
「なんだろう、無駄なuseState使うのやめてもらっていいですか?」zenn.dev
が少々重たい上に(書き方が悪いだけかもですが)以前はPages Router × SSGにて実装していたので、パフォーマンスが気になりやはりSSGが良いと思い書き直しました。
SSGの設定をする
Pages Routerの場合はnext build & next export
を実行する必要がありますが、App Routerだと下記の設定をnext.config.js
に追加し、next build
の実行だけで静的ファイルがout
ディレクトリに作成されます。
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export'
}
module.exports = nextConfig
※ 余談ですが、上記の設定をせずにbuildをするとout
ディレクトリにファイルは生成されませんが、.next/server
の中に静的ファイルが生成されnexr start
でサーバを動かした時にこのファイルが使用されるようです。
データの取得
PagesRouterでは、getStaticProps
、getServersideProps
を使用しSSG、SSRの挙動を使い分けていましたが、App Routerではそれらを区別せずにServer Component内でデータの取得を行います。
例えば以下のようなコードでgetStaticProps
と同じ挙動を実現できます。
export default async function Page() {
// 先程の設定でSSGを有効にしているので、この処理はbuild時に実行されます。
const res = await fetch('https://api.example.com/...')
const data = await res.json()
return <main>...</main>
}
動的なルートの生成
以下の例を元に確認していきます。
type Props = {
params: {
slug: string
}
}
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return data.map((post) => ({
slug: post.slug
}))
}
export default async function Page({ params }: Props) {
const post = await getPostDetail({ slug: params.slug })
// ...
}
Pages RouterではgetStaticPaths
にて動的ルートを生成していましたが、App RouterではgenerateStaticParams
を使用します。
さらに以前のgetStaticPaths
のようにpaths
のネストや、params
をkeyにもつ必要はなくなり、非常にシンプルに書けるようになりました。
export const getStaticPaths = (async () => {
return {
paths: [ {
params: {
slug: 'next.js'
}
}],
fallback: true
}
})
また、paramsの型に関しては、GetServerSideProps<T>
のような型がなさそうなので、現時点では自分で作成し、指定をする必要があります。
type Props = {
params: {
slug: string
}
}
export default async function Page({ params }: Props) {
また、以下のように複数のセグメントによるパスを作成したい場合は、以下のように記述することで実現ができます。
export function generateStaticParams() {
return [
{ category: "electronics", product: "1" },
{ category: "electronics", product: "2" },
{ category: "books", product: "3" },
{ category: "books", product: "4" },
]
}
export default function Page({ params }: { params: { category: string; product: string } }) {
// ...
}
例えば、以下のようなファイル構造だと仮定すると
app
├── products
│ ├── [category]
│ │ └── [product]
│ │ └── page.tsx
│ └── page.tsx
└── page.tsx
以下のようなパスが生成されます。
- /products/electronics/1
- /products/electronics/2
- /products/books/3
- /products/books/4
- ...
さいごに
SSGに関しては多少シンプルに書けるようになった気がします。
しかし、App Routerそのものに関しては使用していてPages Routerとは完全に別物に感じます。
特にキャッシュ、Server Action(これはReactの機能ですが)あたりは複雑そうで、まだノータッチなのでいずれキャッチアップしたいですね..。