Next.js 13にしました
ようやくこのサイトをNext.js 13にできました。そのメモです。
webpackローダーのESM対応
いきなりNext.jsではないのですが、webpack@5.80
でESMのLoaderが指定できるようになりました。これがいちばんありがたかったポイントです。
例えば*.mts
をTypeScriptでロードするために以下のような設定をしたとしましょう。MyTsLoader
は{"type":"module"}
なパッケージとします。
1const webpackConfig = {2 module: { rules: [{ test: /\.mts$/, loader: 'MyTsLoader' }] },3};
webpack@5.79
とwebpack@5.80
それぞれでの動作は以下で確認できます:
webpack@5.79
: CodeSandbox:repl-webpack-5-79-mjs-loaderwebpack@5.80
: CodeSandbox:repl-webpack-5-80-mjs-loader
webpack@5.79
では以下のエラーになります:
1Error [ERR_REQUIRE_ESM]: require() of ES Module /workspace/ts-loader-mjs/index.mjs not supported.2Instead change the require of /workspace/ts-loader-mjs/index.mjs to a dynamic import() which is available in all CommonJS modules.
そのためwebpack@5.79
以前はCJSからESMを呼ぶなど回り道が必要でしたが、
- 2023/4/20に
webpack@5.80
でそれが不要になり - 2023/6/9に
vercel/next.js#50992
で取り込まれ - 2023/6/11に
next@13.4.5
でリリースされたことで
Next.js環境でもESMのLoaderが使えるようになりました。
MDXで書く
https://nextjs.org/docs/pages/building-your-application/configuring/mdx
ESMが楽に使えるようになったのでESMでリリースされているunified関連のパッケージも楽に使えるようになりました。
パッケージの追加
@next/mdx
@mdx-js/loader
をインストールします1。
1npm install @next/mdx @mdx-js/loader
mdx-components.jsxの追加
何もしないのであればこれでOKです。
1export const useMDXComponents = (components) => ({ ...components });
next.config.mjsの変更
このサイトの実際の設定はnext.config.mjs
を参照してください。
remark-gfm
: GFMで書けるようにしますremark-math
: 数式ブロックを認識しますrehype-highlight
: コードブロックをハイライトしますrehype-katex
: 数式ブロックをKaTeXでレンダリングしますrehype-slug
: 見出しにidを付けます
1import mdx from '@next/mdx';2import remarkGfm from 'remark-gfm';3import remarkMath from 'remark-math';4import rehypeHighlight from 'rehype-highlight';5import rehypeKatex from 'rehype-katex';6import rehypeSlug from 'rehype-slug';78const withMDX = mdx({9 options: {10 remarkPlugins: [remarkGfm, remarkMath],11 rehypePlugins: [rehypeHighlight, rehypeSlug, rehypeKatex],12 },13});1415const nextConfig = {16 // page.tsx, page.mdxがレスポンスを返します17 pageExtensions: ['tsx', 'mdx'],18};1920export default withMDX(nextConfig);
page.mdxを作って表示してみる
次のapp/mdx-sample/page.mdx
を追加して/mdx-sample
でページが表示されればOKです。
1# MDXのサンプル23これは**MDXのサンプル**です。
記事やサイトの画像を生成する
satoriというNext.jsに含まれるツールを利用して画像を生成できるようになったので、これを使っています。
/path/to/page
というページの画像を/cover/path/to/page
で生成します。
- 例えばこのページの画像はこちら:
/cover/2023/next13
- 画像を生成しているコードはこちら:
/app/cover/[[...path]]/route.tsx
- フォントは @fontsource/noto-sans-jp のフォルダから
public
フォルダに事前にコピーしておいたものをfetch
します。 - 文字出力の際に提供したフォントにない文字があるとエラーになります。
- adobe-blankを渡しておけばエラーにはならなくなりそうです。
- フォントは @fontsource/noto-sans-jp のフォルダから
<meta>
に/cover/path/to/page
を追加します。MDXでmetadataを設定するにはpage.tsx
と同様にmetadata
をexport
します。参考: configuring/mdx#frontmatter
1export const metadata = {2 title: 'Next.js 13にしました',3 openGraph: {4 images: [5 {6 url: '/cover/2023/next13',7 width: 1280,8 height: 640,9 },10 ],11 },12};
しかしこのopenGraph
の値はパスだけわかれば生成できるし、パスが変わったら修正が必要なのでこのような指定はしたくありません。layout.tsxで設定したいところですが現時点ではうまくできませんでした2。そのため後述のようにASTの時点で追加するようにしました。
MDXのASTをいじる
以下の機能を追加しました。
- コード、画像、表、数式、を
<figure>
の中に入れる<figcaption>
をつける- それぞれリンクできる3
- コードブロックに行番号を追加
- 行番号にリンクできる
- Shift+クリックやCtrl+クリックで範囲指定できる
- 数式を
\begin{align}
と\end{align}
で囲む - 数式番号にリンクできる
- YouTube動画のタイトルとリンクをキャプションに追加する
export metadata = {...}
の中にopenGraph
を追加する- MDXの中の
export metadata = {...}
のブロックのnode.type
はmdxjsEsm
node.data.estree
にJavaScriptのASTがあるのでmetadata
をexport
するExportNamedDeclarationがあればそれをいじるnode.value
にコードが文字列で入っていますがそちらを変更してもmetadataは変更されません。- 今考えると
node.value
をいじって4node.data.estree
を更新する方が楽でした。
- MDXの中の
機能追加は以下のような関数をnext.config.mjs
のremarkPlugins
とrehypePlugins
に追加すれば動きます。
1// import type { Root } from 'mdast'; // MarkdownのASTをいじる場合2import type { Root } from 'hast'; // HTMLのASTをいじる場合34export const rehypePlugin = async (tree: Root, file: { path: string }) => {5 // ここでtreeをいじる6 return tree;7};
Footnotes
-
@mdx-js/react
は*.mdx
をimport
する必要があれば追加します。 arrow_insert -
middlewareでヘッダにパスを追加してheaders()でそれを読むなど言及されていますがうまくいきませんでした。 arrow_insert
-
idを
position:absolute
で実際の要素よりも上に表示した要素につけるとリンクから飛んできたときにヘッダーで隠れないようにできます。 arrow_insert -
最初の
metadata = {
の直後にopenGraph:{...},
を入れます。 arrow_insert