はじめに
前回「ポートフォリオ兼近況報告用サイトをデプロイした」記事を投稿しまして。
「記事ごとの静的ページを作成」を今後の方針に挙げていました。
今回は、その対応を完了したのでまとめていこうと思います。
GitHubのレポジトリ↓
デプロイ↓
使用した技術
前回と同じです。
- Next.js ver.12.2.5
- TypeScript
- Notion: 今回は関係なし
- notion-page-to-html: 今回は関係なし
- Vercel: 今回は関係なし
- Google Analytics: 今回は関係なし
- SCSS: 今回は関係なし
- (NextUI: レスポンシブ用のGridを使ってたのですが、結局は直書きがベストかなと)
SSGについて
SSGを自分なりの理解に要約すると、「ビルドする時にfetchとかaxiosのリクエスト何かをやってデータを取得、その取得したデータから固定ページ(html)を生成してくれる機能」なのかという感じです。
例えば、こんなパス /article/(ここにIDが入る) があった時
SSG対応をしていないと /article/hoge に最初アクセスされた時、記事の内容は無く、fetchなりでデータが取得されて初めて記事が表示されることになる。
→ 前回まではこの状態でした。
ユーザ固有の情報を取得するようなアプリであればSSG対応は無意味ですが、ブログとかでは必須になってくるものです。
具体的にはブログの場合、該当のパスへのアクセスが来るたびにデータ取得を行っているのでコスパの面で無意味、データフェッチに若干時間が掛かる、という感じです。
Next.jsにおけるSSG対応
Next.jsにはデフォルトでSSGに対応する仕組みがあるのでそれを使いました。
SSG対応の処理はページ単位で記述することになっていて、pages配下の xxx.tsx で適用(_app.tsxでは無理みたい)します。
- getStaticProps()
- getStaticPaths()
使い方について、型を厳密に書いているものが見当たらなかったので細かく書いていこうと思います。
まず、これらの関数は import で読み込むのでは無く「この名前の関数を作成してexport」する必要があります。(ちょっとメンドイ)
一応、型が存在しているので const として定義するとしっかり型が効きます。
getStaticProps()
getStaticProps()を自分なりにまとめると「ビルドする時にfetchとかaxiosのリクエスト何かをやってデータを取得、その取得したデータをNextPageに渡す」を担当している、です。
一部省略したコードを以下に書いてます。
import type { GetStaticProps, NextPage } from "next"
import { PostType } from "@/pages/api/api_output_types" // typesで分けたほうが良かったかも
export type PostListProps = {
posts: PostType[]
}
const Post: NextPage<PostListProps> = ({ posts }: PostListProps) => {
return /* 表示するとこ */
}
export const getStaticProps: GetStaticProps<PostListProps> = async () => {
const posts = /* 記事を取得するロジック */
return {
props: {
posts,
},
}
}
export default Post
getStaticProps()の中で記事の一覧(記事の配列)を取得 → Post({ posts }: PostListProps) に渡すという流れが何となく分かるかと思います。
getStaticProps()ではより細かいこともできて、ここではやってませんが以下のようにすると404ページが表示されるようになります。(個別ページではこうしてますが、一覧ではいいかなー)
export const getStaticProps: GetStaticProps<PostListProps> = async () => {
try {
const posts = /* 記事を取得するロジック */
return {
props: {
posts,
},
}
} catch (err: unknown) {
return {
notFound: true
}
}
}
getStaticPaths()
getStaticPaths()を自分なりにまとめると「/hoge/[id] となるようなパス(Dynamic Routes)の生成」を担当している、です。
getStaticProps()に比べるとシンプルな機能と覚えてます。
import type { NextPage, GetStaticPaths, GetStaticProps } from "next"
import { PostGenreType } from "@/pages/api/api_output_types"
export type PostDetailType = {
title: string
genres: PostGenreType[]
createdAt: string
lastUpdatedAt: string
html: string
}
const PostDetail: NextPage<PostDetailType> = ({ ~ }: PostDetailType) => {
return /* 表示するとこ */
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = /* 記事の一覧を取得するロジック */
const paths = posts.map((post) => `/post/${post.slug}`)
return { paths, fallback: false }
}
export const getStaticProps: GetStaticProps<PostDetailType> = async (ctx) => {
try {
if (
ctx.params === undefined ||
ctx.params.id == undefined ||
Array.isArray(ctx.params.id)
) {
// 一応 id のバリデーション & 型をかっちりするために入れている
return {
notFound: true,
}
}
const post = /* 記事を取得するロジック */
return {
props: {
~
},
}
} catch (err: unknown) {
return {
notFound: true,
}
}
}
export default PostDetail
getStaticPaths()でgetStaticProps()で取得するパスを生成 → getStaticProps()で記事を取得 → Post({ posts }: PostDetailType) に渡すという流れが何となく分かるかと思います。
修正
修正箇所
主な修正箇所は以下のとおりです。
- 一覧画面 (/pages/index.tsx)
- 個別記事画面 (/pages/post/[id].tsx)
修正内容
- API routesの廃止
- コントローラー(PostController 名前はこれで良かったのか...)の作成
- 一覧画面
- getStaticProps() の追加
- 個別記事画面
- getStaticPaths(), getStaticProps() の追加
実際のコードは上のほぼ上のサンプルコードの通りです。
今後
- 数式やコードに対応できていない、上手く表示されないのでこれの解消
- micro CMSいいかも(今回の修正で以降はスムーズにいきそう)
- Notion → micro CMS
- getStaticPaths() → getStaticProps()のロジックの見直し
- 記事数Nでビルド時にN回記事取得ロジックが走ることになるのでどうにかならないか?
- micro CMSに移行するなら尚更
おわりに
重い腰を上げ、2ヶ月ほど前に作成したブツのSSG対応を行った。
調べる段階では、結構時間が掛かるかもと思っていたが、着手してみると1週間かからずに実装できた。Next.jsすげぇ!