Next.js公式チュートリアル要点抜粋 Dynamic Routes
What You’ll Learn in This Lesson
- getStaticPathsを使って動的ルーティングを持つページの生成
- getStaticPropsを使って個々のブログ投稿のデータをfetchする方法
- remarkを通じてマークダウンをレンダリングする方法
- 日付文字列を綺麗に出力する方法
- 動的ルーティングにページをリンクさせる方法
- 動的ルーティングに関するいつか有用な情報
Page Path Depends on External Data
- Next.jsでは外部のデータを使用してページのパスを生成できる。
How to Statically Generate Pages with Dynamic Routes
サンプルのブログ投稿記事の場合、動的ルーティングは以下の仕様となる。
- すべての記事は
/posts/<id>
のパスを持つ、<id>
はトップレベルのposts
ディレクトリー内のマックダウンファイル名。 - サンプルのファイル名は
ssg-ssr.md
とpre-rendering.md
のため、パスは/posts/ssg-ssr
と/posts/pre-rendering
となる。
Overview of the Steps
動的ルーティング(動的パス)を生成するための流れ:
-
pages/posts
の下に[id].jsというファイル(ページ)を生成する。[]はNext.jsでは動的に変わる部分を指す。 -
pages/posts/[id].js
にページを作成するコードを書く。(いままで通り)
import Layout from '../../components/layout'
export default function Post() {
return <Layout>...</Layout>
}
- 同じページ(ファイル)内に非同期関数getStaticPathsを生成する。この関数では
id
になり得る値のリストを返す。
import Layout from '../../components/layout'
export default function Post() {
return <Layout>...</Layout>
}
export async function getStaticPaths() {
// Return a list of possible value for id
}
- 最後にgetStaticPropsをもう一度実装する必要がある。渡されたidでブログ投稿のデータをfetchする役割を持つ。(引数
params
にidが含まれる)
import Layout from '../../components/layout'
export default function Post() {
return <Layout>...</Layout>
}
export async function getStaticPaths() {
// Return a list of possible value for id
}
export async function getStaticProps({ params }) {
// Fetch necessary data for the blog post using params.id
}
全体構造図:
Implement getStaticPaths
getStaticPathsの実装例(チュートリアルの実装例):
- ページに表示するコンポーネントの作成
// pages/posts/[id].js
import Layout from '../../components/layout'
export default function Post() {
return <Layout>...</Layout>
}
- マックダウンファイル名を抽出するヘルパー関数の作成
// lib/posts.js
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$/, '')
}
}
})
}
※ getStaticPathsで使用するために戻り値のフォーマットを整形する必要がある。文字列の配列ではなく、オブジェクトの配列の形にしないといけない。また、オブジェクトにはparamsというキーを持たせる必要があり、さらに今回は[id].js
で示したようにidを取得する必要があるため、オブジェクト内にさらにidキーを持たせる必要がある。(コメントのデータ形式)さもなければgetStaticPathsは失敗する。
最後、getAllPostIds
をgetStaticPaths内で呼び出すようにする。
// pages/posts/[id].js
import { getAllPostIds } from '../../lib/posts'
export async function getStaticPaths() {
const paths = getAllPostIds()
return {
paths,
fallback: false
}
}
※Post
コンポネントの上に貼り付ける(下でも大丈夫!)
Implement getStaticProps
getStaticPaths
でid
(ファイル名)を取得できたので、それらを使ってレンダリングすべき投稿のデータをfetchする仕組みがある。(すでに経験済みのgetStaticProps
)
getStaticPropsの実装例:
- 指定idで投稿データを取得するヘルパー関数の作成,
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
}
}
-
getStaticProps
の実装
pages/posts/[id].js内の
import { getAllPostIds } from '../../lib/posts'
を
import { getAllPostIds, getPostData } from '../../lib/posts'
export async function getStaticProps({ params }) {
const postData = getPostData(params.id)
return {
props: {
postData
}
}
}
に変更する。**getStaticProps**
内ではヘルパー関数**getStaticPaths**
を使用している。
- 最後、コンポネント内に表示の内容を追加して、完了~
// pages/posts/[id].js
export default function Post({ postData }) {
return (
<Layout>
{postData.title}
<br />
{postData.id}
<br />
{postData.date}
</Layout>
)
}
- 以下のリンクで投稿内容を表示されれば成功!おめでとうございます~http://localhost:3000/posts/ssg-ssr
http://localhost:3000/posts/pre-rendering
Summary
全体構造図:
Render Markdown
- markdownコンテンツを表示するためにremarkライブラリーが必要だ。
npm install remark remark-html
-
lib/posts.js
にライブラリーをimportする。
// lib/posts.js
import { remark } from 'remark'
import html from 'remark-html'
- 同じファイルの
getPostData()
にremarkを使用してmarkdownをHTMLに変換する処理を追加する。
// lib/posts.js
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
}
}
-
remark
を呼び出すためにawait
を使うので、getPostData
の前にasync
キーワードを追加する必要がある。getPostData
を使用するgetStaticPropsのところも非同期仕様に置き換える。
// pages/posts/[id].js
export async function getStaticProps({ params }) {
// Add the "await" keyword like this:
const postData = await getPostData(params.id)
// ...
}
- 最後、
Post
コンポネントにcontentHtmlをレンダリングする処理を追加する。(dangerouslySetInnerHTMLについて)
// pages/posts/[id].js
export default function Post({ postData }) {
return (
<Layout>
{postData.title}
<br />
{postData.id}
<br />
{postData.date}
<br />
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</Layout>
)
}
- 以下のリンクで投稿内容にmarkdown形式の文章が表示されれば成功。http://localhost:3000/posts/ssg-ssr
http://localhost:3000/posts/pre-rendering
結果:
Polishing the Post Page
投稿ページをもっときれいに仕上げましょう
- まずタイトルを追加する。next/headをimportし、
title
タグを使用してPost
コンポネントをアップグレードしょう!
// Add this import
import Head from 'next/head'
export default function Post({ postData }) {
return (
<Layout>
{/* Add this <Head> tag */}
<Head>
<title>{postData.title}</title>
</Head>
{/* Keep the existing code here */}
</Layout>)
}
Formatting the Date
日付をフォーマットする
- まずはdate-fnsをインストールする。
npm install date-fns
- 次にファイル
components/date.js
を生成してDate
コンポネントを追加する。
import { parseISO, format } from 'date-fns'
export default function Date({ dateString }) {
const date = parseISO(dateString)
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
}
※ 日付のフォーマット形式をここを参照してください。
-
pages/posts/[id].js
にDate
コンポネントを追加して、{postData.date}
と置き換えてください。
// Add this import
import Date from '../../components/date'
export default function Post({ postData }) {
return (
<Layout>
{/* Keep the existing code here */}
{/* Replace {postData.date} with this */}
<Date dateString={postData.date} />
{/* Keep the existing code here */}
</Layout>
)
}
http://localhost:3000/posts/pre-renderingにアクセスしたら日付はJanuary 1, 2020に変わってるはずだ。
Adding CSS
cssを追加する
- 最後にcssを追加して見栄えをよくしましょう。前に追加した
styles/utils.module.css
を使用する。pages/posts/[id].js
を開きCSSファイルをimportする行を追加し、Post
コンポネントを以下のように書き換える。
// Add this import at the top of the file
import utilStyles from '../../styles/utils.module.css'
export default function Post({ postData }) {
return (
<Layout>
<Head>
<title>{postData.title}</title>
</Head>
<article>
<h1 className={utilStyles.headingXl}>{postData.title}</h1>
<div className={utilStyles.lightText}>
<Date dateString={postData.date} />
</div>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</article>
</Layout>
)
}
結果:
Polishing the Index Page
ホームページを磨いて行こう
- ホームページ(
pages/index.js
)をアップデートする第一歩は、Linkコンポネントを使って個々の投稿にリンクを追加することだ。pages/index.js
を開き、LinkおよびDate
のために以下のコンポネントをimportする。
import Link from 'next/link'
import Date from '../components/date'
- 次に
Home
コンポネントの底辺の部分を以下のように書き換えてください。
<li className={utilStyles.listItem} key={id}>
<Link href={`/posts/${id}`}>
<a>{title}</a>
</Link>
<br />
<small className={utilStyles.lightText}>
<Date dateString={date} />
</small>
</li>
- http://localhost:3000にアクセスすると記事のタイトルはリンクに変え、日付もいい感じフォーマットされる。うまく行かない場合はサンプルコードと比較してみてください。
Dynamic Routes Details
動的ルーティングの詳細
Fetch External API or Query Database
外部APIおよびデータベースへのアクセス
-
getStaticPropsと同様、getStaticPathsにもデータをfetchする処理を書ける。以下のコードでは
getAllPostIds
(getStaticPaths内で使用)外部APIエンドポイントにアクセスする例を示す。
export async function getAllPostIds() {
// Instead of the file system,
// fetch post data from an external API endpoint
const res = await fetch('..')
const posts = await res.json()
return posts.map(post => {
return {
params: {
id: post.id
}
}
})
}
Development v.s. Production
開発モード vs 本番モード
- 開発モード(
npm run dev
あるいはyarn dev
)ではgetStaticPathsはすべてのリクエスト時に実行される。 - 本番モードではgetStaticPathsはビルド時にのみ実行される。
Fallback
-
getStaticPathsで
fallback: false
を指定すると、getStaticPathsで生成可能なパスではない場合は404 pageになる。
Catch-all Routes
すべてのルートをキャッチする
- 動的ルーティングはブラケットに三点リーダー(
...
)を追加することですぺてのパスをキャッツできるようになります。たとえばpages/posts/[...id].js
で記述する場合は、/posts/a
も、/posts/a/b
も、/posts/a/b/c
などなどはすべてヒットする。 - そうしたらgetStaticPaths内でidになり得る値を配列で返す必要がある。
return [
{
params: {
// Statically Generates /posts/a/b/c
id: ['a', 'b', 'c']
}
}
//...
]
- そしてgetStaticProps内の
params.id
は配列になる。
export async function getStaticProps({ params }) {
// params.id will be like ['a', 'b', 'c']
}
- さらに詳しい情報をcatch all routes documentationで見つけられる。
Router
- もしあなたはNext.jsのルーターオブジェクトにアクセスしたいなら、next/routerからuseRouterをimportすればいい。
404 Pages
- 404ページをカスタムしたい場合は
pages/404.js
を作成する。このファイルはビルド時に静的生成される。
// pages/404.js
export default function Custom404() {
return <h1>404 - Page Not Found</h1>
}
- Error Pagesのドキュメンテーションでさらに詳細を調べることができる。
-
getStaticPropsと getStaticPathsを説明するサンプル複数あるので、ソースコードを眺めながら勉強しましょう。
• Blog Starter using markdown files (Demo)
• WordPress Example (Demo)
• DatoCMS Example (Demo)
• TakeShape Example (Demo)
• Sanity Example (Demo)
Discussion