バージョン15にアップグレードする方法
14から15へのアップグレード
Next.jsバージョン15に更新するには、upgrade codemodを使用できます。
npx @next/codemod@canary upgrade latest手動で行う場合は、最新のNextとReactのバージョンをインストールしていることを確認してください。
npm i next@latest react@latest react-dom@latest eslint-config-next@latest知っておくと良いこと
- ピア依存関係の警告が表示された場合は、
reactとreact-domを推奨バージョンに更新するか、--forceまたは--legacy-peer-depsフラグを使用して警告を無視する必要がある場合があります。Next.js 15とReact 19の両方が安定すれば、これは不要になります。
React 19
- reactおよび- react-domの最小バージョンは19になりました。
- useFormStateは- useActionStateに置き換えられました。- useFormStateフックはReact 19でも利用可能ですが、非推奨であり、将来のリリースで削除される予定です。- useActionStateが推奨され、- pending状態を直接読み取るなどの追加のプロパティが含まれています。 詳細はこちら。
- useFormStatusには、- data、- method、- actionなどの追加のキーが含まれるようになりました。React 19を使用していない場合、- pendingキーのみが利用可能です。 詳細はこちら。
- React 19アップグレードガイドで詳細をご覧ください。
補足: TypeScriptを使用している場合は、
@types/reactおよび@types/react-domも最新バージョンにアップグレードしてください。
非同期リクエストAPI(破壊的変更)
以前は同期であった、ランタイム情報に依存する動的APIが **非同期** になりました。
- cookies
- headers
- draftMode
- layout.js、- page.js、- route.js、- default.js、- opengraph-image、- twitter-image、- icon、- apple-iconの- params。
- page.jsの- searchParams
移行の負担を軽減するために、プロセスを自動化する codemodが利用可能であり、APIは一時的に同期でアクセスできます。
cookies
推奨される非同期の使用法
import { cookies } from 'next/headers'
 
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
 
// After
const cookieStore = await cookies()
const token = cookieStore.get('token')一時的な同期使用法
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
 
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
 
// After
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// will log a warning in dev
const token = cookieStore.get('token')headers
推奨される非同期の使用法
import { headers } from 'next/headers'
 
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// After
const headersList = await headers()
const userAgent = headersList.get('user-agent')一時的な同期使用法
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
 
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// After
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// will log a warning in dev
const userAgent = headersList.get('user-agent')draftMode
推奨される非同期の使用法
import { draftMode } from 'next/headers'
 
// Before
const { isEnabled } = draftMode()
 
// After
const { isEnabled } = await draftMode()一時的な同期使用法
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
 
// Before
const { isEnabled } = draftMode()
 
// After
// will log a warning in dev
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftModeparams & searchParams
非同期レイアウト
// Before
type Params = { slug: string }
 
export function generateMetadata({ params }: { params: Params }) {
  const { slug } = params
}
 
export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = params
}
 
// After
type Params = Promise<{ slug: string }>
 
export async function generateMetadata({ params }: { params: Params }) {
  const { slug } = await params
}
 
export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = await params
}同期レイアウト
// Before
type Params = { slug: string }
 
export default function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = params
}
 
// After
import { use } from 'react'
 
type Params = Promise<{ slug: string }>
 
export default function Layout(props: {
  children: React.ReactNode
  params: Params
}) {
  const params = use(props.params)
  const slug = params.slug
}非同期ページ
// Before
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
 
export function generateMetadata({
  params,
  searchParams,
}: {
  params: Params
  searchParams: SearchParams
}) {
  const { slug } = params
  const { query } = searchParams
}
 
export default async function Page({
  params,
  searchParams,
}: {
  params: Params
  searchParams: SearchParams
}) {
  const { slug } = params
  const { query } = searchParams
}
 
// After
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
 
export async function generateMetadata(props: {
  params: Params
  searchParams: SearchParams
}) {
  const params = await props.params
  const searchParams = await props.searchParams
  const slug = params.slug
  const query = searchParams.query
}
 
export default async function Page(props: {
  params: Params
  searchParams: SearchParams
}) {
  const params = await props.params
  const searchParams = await props.searchParams
  const slug = params.slug
  const query = searchParams.query
}同期ページ
'use client'
 
// Before
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
 
export default function Page({
  params,
  searchParams,
}: {
  params: Params
  searchParams: SearchParams
}) {
  const { slug } = params
  const { query } = searchParams
}
 
// After
import { use } from 'react'
 
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
 
