🧏‍♂️

Next.js を Pages Router から App Router に移行するときにやったこと

2023/06/18に公開

Next.js13 で導入された App Router, Route Handler に移行するためにやったことをざっくりまとめます。

前提

  • Chakra UI, next-seo を使用している想定
  • next/link, next/image, next/scriptについては省略します
  • Next.js v13.4.6
  • Chakra UI v2.7.0

App Router(app directory)

_app.tsLayoutlayouts.tsx にする

before

_app.tsx
import { Box, ChakraProvider } from '@chakra-ui/react'
import { NextPage } from 'next'
import { DefaultSeo } from 'next-seo'
import { ReactElement, ReactNode } from 'react'
import Head from 'next/head'

import type { AppProps } from 'next/app'

type NextPageWithLayout<P = object, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}

export default function App({ Component }: AppPropsWithLayout) {
  const getLayout = Component.getLayout ?? ((page) => page)
  
  return (
      <ChakraProvider>
        <Head>
	  <title>title</title>
	  <meta name="description" content="description" />
	  <link rel="icon" href="/favicon.ico" />
	  <meta
	    name="viewport"
	    content="width=device-width,initial-scale=1"
	  />
	</Head>
        <DefaultSeo {...seo} />
        {getLayout(
          <Box as="main">
            <Component {...pageProps} />
          </Box>
        )}
      </ChakraProvider>
  )
})

after
まずはapp/layout.tsxを作成

layout.tsx
import { ReactNode } from 'react'

import { Providers } from './providers'

export const metadata = {
  title: 'title',
  // 以下のように template を使用すると、他のレイアウトで title を設定時に `title | AppName` という形になる
  // title: {
  //   template: '%s | AppName',
  // },
  description: 'description',
}

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="ja">
      <body>
        <main>
          <Providers>{children}</Providers>
        </main>
      </body>
    </html>
  )
}
export const metadata = {
  viewport: {
    width: 'device-width',
    initialScale: 1,
    maximumScale: 1,
  },
}

app/providers.tsxを作成

providers.tsx
'use client'

import { CacheProvider } from '@chakra-ui/next-js'
import { ChakraProvider } from '@chakra-ui/react'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <CacheProvider>
      <ChakraProvider>{children}</ChakraProvider>
    </CacheProvider>
  )
}

favicon, OGP画像, apple-touch-icon を設定

Pages Router では next-seo で設定していましたが、 App Router ではそれぞれ app/icon.*, app/opengraph-image.*, apple-touch-icon.* というファイルを設置すれば設定完了です

useRouter() の pathname, asPath を書き換える

  1. import を next/router から next/navigation にする
- import { useRouter } from 'next/router'
+ import { useRouter } from 'next/navigation'
  1. pathname, asPath を usePathname() にする
- const router = useRouter()
- const path = router.pathname
+ const path = usePathname()

// クエリパラメーターは useSearchParams() を使用して取得する
+ const searchParams = useSearchParams()
+ const search = searchParams.get('search')
  // URL -> `/dashboard?search=my-project`
  // `search` -> 'my-project'

Route Handler

Basic認証をする middleware用API の場合

before

src/pagees/api/auth.ts
import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(_: NextApiRequest, res: NextApiResponse) {
  res.statusCode = 401
  res.setHeader('WWW-authenticate', 'Basic realm="Secure Area"')
  res.end('Basic Auth Required')
}

after

src/app/api/auth/route.ts
export async function GET() {
  return new Response('Basic Auth Required', {
    status: 401,
    headers: {
      'WWW-authenticate': 'Basic realm="Secure Area"',
    },
  })
}

Discussion