React + TypeScript: Next入門03 プリレンダリングとデータ取得

Next.jsチュートリアルの公式作例を、TypeScriptも加えて、一からつくってみるシリーズの第3回です。アプリケーション内のディレクトリに収めたマークダウンファイルからデータを読み込んで、トップページに表示してみます。

# プリレンダリングとは

プリレンダリングはNext.jsのデフォルトで、各ページはあらかじめレンダリングされます。ページのHTMLをクライアント側のJavaScriptにすべてつくらせるのでなく、Next.jsが静的な処理は先に済ませておくということです。プリレンダリングは、パフォーマンスとSEOを高めるのに役立つでしょう。

生成されたページそれぞれに関連づけられているのは、必要最小限のJavaScriptコードです。ブラウザがページを読み込むと、そのJavaScriptコードが実行され、完全にインタラクティブになります(「[ハイドレーション](https://zenn.dev/hisasann/articles/what-is-hidration)」と呼ばれます)。

Next.jsには、つぎのふたつのプリレンダリングの仕方があります。違いは、いつページのHTMLをつくるかです(「[Two Forms of Pre-rendering](https://nextjs.org/learn/basics/data-fetching/two-forms#:~:text=9-,Two%20Forms%20of%20Pre%2Drendering,-Next.js%20has)」参照)。

* [**静的サイト生成**](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation)(Static Site Generation): ビルド時にHTMLを生成するプリレンダリング。そのあとは、あらかじめレンダリングされたHTMLが、リクエストのたびに再利用されます。
* [**サーバーサイドレンダリング**](https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering)(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](https://nextjs.org/docs/pages/building-your-application/rendering#pre-rendering)」)


# マークダウンファイルからデータを取り出す
ブログ投稿のデータはローカルのマークダウンファイルとし、アプリケーションの新たなディレクトリ(`posts`)に収めることにしましょう。つくるファイルはつぎのふたつです。GitHubの公開コードからデータをコピーしてください。

* [`posts/pre-rendering.md`](https://github.com/vercel/next-learn/blob/main/basics/typescript-final/posts/pre-rendering.md)
* [`posts/ssg-ssr.md`](https://github.com/vercel/next-learn/blob/main/basics/typescript-final/posts/ssg-ssr.md)

マークダウンファイルにはメタデータ(`title`と`date`)が含まれています。このメタデータを取り出すライブラリが[gray-matter](https://github.com/jonschlinkert/gray-matter#gray-matter----)です。つぎのコマンドでインストールしてください。

```terminal
npm install gray-matter
```

マークダウンファイルからデータを取り出す新たなユーティリティモジュールが以下の`lib/posts.ts`です(コード001)。なお、ディレクトリ`lib`はトップレベルにつくってください。ライブラリ`gray-matter`を用いた関数`getSortedPostsData`が定められています。行う処理はつぎのふたつです。

* マークダウンファイルからメタデータ`title`と`date`、およびファイル名を取り出します。
    - ファイル名を取得するのは投稿URLの`id`として用いるためです。
* 得られた投稿データは日付順に並べ替えてリストにします。

#### コード001■ユーティリティモジュール
```react:lib/posts.ts
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](https://nextjs.org/learn/basics/data-fetching/blog-data#:~:text=%3B%0A%7D-,Note,-%3A)をご参照ください。

そして、`getSortedPostsData`を`import`するモジュール`pages/index.tsx`に加えるのが関数[`getStaticProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props)です。この関数を`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 by `getStaticProps`.
(「[getStaticProps](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props)」)

```react:pages/index.tsx
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■トップページに表示された投稿データ
![qiita_2301003_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/c23ce7d9-1743-9ee8-356a-09a3bd66571e.png)

#### コード002■トップページのモジュール
```react:pages/index.tsx
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 プリレンダリングとデータ取得
https://github.com/FumioNonaka/react-typescript-next-example/tree/react-typescript-next-example-03

いよいよ次回はプログ投稿のページを動的につくって、Next.jsチュートリアルの公式作例ができ上がりです。


## React + TypeScript: Next入門シリーズ

* [Next入門01 チュートリアルの作例を一からつくってみる](https://qiita.com/FumioNonaka/items/37ad26dab7e5051f672e)
* [Next入門02 イメージとメタデータおよびCSSを扱う](https://qiita.com/FumioNonaka/items/4df0bfe8538eb346cad5)
* Next入門03 プリレンダリングとデータ取得
* [Next入門04 動的ルートを定める](https://qiita.com/FumioNonaka/items/dd9b21834d3634e36540)