記事一覧に戻る

Next.js 13へのアップテート手順

はじめに

Next.js 13が2022年10月リリースされました。新たに実装されたappディレクトリが大きな話題のようですね。まだベータ版とはいえ、Youtube等で 多くの方が紹介動画を出していて、勢いが感じられます。

このサイトも、2022.10.8にNext.jsに移行しています。私にとっては、ようやく移行が済んだと思っていたところに、1か月も経たないうちにやってきたバージョンアップです。<Link />や<Image />コンポーネントは後方互換無しの更新があるとアナウンスされてましたし、なかなかアップグレードに踏み切れずにいました。

しかし、早いものでそろそろ半年が経とうとしています。ぐずぐずしていると次の更新が来てしまいそうです。前向きに捉えれば、ベータ版として導入されたappディレクトリは、Server Componentを使った新しいサーバサイドレンダリング方法で、最先端の技術に触れられるチャンスでもあります。

という訳で、重たい腰を上げてNext.js 12から13にバージョンアップをしました。アップデートの手順や、後方互換無しのコンポーネントへの対応方法をまとめましたので、同じくアップデートに悩んでいる方の参考になると幸いです。

アップグレード対応の負荷は、サイトの大きさや使っている機能に左右されると思うので無責任なことは言えませんが、、、あくまで私の場合ですが、思ったより簡単にできました。

この記事の説明範囲

バージョンについて

私がNext.js 12からNext.js 13にアップグレードした時の手順を説明します。基本、以下の公式の手順どおりに実施しています。

12より古いバージョンから上げる場合は対象にしていません。

新機能について

後方互換が切れた機能の対応方法を記載します。新機能の紹介はありませんのでご了承ください。こちらは試したら別途記事にします。

現在の環境

Node.jsのバージョン

> node -v
v16.13.0

Next.js 13では、Node.jsの最低バージョンが14.6.0に上がりました。

The minimum Node.js version has been bumped from 12.22.0 to 14.6.0

Next.js, React.js関連のパッケージ

{
  "eslint": "^8.25.0",
  "eslint-config-next": "^12.3.1",
  "next": "12.2.5",
  "react": "18.2.0",
  "react-dom": "18.2.0",
}

Next.js13では、Reactの最低バージョンが18.2.0に上がりました。最新のバージョン(2023.3時点)ですね。

The minimum React version has been bumped from 17.0.2 to 18.2.0.

アップグレード

gitでブランチ切替

万が一の時に、元に戻せるよう別ブランチでアップグレードします。

git branch
* dev
  main

アップデートコマンド

公式が案内しているコマンドでインストールします。

最新版でインストールするコマンドなので、もしNext.js14移行がリリースされている場合、実行せずブラウザバックしてください。

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

インストール完了後のバージョンは以下の通りです。Next.jsは13.2.4になりました。併せて、Next.js用eslintのconfigも更新されています。私の場合、Reactは最新版だったのでReactとReact-Domの更新はありませんでした。

{
  "eslint": "^8.25.0",
  "eslint-config-next": "^13.2.4",
  "next": "^13.2.4",
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
}

更新内容

"next/link"のLinkコンポーネントに後方互換無しの修正が入っています。子要素のaタグは不要になり、aタグのhrefは直接Linkコンポーネントのpropとして記述をすることになります。

例えばこのページの上部と下部にある「記事一覧に戻る」は、以下の記述になっています。

// Next.js 12
<Link href="/blog">
  <a className={styles.back}>記事一覧に戻る</a>
</Link>

....既にLinkにhrefついてますね笑

Next.js 12でも、Linkにhrefの指定も、実験機能として実装されていたとのことです。私の場合、あまり考えずにバンバン使っていたので、 幸い今回のアップグレードで直す箇所は少なくなりそうです。

なにはともあれ、Next.js 13では、aタグは自動補完されるので、以下のように記述にする必要があります。

// Next.js 13
<Link href="/blog" className={styles.back}>記事一覧に戻る</Link>

もしくは、legacyBehaviorというpropを指定することで、旧バージョンの動作を強制させることも可能です。

// Next.js 13
<Link href="/blog" legacyBehavior>
  <a className={styles.back}>記事一覧に戻る</a>
</Link>

ドキュメントを見ると、legacyBehaviorが設定されていない場合、aタグのプロパティをLinkに設定出来る、と記載されています。classNameとかもそのまま移植して問題なそうですね。

Note, when legacyBehavior is not set to true, all anchor tag properties can be passed to next/link as well such as, className, onClick, etc.

ちなみに、何も直さずにnpm run devしてページを開こうとしたら、エラーになりました。

link-error

aタグを削除するか、legacyBehaviorを指定しろと言ってますね。

codemod(code-modificationの略?)と呼ばれる、自動変換バッチを公式が提供しています。

Safely removes <a> from next/link or adds legacyBehavior prop.

安全にaタグを削除するか、legacyBehavior を追加すると記載されています。

せっかくなので使ってみます。

コマンドは以下の通りです。

npx @next/codemod@latest new-link パス

「パス」には変換したいコンポーネントが入っているフォルダかファイル名を指定します。私の場合、まずはpages を対象にしたいので、 npx @next/codemod@latest new-link pagesで実行します。

@next/codemodをインストールしても良いか?と聞かれたので、yを押して進めます。なお、git stashかgit commitされていないファイルがあると、「Thank you for using @next/codemod! But before we continue, please stash or commit your git changes.」とメッセージが表示され、先に進みません。git stashかcommitしてから実行してください。

なお、 末尾に --dryを付けて実行するといわゆるdry-runになります。コードの変更なしにテスト実行してくれます。

