📝

ブログをNext.js 13に移行した

React Server Component / App Routerに対応

(2023/10/05 追記)Astroで書き直した

はじめに

🎉 Next.js 13.2がリリースされ、App Router (beta) & React Server Component(以下RSC)が実用できそうな感じになってきたため、このサイトでも移行した。Metadata API、Route Handler等も同時に導入した。

やったこと

(一般的な設定についてはドキュメント等を参照)

next.config.js

next.config.mjs
/** @type {import('next').NextConfig} */
let nextConfig = {
reactStrictMode: true,
experimental: {
serverComponentsExternalPackages: [
"playwright",
"svgo",
"plaiceholder",
"@plaiceholder/next",
"fetch-site-metadata",
],
scrollRestoration: true,
appDir: true,
},
images: {
formats: ["image/avif", "image/webp"],
domains: ["asciinema.org", "raw.githubusercontent.com"],
},
};

experimental.serverComponentsExternalPackagesいう項目がけっこう重要。Markdownの処理にNode.jsのライブラリを使用している場合、該当ライブラリをここに列挙する必要がある。

あと関係ないがnext.config.jsドシドシESM.mjs)で書こう。

Markdown / MDXの処理

公式ブログ見る限り、@next/mdxnext-mdx-remotecontentlayer三者は現時点(2023/03/03)でRSCに対応している。

GitHub - hashicorp/next-mdx-remote: Load MDX content from anywhere
Load MDX content from anywhere. Contribute to hashicorp/next-mdx-remote development by creating an account on GitHub.
github.com
GitHub - hashicorp/next-mdx-remote: Load MDX content from anywhere
Contentlayer makes content easy for developers
Contentlayer is a content SDK that validates and transforms your content into type-safe JSON data you can easily import into your application.
contentlayer.dev
Contentlayer makes content easy for developers

next-mdx-remote/rsc使ったところ、入出力の仕様が微妙に変更されていて戸惑った。compileMDXJSXを直接吐かせるか、<MDXRemote>コンポーネントにMarkdown / MDXを食わせるか選べるようだ(おそらくどちらでも出力に差はない)。

そのほか、frontmatter型を付けられるようになった。

lib/compiler.ts
import { compileMDX } from "next-mdx-remote/rsc";
...
import MDXComponent from "components/MDXComponent";
...
const compiler = async (source: string) => {
const result: Promise<{
content: JSX.Element;
frontmatter: {
slug: string;
title: string;
date: string;
description: string;
tags: string[];
};
}> = compileMDX({
source,
components: MDXComponent,
options: {
...
},
});
return result;
};
export default compiler;

Route HandlerによるOpen Graph(OG)画像生成

Next.js 13.2で従来のAPI Routesを代替するRoute Handlerが登場したため、ついにOG 画像生成使っていたpagesディレクトリを完全に廃止1できるようになった。

Routing: Route Handlers | Next.js
Create custom request handlers for a given route using the Web's Request and Response APIs.
nextjs.org
Routing: Route Handlers | Next.js

Metadata API

同じくNext.js 13.2で登場。サイトマップの生成に使った。

src/app/sitemap.ts
import type { MetadataRoute } from "next";
import { globby } from "globby";
import { dateConverter } from "lib/build";
import { HOST } from "lib/constant";
// Article index file
import postIndex from "share/index.json";
import type { PostData } from "lib/interface";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const constPaths = await globby(["src/app/**/page.tsx", "src/app/page.tsx"], {
ignore: ["src/app/api/*.tsx", "src/app/grad_essay/**", "src/app/**/[*/**"],
});
const constPageEntries = constPaths.map((filePath) => {
const constPageEntry = {
relpath: filePath.replace("src/app/", "").replace("page.tsx", ""),
lastmod: "",
};
return constPageEntry;
});
const blogposts = postIndex.articles.blog;
const blogTags = postIndex.tags.blog;
const blogEntries = blogposts.map((post: PostData) => {
const blogEntry = {
relpath: `blog/posts/${post.data?.slug}`,
lastmod: dateConverter(post.data?.date),
};
return blogEntry;
});
const blogTagEntries = blogTags.map((tag: string) => {
const blogTagEntry = {
relpath: `blog/tag/${tag}`,
lastmod: "",
};
return blogTagEntry;
});
const sitemapEntries = constPageEntries.concat(blogEntries, blogTagEntries);
return sitemapEntries.map((entry) =>
entry.lastmod !== ""
? {
url: `https://${HOST}/${entry.relpath}`,
lastModified: entry.lastmod,
}
: {
url: `https://${HOST}/${entry.relpath}`,
},
);
}

所感

もともと完全な静的サイトなので、実のところそれほどパフォーマンスに変化はない。バンドルサイズは多少小さくなったかもしれないが。

ビルド結果
ビルド結果

ちなみにLighthouseのスコアはこんな感じ:

トップ
トップ
プロフィール
プロフィール
この記事
この記事

脚注

  1. 厳密には404.jsがまだ残っているが、こちらで書かなくても処理されるのでpagesディレクトリ自体は削除可能