Next.js v10 から v14にアップデートする

はじめに

タイトルの中身に入る前にまず、このブログの構成を簡単に説明すると、 Next.js で作ったアプリケーションを netlify 上にデプロした簡易なブログ構成を取っています。

今回バージョンアップをしようと思ったきっかけとして、このブログをもう少しアクティブにアウトプットの場として利用していきたいとなったことをきっかけに、せっかくならログを取りたいということで、Google Analyticsを導入しようと思いました。 その時に、あまりにも古いバージョンを使っているために、一部コードが動かない部分があったため、意を決してバージョンアップを図りました。 今回はこのブログが対象となった移行範囲のみを対象としています。

余談ですが、このブログを今の構成に変えたのが2021年4月頃で、その時のNext.jsのバージョンは 10.1.2、今のLatest Versionが(2023/11/08時点で) 14.0.1 であり、メジャーバージョンが4つも古くなっている状態だったこととに衝撃を受けました。。。

移行手順

ライブラリのバージョンアップデート

基本的には、公式が用意している各バージョンからのアップグレードについてのドキュメント を参考に作業を進めました。

このブログは npmを利用しているため

npm i next@latest react@latest react-dom@latest eslint-config-next@latest

を叩くことで、必要なバージョンアップデートは終了します。 適宜必要に応じて npm audit を実行し、あまりにも古いバージョンがあれば、追加でアップデートすることでライブラリのバージョン更新については終了です。

Link Componentの廃止

Next.jsの13以降から、<Link> コンポーネントの仕様が変わったため、ビルド時にエラーが出るようになりました。 具体的には、<Link> コンポーネントのレンダーの際に元々、HTML要素の <a> タグを含めていなかったようですが、12.2以降から含まれるようになったため、こちらの移行が必要になります。

The <Link> Component no longer requires manually adding an <a> tag as a child. This behavior was added as an experimental option in version 12.2 and is now the default. In Next.js 13, <Link> always renders <a> and allows you to forward props to the underlying tag.

import Link from 'next/link'

// Next.js 12: `<a>` has to be nested otherwise it's excluded
<Link href="/about">
  <a>About</a>
</Link>

// Next.js 13: `<Link>` always renders `<a>` under the hood
<Link href="/about">
  About
</Link>

あとから気づいたのですが、公式で移行時のスクリプトが用意されているようで、以下を叩くことで自動で修正してくれるので便利です。

npx @next/codemod@latest new-link .

next export コマンドの廃止

元々、このコマンドでデプロイしていたのですが、下記の通りv14からdeprecatedになりました。 この機能を引き続き利用するためには、 next.config.js ファイルに一行追加が必要になります。

The next export command is deprecated in favor of output: 'export'. Please see the docs for more information.

module.exports = {
  output: 'export',
};

おまけ(next-mdx-remoteのアップデート)

以上が Next.js 自体の移行に際して発生した作業でした。 自分の場合これに加えて、ブログ自体で利用していたライブラリのバージョンアップデートに伴う変更が発生したので、そちらも合わせて紹介します。

next-mdx-remote はMarkdownファイルの拡張である mdx を利用するためのライブラリです。 私のブログでは、タイトルは公開日などを記事ファイルに含めるために、このライブラリを使っていたためこちらも移行が必要になりました。

Next.js を14にしたことで、next-mdx-remoteも最新バージョン近くまでアップデートする必要がありました。

それに伴い発生したのは以下です。

  • render-to-string が廃止になり、 serialize に変更
  • hydrate の廃止
  • 型が、 MdxRemote.Source から、 MDXRemoteSerializeResult に変更
  • markdownの拡張のために、GFM(GitHub Flavored Markdown)を追加する。

それぞれ詳細を説明します。

render-to-string が廃止になり、 serialize に変更

// 移行前
const mdxSource = await renderToString(content, { components, scope: data });

// 移行後
const mdxSource = await serialize(content, {
  scope: data,
});

元々 renderToString にてmdxSourceを生成していたところが、serialize を使う形に変更されています。

移行前に渡していた components は、YoutubeのURLからEmbedしてくれるための拡張コンポーネントです。 こちらは後ほど、別の箇所に移行されています。

hydrate の廃止

もともと、hydrate を利用して、レンダーするためのコンポーネントを生成していたのですが、 MDXRemote というコンポーネントにPropsという形で source などを渡す形になりました。

// 移行前
import hydrate from "next-mdx-remote/hydrate";

const content = hydrate(source, { components })

が、以下のような形になります。より自然な形になりました。

// 移行後
import { MDXRemote } from 'next-mdx-remote';

<MDXRemote {...source} components={components} />

型が、 MdxRemote.Source から、 MDXRemoteSerializeResult に変更

こちらはそのままなのですが、

// 移行前
import { MdxRemote } from 'next-mdx-remote/types';

export type Props = {
  source: MdxRemote.Source;
};

としていたところから、

// 移行後
import { MDXRemoteSerializeResult } from 'next-mdx-remote';

export type Props = {
  source: MDXRemoteSerializeResult
};

という形に変更になっています。 細かいですが、'next-mdx-remote/types' という形でexportされなくなり、 next-mdx-remote に変更されています。

markdownの拡張のために、GFM(GitHub Flavored Markdown)を追加する

これは自分の理解がまだ完全ではないのですが、元々不要だったMarkdownの拡張が必要になりました。

具体的には、

【コンピューターサイエンス】 数体系(Number System)の基本 で使っている表が崩れてしまいました。

具体的に何が拡張で、何がデフォルトかは普段GithubのMarkdownに慣れすぎてしまっていて、よく分かっていなかったのですが、テーブルや、タスクリスト(チェックボックスのチェックリスト)などが、デフォルトではなく、extensionという位置づけのようです。

詳しくはremark-gfmのリポジトリを参照して確認ください。

話を戻して、これを追加しないとテーブルが描画されなかったため拡張を入れることにしました。 といっても非常に簡単で、先程の serialize の引数にオプションとしてpluginを渡すだけです。

npm install remark-gfm

remark-gfm をインストールした後に、

import remarkGfm from 'remark-gfm';

const mdxSource = await serialize(content, {
  parseFrontmatter: true,
  mdxOptions: {
    remarkPlugins: [remarkGfm],
  },
  scope: data,
});

として mdxOptionsremarkPlugins として配列を期待しているので、こちらに remarkGfm を渡します。

これで、先程のMarkdownの拡張が完了しました。 ちなみに私が移行時は(2023/10/13現在)、最新バージョン remark-gfm@4.0 にてエラーが発生していたため、その場合 remark-gfm@3.0 を利用することで回避することができるようです。

以上になります。