ファイル名の羅列等、一部省略しています。ドキュメントに記載がないため意味が微妙に掴めないところもありますが、少なくともエラーが無かったことは読み取れます。 修正内容はgitでも確認できるので、問題ありません。

> npx @next/codemod@latest new-link pages          
Results:
0 errors
53 unmodified
0 skipped
6 ok
Time elapsed: 4.105seconds 

legacyBehaviorがついたものです。これはもとの作りがいまいちだったかも、、、

// 変換前
<Link href={route}>
  <button className={styles.read}>
    <a className={styles.noDecoration} onClick={() => fn()}>Read</a>
  </button>
</Link>

// 変換後
<Link href={route} legacyBehavior>
  <button className={styles.read}>
    <a className={styles.noDecoration} onClick={() => fn()}>Read</a>
  </button>
</Link>

こちらはaタグが削除されたパターンです。

// 変換前
<Link href="/blog">
  <a className={styles.back}>記事一覧に戻る</a>
</Link>

// 変換後
<Link href="/blog" className={styles.back}>
  記事一覧に戻る
</Link>

こんなものもありました。変換しなくても問題なさそうですが、legacyBehaviorがついています。これは動作確認して、手修正するかもしれません。 aタグを削除するか、legacyBehaviorをつけるか、必ずいずれかが実行されるのかもしれないですね。

// 変換前
<Link href={rel.url}>{rel.title}</Link>

// 変換後
<Link href={rel.url} legacyBehavior>{rel.title}</Link>

1箇所だけ、変更がされないファイルもありました。

<Link href={href}>
  <li className={cn} onClick={() => select()}>
    <a className={styles.link}>{text}</a>
  </li>
</Link>

これは私の作りが悪かったでしょうか、、、aタグがLinkの「孫」になっているためか、変換の対象にならなかったようです。一番の上のコンポーネントも孫になっていますが、、、間にあるHTML要素が、block要素なのかinline要素なのかで動作が異なるのかもしれませんが、そこまではドキュメントに記載はありません。

しかも、質の悪いことに、実際にページを開いてみても、「aタグを削除してください」とはエラー表示されませんでした。「Error: Hydration failed because the initial UI does not match what was rendered on the server.」と表示されていたため、解決するのに時間がかかってしまいました。

動作確認中に上記のエラーが出る場合、codemodの変換をすり抜けてしまったLinkコンポーネントが悪さをしている可能性があります。

手動でaタグが残っているLinkコンポーネントを検索し、以下のようにaタグを削除し、classをLinkに引き上げたら解決しました。classをLinkにあげるか、liにあげるかはcssの内容次第かと思います。

<Link href={href} className={styles.link}>
  <li className={cn} onClick={() => select()}>
    {text}
  </li>
</Link>

<Image /> Componentの更新

更新内容

Next.js 12の"next/image"は、"next/legacy/image"にパスが変更されています。そして、Next.js 12の"next/future/image"が、13では"next/image"に変わっています。

私はcssの適用がうまく出来なかったので、このコンポーネントは使っていませんでした。新しいバージョンではスタイル付けがしやすくなると紹介されているので、今後使用を検討します。ビルド時に毎回「next/image使って。。。?」とワーニングが出てきますしね。

next-image-to-legacy-image codemod

こちらも変換ツールが公式から提供されています。残念ながら、私は使っていないので結果の共有は出来ません。"next/image"のインポートパスを、"next/legacy/image"に書き換える、という内容になっています。Linkコンポーネントはaタグを削除したりしてくれましたが、こちらをパスを変えてくれるだけです。

コマンドは以下の通りです。

npx @next/codemod@latest next-image-to-legacy-image パス

next-image-experimental codemod (experimental)

上記のnext-image-to-legacy-imageのcodemodを実行していることが前提になりますが、新版の"next/image"に書き換えてくれるcodemodもあります。ただし、experimental(実験)ステータスなのでご注意ください。試される場合は公式のドキュメントを確認の上、実行してください。

その他の更新

ドキュメントを見る限り、後方互換が無い更新は、<Link />と<Image />コンポーネントのみのようです。

後は、"next/font"の導入や、<Script />コンポーネントがベータ版のappディレクトリもサポートするような更新が入っているようです。 また、ステータスはまだαですが、Webpackに代わる次世代バンドラ、Turbopackも使えるようになっています。名前がカッコいいですね。

このあたりの新機能は、試してみたら別途記事にしたいと思います。

最後に

バージョンを上げるだけなら、思いのほか簡単にアップデート出来ました!私の場合は、実質<Link />の修正だけで済みました。

(厳密には、まだ本番ビルドが控えています。しかし、このページを見られているということは、無事ビルドも出来たということでしょう。)

やはり、まだベータとは言え、従来のpagesディレクトに代わるappディレクトリの導入が、大きな話題の理由なのでしょうね。 これによって、getServerSidePropsgetStaticPropsといった機能を使った従来のサーバサイドレンダリングから、Server Componentsを使ったレンダリングへの移行が、今後の方針として明確になったものと私は認識しています。

私も今後の移行を見据えて、appディレクトリを試していこうと思います。

まだ日本語版は出ていませんが、最近Reactのドキュメントが刷新されました。インストールの仕方で、従来まではcreate-react-appsが案内されていましたが、Next.jsやGatsbyといったフレームワークを使ったインストールを行うように変更されています。React関連はたくさんのパッケージやフレームワークがあるので、Next.jsを名指ししてくれると心強いです。

当面のReactの課題は、初回ロード時間の短縮のようなので、もとよりサーバサイドレンダリングを実現していたNext.jsとは利害が一致するのでしょうね。

参考リンク

記事一覧に戻る