logo

アルパカログ

Next.js バージョン13へのアップデート方法と解説

Next.js バージョン13ではこれまでのバージョンに比べて割とドラスティックな変更内容が多くなっています。

中でも app ディレクトリの採用とそれにまつわるページコンポーネントの仕組みは、ある程度その概念を理解して対応する必要があります。

とはいえ、待望のレイアウトが使えるようになったりと、私たちユーザーにもその進化が感じられる楽しいアップデートであると言えます。

この記事では、Next.js バージョン13へのアップデートのために必要な変更箇所を、簡単な新機能の解説を交えながら説明します。

目次

Next.js 13の主な変更内容

Next.js 13 の新機能のうち、対応が必要になるのは下記です。

  • app ディレクトリ(ベータ版)
    • layout ファイル
    • page ファイル
    • head ファイル
  • next/link コンポーネント

以降はこれらの点について解説していきます。

全ての新機能については下記のオフィシャルブログをご覧ください。

公式のアップグレードガイドは下記をご覧ください。

下記ツイートのスレッドもわかりやすいので良かったら参考にしてください。

前準備

Node のバージョンが16.8以上であることを確認します。

16.8未満だった方はこの機会にバージョン18へのアップデートもご検討ください。

Next.js とその関連ライブラリをアップデートします。

お使いのパッケージソフトに合わせてコマンドを選んで実行してください。

npm install [email protected] [email protected] react-[email protected] eslint-config-[email protected]
# or
yarn add [email protected] [email protected] react-[email protected] eslint-config-[email protected]
# or
pnpm update [email protected] [email protected] react-[email protected] eslint-config-[email protected]
Next.js を最新バージョンにアップデートする

next.config.js で新機能の app ディレクトリを有効にします。

app ディレクトリによってこれまでよりも高速になりますが、現状はまだ実験的な機能であるため使用は自己責任でお願いします。

next.config.jsexperimental.appDir: true を追加します。

const nextConfig = {
  experimental: {
    appDir: true,
  },
};
next.config.js に appDir を追加する

これで準備は整いました。

以降は app ディレクトリと next/link コンポーネントの移行方法について説明していきます。

app ディレクトリ

Next.js 13ではページコンポーネントは、プロジェクトルートに新しく app ディレクトリを作成してその下に配置します。

ページコンポーネントはそのまま移動すれば良いというわけではなく layout.tsx , page.tsx , head.tsx の3ファイルに分割する必要があります。

また、動的ルーティングに関わるディレクトリ構成も変わっています。

動的ルーティングとディレクトリ構成

app ディレクトリ有効後も、ディレクトリ構成をベースにした基本的なルーティングは変わりませんが、動的ルーティング部分のみ変更が必要です。

例えば pages/blog/index.tsx は app ディレクトリ有効後は app/blog/ にファイルを配置します。

しかし例えば pages/blog/[slug].tsx のような動的ルーティングは、 app/blog/[slug]/ のように動的ルーティングに対応したディレクトリを作成してその下にファイルを配置することになります。

APIルートは現状の pages/api のまま変わりません。

そのほかの componentsstyles といったディレクトリ、ファイルはどこに配置しても構いません。

create next-app 初期化直後のプロジェクトを見てみると module.cssapp 以下の page.tsx と同じ階層に配置しているようです。

layout.tsx

Next.js 13 ではページの共通部分をレイアウトとして定義できるようになりました。

最初に root レイアウトとして app/layout.tsx を作成します。

export default function DashboardLayout({
  children, // will be a page or nested layout
}: {
  children: React.ReactNode,
}) {
  return (
    <section>
      {/* Include shared UI here e.g. a header or sidebar */}
      <nav></nav>

      {children}
    </section>
  );
}
root レイアウトファイル app/layout.tsx を作成する

子階層で個別のレイアウト使いたい場合はそのディレクトリに layout.tsx を配置します。

同じ階層に layout.tsx が存在しない場合は app/layout.tsx が使用されます。

head.tsx

バージョン12以前は next/head を使ってページコンポーネントなどに head タグの内容を記述していましたが、バージョン13では next/head の代わりに各階層に head.tsx を配置します。

async function getPost(slug) {
  const res = await fetch('...');
  return res.json();
}

export default async function Head({ params }) {
  const post = await getPost(params.slug);

  return (
    <>
      <title>{post.title}</title>
    </>
  )
}
各階層に head タグの内容を記述する head.tsx を作成する

head タグの内容が動的に変わる場合は、上記の例のようにデータ取得メソッドを head.tsx ファイル内に定義することもできます。

1つ注意点として、バージョン12以前は viewport に関する記述が自動的に入っていましたが、バージョン13では自動で入らなくなったので自前で viewport を定義する必要があります。

<meta name="viewport" content="width=device-width,initial-scale=1.0" />
viewport の meta タグは自分で head.tsx に追加しよう

head.tsx の内容が繰り返しになってしまう場合には、コンポーネントにまとめて定義しておいて呼び出す方法が便利です。

const DocumentHead = ({ title = '', description = '' }) => {
  return (
    <>
      <title>{title}</title>
      <meta name="viewport" content="width=device-width,initial-scale=1.0" />
      <meta
        name="description"
        content={description ? description : 'default description...'}
      />
    </>
  )
}
components/document-head.tsx のように共通化しておく