export default function Page(props: {
  params: Params
  searchParams: SearchParams
}) {
  const params = use(props.params)
  const searchParams = use(props.searchParams)
  const slug = params.slug
  const query = searchParams.query
}// Before
export default function Page({ params, searchParams }) {
  const { slug } = params
  const { query } = searchParams
}
 
// After
import { use } from "react"
 
export default function Page(props) {
  const params = use(props.params)
  const searchParams = use(props.searchParams)
  const slug = params.slug
  const query = searchParams.query
}
 ルートハンドラー
// Before
type Params = { slug: string }
 
export async function GET(request: Request, segmentData: { params: Params }) {
  const params = segmentData.params
  const slug = params.slug
}
 
// After
type Params = Promise<{ slug: string }>
 
export async function GET(request: Request, segmentData: { params: Params }) {
  const params = await segmentData.params
  const slug = params.slug
}runtime 設定(破壊的変更)
runtime セグメント設定は、edgeに加えてexperimental-edgeという値もサポートしていました。両方の設定は同じものを指しますが、オプションを簡素化するため、experimental-edgeが使用された場合はエラーになります。これを修正するには、runtime設定をedgeに更新してください。これを自動的に行う codemod が利用可能です。
fetchリクエスト
fetchリクエストは、デフォルトでキャッシュされなくなりました。
特定のfetchリクエストをキャッシュにオプトインするには、cache: 'force-cache'オプションを渡すことができます。
export default async function RootLayout() {
  const a = await fetch('https://...') // Not Cached
  const b = await fetch('https://...', { cache: 'force-cache' }) // Cached
 
  // ...
}レイアウトまたはページ内のすべてのfetchリクエストをキャッシュにオプトインするには、セグメント設定オプションexport const fetchCache = 'default-cache'を使用できます。個々のfetchリクエストがcacheオプションを指定している場合、そちらが優先されます。
// Since this is the root layout, all fetch requests in the app
// that don't set their own cache option will be cached.
export const fetchCache = 'default-cache'
 
export default async function RootLayout() {
  const a = await fetch('https://...') // Cached
  const b = await fetch('https://...', { cache: 'no-store' }) // Not cached
 
  // ...
}ルートハンドラー
Route HandlersのGET関数は、デフォルトでキャッシュされなくなりました。GETメソッドをキャッシュにオプトインするには、Route Handlerファイルにexport const dynamic = 'force-static'のようなルート設定オプションを使用できます。
export const dynamic = 'force-static'
 
export async function GET() {}クライアントサイドルーターキャッシュ
<Link>またはuseRouterを介してページ間をナビゲートする際、ページセグメントはクライアントサイドルーターキャッシュから再利用されなくなりました。ただし、ブラウザの前後ナビゲーションおよび共有レイアウトでは引き続き再利用されます。
ページセグメントをキャッシュにオプトインするには、 staleTimes 設定オプションを使用できます。
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
      static: 180,
    },
  },
}
 
module.exports = nextConfigレイアウトとローディング状態は、ナビゲーション時に引き続きキャッシュおよび再利用されます。
next/font
@next/fontパッケージは、組み込みのnext/fontに置き換えられました。インポートを安全かつ自動的に名前変更するためのcodemodが利用可能です。
// Before
import { Inter } from '@next/font/google'
 
// After
import { Inter } from 'next/font/google'bundlePagesRouterDependencies
experimental.bundlePagesExternalsは安定し、bundlePagesRouterDependenciesに名前が変更されました。
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Before
  experimental: {
    bundlePagesExternals: true,
  },
 
  // After
  bundlePagesRouterDependencies: true,
}
 
module.exports = nextConfigserverExternalPackages
experimental.serverComponentsExternalPackagesは安定し、serverExternalPackagesに名前が変更されました。
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Before
  experimental: {
    serverComponentsExternalPackages: ['package-name'],
  },
 
  // After
  serverExternalPackages: ['package-name'],
}
 
module.exports = nextConfigSpeed Insights
Speed Insightsの自動インストルメンテーションはNext.js 15で削除されました。
Speed Insightsを継続して使用するには、Vercel Speed Insightsクイックスタートガイドに従ってください。
NextRequest Geolocation
NextRequestのgeoおよびipプロパティは削除されました。これらの値はホスティングプロバイダーから提供されるためです。この移行を自動化するcodemodが利用可能です。
Vercelを使用している場合は、代わりに@vercel/functionsのgeolocationおよびipAddress関数を使用することもできます。
import { geolocation } from '@vercel/functions'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  const { city } = geolocation(request)
 
  // ...
}import { ipAddress } from '@vercel/functions'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  const ip = ipAddress(request)
 
  // ...
}役に立ちましたか?