樹脂が固まる前に

Web Frontend / Android / Designが好きな人のメモ

Next.jsでSSGしてたサイトをPages RouterからApp Routerに載せ替えた

Next.jsでSSGしてたサイトをPages RouterからApp Routerに載せ替えたので、備忘録。

App RouterにすることでRSC(React Server Component)を使えるので、いつかやらねばなと思っていました。

とはいえApp RouterでのWebアプリ構築はテストまわりなどでトラブりがちな印象ではあります。

もし、今から新規プロジェクトをやるならApp Routerにしつつ、ほぼApp Routerの機能を使わない形で始めるでしょう。

話を戻して、今までApp Routerに移行しなかったのは、Next.jsでApp Routerが登場した当初、SSGに対応していなかったからです。

それに気づかず移行作業を進めていたらうまく動かなくて萎えた覚えがあります。

以下のようにSSG対応されてからは、普通に面倒で移行をサボっていました。 nextjs.org

移行作業

手順は正直下の通りなので、ざっくり関係ある部分の作業をかいつまんでメモっておきます。 nextjs.org

方針としては、最低限App Routerで動く形にするだけにしました。

Pages RouterとApp Routerは共存でき、ページ単位で移行ができるので、少しずつやっていきました。

Layout追加

App RouterではPageファイルの他に共通部分をLayoutで管理するので、それの追加を行います。

基本的には_app.tsx_document.tsxで行っていたmetadataやGoogle Fontなど外部のリソースを読み込む部分をまとめます。

globalなCSSなどもここで読み込むと良さそうです。

import './global.css'

export const metadata: Metadata = {
  title: '...',
}

const RootLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => (
  <html lang='ja'>
    <head>
      {/* ... */}
    </head>
    <body>{children}</body>
  </html>
)

export default RootLayout

ざっくりこんな形です。

pageコンポーネントをClient Componentに移行する

pagesフォルダ以下にあるpageコンポーネントは、ほとんどがただのStatelessなコンポーネントでした。 ものによっては、それに追加で

  • 動的パス生成のためのgetStaticPaths関数
  • 静的ファイル生成のためのgetStaticProps関数

などが生えているファイルがありました。

そこからStatelessなコンポーネント部分を'use client'をつけてClient Componentとしてappフォルダ以下に移行します。

App Routerの場合は'next/router'のuseRouterがそのままでは動かないので、適宜'next/navigation'にあるHooksを使うようにしました。

ちなみにClientでしか動かないComponentでない場合は、普通にServer Componentとして定義してしまいます。

Server Componentを追加する

さっき追加したClient Componentを描画するためのServer ComponentのPageを追加します。

export async function generateStaticParams() {
  // ...
}

const Page = async () => {
  const data = await xxx
  return <ClientComponent data={data} />
}
export default Page

動的パス生成の場合はgenerateStaticParamsで行います。

Pages RouterとApp Routerとで同じページは同居できないので、pages以下の該当ページのファイルを削除してページの移行完了です。

これを各ページ分繰り返していきます。

感想

終わって見たらあっという間でした。

SSGのためにNode.jsでデータを用意していた部分も多かったので、思ったよりServer Componentへの移行は難しくなかったです。

どちらかというとClient Componentの方が少ないぐらいでした。

ただ、これがページ数の多いWebアプリだったら大変そうな感じはします。