こうしておけば head.tsx ではコンポーネントを呼び出すだけになりコードの見通しが良くなります。

import DocumentHead from '../../components/document-head'

const BlogHead = () => (
  <DocumentHead title="Blog" />
)

export default BlogHead
head.tsx の中ではコンポーネントを呼び出すだけ
page.tsx

Next.js 13からはサーバーコンポーネントクライアントコンポーネントという概念が追加されました。

page.tsx に限らず app ディレクトリ以下の .tsx ファイルは全てサーバーコンポーネントかクライアントコンポーネントかのどちらかとして扱う必要があり、デフォルトではサーバーコンポーネントとして扱われます。

基本的にはサーバーコンポーネントを用い、必要に応じて部分的にクライアントコンポーネントを使用するといったイメージです。

サーバーコンポーネントとクライアントコンポーネントに分ける

それではまず、移行前のページコンポーネントを元に page.tsx を作成します。

getStaticProps() メソッドは無くなったので、JSXを返す1つのメソッドとして定義します。

データ取得などは head.tsx と同様に別メソッドを定義して呼び出します。

その際は必要に応じて async function とするのを忘れないようにしましょう。

export default function Page({ params, searchParams }: {
  params: { slug: string },
  searchParams: { id: string },
}) {
  return (
    <>
      <p>{params.slug}</p>
      <p>{searchParams.id}</p>
    </>
  );
}
page.tsx はこれまでと変わらない

page.tsx のうち、下記に該当する部分をクライアントコンポーネントにするため別ファイルに分割します。

  • onClick()onChange() などを使いインタラクティブな処理を行っている
  • useState()useEffect() などのフックを用いた処理を行っている
  • window などブラウザでのみ使用可能な API を用いている
  • 上記3つのいずれかに当てはまるカスタムコンポーネントを用いている
  • React クラスコンポーネントを使用している

詳細は下記ドキュメントをご覧ください。

コンポーネントファイルをクライアントコンポーネントとして扱うにはファイルの先頭に 'use client' を追加します。

'use client'

import React, { useState, useEffect } from 'react'
// ...
クライアントコンポーネントとして扱うには先頭に ‘use client’ を追加する

これでクライアントコンポーネントとして扱えるようになりました。

クライアントコンポーネントの定義はファイル単位なので、どのように分割するかはよく考える必要があります。

useRouter の置き換え

useRouter フックは next/router からインポートしていましたが、Next.js 13 では next/navigation に変更になりました。

同時に router.events は現状サポートされていないので、もし使用している場合は置き換える必要があります。

現在のURLパスを得る useRouter フックの asPathusePathname() を使って置き換えることができます。

詳細は下記の公式リファレンスをご覧ください。

参考までに私が行った置き換え差分も置いておきます。

getStaticPaths() の置き換え

blog/[slug].tsx のような動的ルーティングを用いたページコンポーネントでは、動的なパラメータが取りうる値を getStaticPaths() メソッドで定義しておくことができました。

export function getStaticPaths() {
  return {
    paths: ['/blog/hello-world', '/blog/my-first-post'],
  }
}
以前は getStaticPaths() でパスの配列を paths として返していた

app ディレクトリでは代わりに generateStaticParams() を使います。

ただし、返すべき値の内容と構造が変わっているので注意してください。

export async function generateStaticParams() {
  return [{ slug: 'hello-world' }, { slug: 'my-first-post' }]
}
Next.js 13 の app ディレクトリではパラメータだけを配列にして返す

未定義の動的パラメータでアクセスした際に404エラーとしたい場合は page.tsxexport const dynamicParams = false を定義します。

が、現状こちらは不具合がありそうなことに気づいたので下記で報告しています。

ℹ️
2022年11月29日追記:下記PRで対応いただけました。
ISRのrevalidate

ISRを使用するには、以前は getStaticProps(){ revalidate: 60 } のように返していました。

Next.js 13 の app ディレクトリでは page.tsx もしくは layout.tsxexport const revalidate = 60 のように定義します。

時間以外の設定の詳細は下記公式リファレンスをご覧ください。

以上で page.tsx の移行に必要な変更が完了しました。

next/link コンポーネント

Next.js 13 では next/linkLink コンポーネントが変更になりました。

以前のバージョンでは下記のように記述していました。

<Link href="/blog/[slug]" as={`/blog/${slug}`} passHref>
  <a>{postTitle}</a>
</Link>
以前のバージョンではこのように記述していた

Next.js 13 では Link コンポーネントの子要素の a タグが不要になりました。

また、 Link コンポーネントの aspassPref といったプロパティも廃止となりました。

上記の Link コンポーネントは13では下記のようになります。

<Link href={`/blog/${slug}`}>
  {postTitle}
</Link>
Next.js 13では子要素の a タグと Link コンポーネントのいくつかのプロパティが廃止された

その他の詳細は下記の公式リファレンスをご覧ください。

おわりに

最後に、私が開発しているソフトウェア easy-notion-blog を Next.js 13 にアップデートした際の全差分を置いておきます。

参考になれば幸いです。

以上、長くなりましたが Next.js バージョン13へのアップデート方法を解説しました。

Next.js 13 への移行は大変ですが、それはそれだけ挑戦的でワクワクさせてくれる機能が多いということを意味します。

ぜひ Next.js 13 へアップデートして新しい機能を楽しんでくださいね。