Nuxt2 のサポート終了に伴って Nuxt3 で構築し直していましたが,いろいろと問題が発生してしまったので,Next.js で作り直しました.
基本的にドキュメント通りに進め,要点のみ記載しています.
環境
- Node.js: v22.13.1
- Next.js: 15.1.6 (App Router)
@next/mdx を使う場合
frontmatter(MD / MDX ファイル上部の YAML 形式などの説明文)を使わない場合は,@next/mdx
を利用すると楽に導入できます.
導入
$ npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
Remark and Rehype
テーブルなど,いくつかの HTML 要素に対応していないため,remark-gfm
を利用する必要があります.
ソースコードをハイライトする場合は,rehype-highlight
も必要です.
$ npm i remark-gfm rehype-highlight
const withMDX = createMDX({
options: {
remarkPlugins: [['remark-gfm']],
rehypePlugins: [['rehype-highlight']],
},
})
※公式ドキュメントによると,Turbopack を使っている場合はこれらのプラグインが利用できないようです.
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
const withMDX = createMDX({
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypeHighlight],
},
})
next-mdx-remote を使う場合
導入
@next/mdx
を使うと簡単に表示できますが,Frontmatter も表示されてしまいます.
YAML での Frontmatter を利用したい場合は,next-mdx-remote
を使うと良いです.
$ npm i next-mdx-remote
const nextConfig: NextConfig = {
transpilePackages: ['next-mdx-remote'],
}
import { MDXRemote } from 'next-mdx-remote/rsc'
const content = 'This is **MDX** style.'
export default async function Page() {
return <MDXRemote source={content} />
}
サーバコンポーネントとしてレンダリングしたいため,rsc
版を読み込みます.
参考:GitHub - hashicorp/next-mdx-remote: Load MDX content from anywhere
MDX ファイルを読み込む
複数の MDX ファイルを処理したいときには,fs
や globby
などのパッケージを利用するように,との記載があります.
ターミナル上のカレントディレクトリを基準に,MDX ファイルを格納しているディレクトリのパスを指定します.
import fs from 'fs'
import path from 'path'
...
export function generateStaticParams() {
const dirPath = path.join(process.cwd(), 'src/app/content/')
const posts = fs.readdirSync(dirPath)
return posts.map((n) => {
return { slug: n.replace('.mdx', '') }
})
}
HTML 要素のカスタマイズ
import { MDXRemote } from 'next-mdx-remote/rsc'
const components = {
h2: (props: { children: string }) => (
<h2 className="font-medium text-2xl">{props.children}</h2>
),
}
export function CustomMDX(props: { source: string; [key: string]: any }) {
return (
<MDXRemote
{...props}
components={{ ...components, ...(props.components || {}) }}
/>
)
}
Frontmatter
$ npm i gray-matter
import matter from 'gray-matter'
import { CustomMDX } from '../mdx-remote'
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const slug = (await params).slug
const { data, content } = matter.read(
path.join(process.cwd(), `src/app/content/${slug}.mdx`)
)
return <CustomMDX source={content} />
}
Remark and Rehype
@next/mdx
を使う場合と同様です.
$ npm i remark-gfm rehype-highlight
import { MDXRemote } from 'next-mdx-remote/rsc'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
export function CustomMDX(props: { source: string; [key: string]: any }) {
return (
<MDXRemote
{...props}
components={{ ...components, ...(props.components || {}) }}
options={{
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypeHighlight],
},
}}
/>
)
}
このブログでは LaTeX も使った記事もあるため,以下のパッケージも利用しています.
- remark-math/packages/remark-math at main · remarkjs/remark-math · GitHub
- remark-math/packages/rehype-katex at main · remarkjs/remark-math · GitHub
OGP 画像の自動生成
画像の自動生成も簡単にできますが,ビルドしようとしたところ,以下のエラーが発生しました.
Error: export const dynamic = "force-static"/export const revalidate not configured on route "/opengraph-image" with "output: export".
下記の記述を追加しました.
export const dynamic = 'force-static'
参考:Metadata Files: opengraph-image and twitter-image | Next.js
静的サイトとして出力
いわゆる SSG として出力します.
const nextConfig: NextConfig = {
output: 'export',
}
$ npm run build
静的コンテンツを手元のサーバで動かすには,以下のように設定を変更する必要があります.
{
"scripts": {
"start": "npx serve@latest <directory name>"
}
}
$ npm start
初回のコマンド実行時は,パッケージがインストールされます.
Google サービスの導入
Google Analytics
ref. Using Google Analytics with Next.js (through next/script
) | Next.js
Google AdSense
ref. How to implement Google Adsense on App Router (Next.js) | by Kamo Tomoki | Medium
MDX ファイルへの変換
従来は MD ファイルでしたが,今回は MDX ファイルを使用するため,記事の移行にあたって全てのファイルの拡張子を変更しました.
$ for filename in *.md; do mv "$filename" "${filename%.md}.mdx"; done
Sitemap 生成
参考:Metadata Files: sitemap.xml | Next.js
Lighthouse による採点
従来の Nuxt3 の採点は以下の通り.
Mobile
Measured by Lighthouse
PC
Measured by Lighthouse
それに対し,Next.jsの採点は以下の通り.
Mobile
Measured by Lighthouse
PC
Measured by Lighthouse
パフォーマンスはかなり向上しました.
Best Practicesの点数が著しく低下していますが,原因はGoogle系サービスのAPIがDeplicated APIとなっているためのようです.
Nuxt3 運用時からの変更・改善点
Nuxt を利用していたときは,ビルド時間が非常に長いという問題が生じていました.
Nuxt2 から Nuxt3 への移行が影響したわけではなく,日本語と英語に対応させていた結果,ビルド対象のファイルが非常に多くなってしまったことが原因です.
また,Nuxt3 への移行後,MD ファイルを追加するとビルドが終わらず,新しい記事を掲載できなくなっていました.
Next.js(App Router)への移行によりビルド時間が短縮されたほか,当然ながらビルドが終わらない問題も解決しました.