- 前回の記事
- Dynamic Routes とは
- Markdown で書かれた記事を表示する
- 日付をきれいに表示する(CSS)
- fallback について
- Catch-all Routes
- 404 ページをカスタマイズする場合
- API を叩く際の注意点
- 次回
前回の記事
この記事は第4回の記事です。
今までの記事
- #1 Next.js の思想や概要の説明、Dockerでの開発環境構築
- #2 Next.js の基本的な使い方
- #3 Server-side Rendering と Static Generation
今回はルーティング周りをやっていきます。
Dynamic Routes とは
ブログの記事とか、書くたびに Routes 、つまり URL が増えていきます。こういうもののことを Dynamic Routes と呼びます。 /posts/<id>
のような形式のルーティングのことです。
前回の記事で、ブログ記事を markdown ファイルで用意し、読み込むことをしました。今回はこの markdown のファイル名を ID として、 Dynamic Routes を設定しようと思います。
例えば、こんな URL になります。
posts/ssg-ssr
/posts/pre-rendering
これをするには、pages/posts/[id].js
というファイルを作ります。 []
が Dynamin Routes を表します。
そして、新しく、 getStatisPaths
関数が登場します。id
として利用可能な値をこの関数で返します。
例:
import Layout from '../../components/layout' export default function Post() { return <Layout>...</Layout> } export async function getStaticPaths() { // id として取りうる値を列挙して返す }
続いて、今まで同様に getStatisProps
を実装しますが、パラメータで id を受け取るように変更します。
pages/posts/first-post.js
ファイルは使わなくなるので消ます。
代わりに [id].js
を作成します。中身はまだ空です。
import Layout from '../../components/layout' export default function Post() { return <Layout></Layout> }
getStatisPaths
関数で、ページ一覧が必要になるので、lib/posts.js
にメソッドを追加します。レスポンス形式はコメントに書いてあるとおりです。 string[] を返すわけはないので、注意が必要です。
export function getAllPostIds() { const fileNames = fs.readdirSync(postsDirectory) // Returns an array that looks like this: // [ // { // params: { // id: 'ssg-ssr' // } // }, // { // params: { // id: 'pre-rendering' // } // } // ] return fileNames.map(fileName => { return { params: { id: fileName.replace(/\.md$/, '') } } }) }
これを [id].js
から使います。fallback: false
については、ここでは説明しません。
import { getAllPostIds } from '../../lib/posts' export async function getStaticPaths() { const paths = getAllPostIds() return { paths, fallback: false } }
続いて、 lib/posts.js
に、postの中身を取得する関数を追加します。
export function getPostData(id) { const fullPath = path.join(postsDirectory, `${id}.md`) const fileContents = fs.readFileSync(fullPath, 'utf8') // Use gray-matter to parse the post metadata section const matterResult = matter(fileContents) // Combine the data with the id return { id, ...matterResult.data } }
これを pages/posts/[id].js
の getStaticProps
の中で使います。
import { getAllPostIds, getPostData } from '../../lib/posts' // params を受け取るようにする export async function getStaticProps({ params }) { // id に対応するデータを取得 const postData = getPostData(params.id) return { props: { postData } } }
最後に、 [id].js
の Post
コンポーネントの中身を実装します。
export default function Post({ postData }) { return ( <Layout> {postData.title} <br /> {postData.id} <br /> {postData.date} </Layout> ) }
http://127.0.0.1:3000/posts/ssg-ssr にアクセスすると、ブログのタイトルと日付だけ出ているはずです。
ここまでの差分: https://github.com/yoshikyoto/nextjs-blog/commit/3ebbc61d0ea55077c4b9daf79a40f98502daa900
Markdown で書かれた記事を表示する
markdown のライブラリを追加します。
npm install remark remark-html
docker再起動
docker-compose down
docker-compose up -d
lib/posts.js
の getPostData
を修正します。await remark()
するために、関数が async になっている必要があります。
import remark from 'remark' import html from 'remark-html' export async function getPostData(id) { const fullPath = path.join(postsDirectory, `${id}.md`) const fileContents = fs.readFileSync(fullPath, 'utf8') // Use gray-matter to parse the post metadata section const matterResult = matter(fileContents) // Use remark to convert markdown into HTML string const processedContent = await remark() .use(html) .process(matterResult.content) const contentHtml = processedContent.toString() // Combine the data with the id and contentHtml return { id, contentHtml, ...matterResult.data } }
メソッドが async になったので、pages/posts/[id].js
でも await
するようにします。
export async function getStaticProps({ params }) { const postData = await getPostData(params.id) }
最後に、Post コンポーネントに修正を加えます。
export default function Post({ postData }) { return ( <Layout> {postData.title} <br /> {postData.id} <br /> {postData.date} <br /> <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} /> </Layout> ) }
ここまでの差分: https://github.com/yoshikyoto/nextjs-blog/commit/094a103c36460d205044772b8cc180b3d997394a
日付をきれいに表示する(CSS)
特に重要なところはないので GitHub の差分だけ
https://github.com/yoshikyoto/nextjs-blog/commit/1cb1d36da21d6913fe8d1fe8a58442e0d8af6ad9
さらに CSS を追加してきれいにする
https://github.com/yoshikyoto/nextjs-blog/commit/c73c4786d0022d23ad2677033befd19724d5d4f1
さらに index ページにも CSS あてる
https://github.com/yoshikyoto/nextjs-blog/commit/c5c97a051d02b7bf457b8aa2df1f70b313271bd8
fallback について
fallback: false
は、ページが見つからなかった時に 404 を返します。
fallback についてはドキュメントを読むのが良さそうです。
https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required
Catch-all Routes
pages/posts/[...id].js
とすると、以下のようなものにマッチするようになります。
/posts/a
/posts/a/b
/posts/a/b/c
// getStaticPaths return [ { params: { // Statically Generates /posts/a/b/c id: ['a', 'b', 'c'] } } ] export async function getStaticProps({ params }) { // params.id will be like ['a', 'b', 'c'] }
404 ページをカスタマイズする場合
pages/404.js
を作成すると良い
export default function Custom404() { return <h1>404 - Page Not Found</h1> }
API を叩く際の注意点
Next.js を使えば、サーバーサイドのコードも実装できますが、基本的に、API は JS で実装しないほうが良さそうです。
getStaticProps や getStaticPaths といったメソッドは、ビルド時に実行されることになります。ビルド時に API が叩けない状態だと、解決に失敗します。つまり、 Next で API も実装して、さらにクライアントサイドも実装する場合、 getStatisProps
などを解決刷る時に困ります。
解決豊富としては、 getStaticProps や getStaticPaths の中では直接 DB などに接続することです。コードはクライアントに公開されないので、これらのメソッドの中では直接DBを読み書きしても問題ありません。
詳細はドキュメントを参照してください。
https://nextjs.org/learn/basics/api-routes/creating-api-routes
次回
次回はデプロイをやっていきます。