Next.jsチュートリアルの公式作例を、TypeScriptも加えて、一からつくってみるシリーズの第3回です。アプリケーション内のディレクトリに収めたマークダウンファイルからデータを読み込んで、トップページに表示してみます。
プリレンダリングとは
プリレンダリングはNext.jsのデフォルトで、各ページはあらかじめレンダリングされます。ページのHTMLをクライアント側のJavaScriptにすべてつくらせるのでなく、Next.jsが静的な処理は先に済ませておくということです。プリレンダリングは、パフォーマンスとSEOを高めるのに役立つでしょう。
生成されたページそれぞれに関連づけられているのは、必要最小限のJavaScriptコードです。ブラウザがページを読み込むと、そのJavaScriptコードが実行され、完全にインタラクティブになります(「ハイドレーション」と呼ばれます)。
Next.jsには、つぎのふたつのプリレンダリングの仕方があります。違いは、いつページのHTMLをつくるかです(「Two Forms of Pre-rendering」参照)。
- 静的サイト生成(Static Site Generation): ビルド時にHTMLを生成するプリレンダリング。そのあとは、あらかじめレンダリングされたHTMLが、リクエストのたびに再利用されます。
- サーバーサイドレンダリング(Server-side Rendering): リクエストごとにHTMLを生成するプリレンダリングです。
可能であるかぎり、静的生成が推奨されます。ページのビルドが1回で済むため、サーバーサイドレンダリングより速く処理できるからです。このチュートリアルでは、静的生成を用います。
We recommend using Static Generation over Server-side Rendering for performance reasons. Statically generated pages can be cached by CDN with no extra configuration to boost performance. However, in some cases, Server-side Rendering might be the only option.
(「Pre-rendering」)
マークダウンファイルからデータを取り出す
ブログ投稿のデータはローカルのマークダウンファイルとし、アプリケーションの新たなディレクトリ(posts
)に収めることにしましょう。つくるファイルはつぎのふたつです。GitHubの公開コードからデータをコピーしてください。
マークダウンファイルにはメタデータ(title
とdate
)が含まれています。このメタデータを取り出すライブラリがgray-matterです。つぎのコマンドでインストールしてください。
npm install gray-matter
マークダウンファイルからデータを取り出す新たなユーティリティモジュールが以下のlib/posts.ts
です(コード001)。なお、ディレクトリlib
はトップレベルにつくってください。ライブラリgray-matter
を用いた関数getSortedPostsData
が定められています。行う処理はつぎのふたつです。
- マークダウンファイルからメタデータ
title
とdate
、およびファイル名を取り出します。- ファイル名を取得するのは投稿URLの
id
として用いるためです。
- ファイル名を取得するのは投稿URLの
- 得られた投稿データは日付順に並べ替えてリストにします。
コード001■ユーティリティモジュール
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const postsDirectory = path.join(process.cwd(), 'posts');
export type PostData = {
id: string;
date: string;
title: string;
};
export const getSortedPostsData = (): PostData[] => {
// /postsディレクトリ内のファイル名を得る
const fileNames = fs.readdirSync(postsDirectory);
const allPostsData = fileNames.map((fileName) => {
// 拡張子".md"をファイル名から除く
const id = fileName.replace(/\.md$/, '');
// マークダウンファイルを文字列として読み込む
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
// gray-matterでpostのメタデータ部分を解析する
const matterResult = matter(fileContents);
// 取り出したデータにidを組み合わせる
return {
id,
...matterResult.data as {date: string; title: string},
};
});
// postを日付順に並べ替える
return allPostsData.sort((a, b) => {
if (a.date < b.date) {
return 1;
} else {
return -1;
}
});
};
なお、このユーティリティモジュールlib/posts.ts
のコードは、Next.jsの学習には大きく関わりません。コードのコメントを流し読むくらいで結構です。import
したライブラリについて知りたい方は、Noteをご参照ください。
そして、getSortedPostsData
をimport
するモジュールpages/index.tsx
に加えるのが関数getStaticProps
です。この関数をexport
すると、戻り値のプロパティがNext.jsによるページの静的生成に用いられます。そして、それらのプロパティは以下のコードのようにページのコンポーネント(Home
)に引数として渡されるのです。
If you export a function called
getStaticProps
(Static Site Generation) from a page, Next.js will pre-render this page at build time using the props returned bygetStaticProps
.
(「getStaticProps」)
import { getSortedPostsData } from '../lib/posts';
import type { PostData } from '../lib/posts';
type Props = {
allPostsData: PostData[];
};
export const getStaticProps = async () => {
const allPostsData = getSortedPostsData();
return {
props: {
allPostsData,
},
};
};
// export default function Home() {
export default function Home({ allPostsData }: Props) {
return (
<Layout home>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<h2 className={utilStyles.headingLg}>Blog</h2>
<ul className={utilStyles.list}>
{allPostsData.map(({ id, date, title }) => (
<li className={utilStyles.listItem} key={id}>
{title}
<br />
{id}
<br />
{date}
</li>
))}
</ul>
</section>
</Layout>
);
}
これで、マークダウンファイルから取り出されたメタデータが、投稿情報として日付順に表示されるようになりました。書き改めたトップページのモジュールpages/index.tsx
の記述全体は、以下のコード002にまとめます(ソース01)。
図001■トップページに表示された投稿データ
コード002■トップページのモジュール
import Head from 'next/head';
import Layout, { siteTitle } from '../components/layout';
import { getSortedPostsData } from '../lib/posts';
import type { PostData } from '../lib/posts';
import utilStyles from '../styles/utils.module.css';
type Props = {
allPostsData: PostData[];
};
export const getStaticProps = async () => {
const allPostsData = getSortedPostsData();
return {
props: {
allPostsData,
},
};
};
export default function Home({ allPostsData }: Props) {
return (
<Layout home>
<Head>
<title>{siteTitle}</title>
</Head>
<section className={utilStyles.headingMd}>
<p>[Your Self Introduction]</p>
<p>
(This is a sample website - you’ll be building a site like this on{' '}
<a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
</p>
</section>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<h2 className={utilStyles.headingLg}>Blog</h2>
<ul className={utilStyles.list}>
{allPostsData.map(({ id, date, title }) => (
<li className={utilStyles.listItem} key={id}>
{title}
<br />
{id}
<br />
{date}
</li>
))}
</ul>
</section>
</Layout>
);
}
ソース01■Next入門03 プリレンダリングとデータ取得
いよいよ次回はプログ投稿のページを動的につくって、Next.jsチュートリアルの公式作例ができ上がりです。
React + TypeScript: Next入門シリーズ
- Next入門01 チュートリアルの作例を一からつくってみる
- Next入門02 イメージとメタデータおよびCSSを扱う
- Next入門03 プリレンダリングとデータ取得
- Next入門04 動的ルートを定める