Menu

バージョン 15

バージョン 14 から 15 へのアップグレード

Next.js バージョン 15 に更新するには、upgrade コードモッドを使用できます:

Terminal
npx @next/codemod@canary upgrade latest

手動で行う場合は、最新の Next & React RC をインストールしていることを確認してください:

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

補足:

  • ピア依存関係の警告が表示された場合、reactreact-dom を推奨バージョンに更新するか、警告を無視するために --force または --legacy-peer-deps フラグを使用する必要があります。Next.js 15 と React 19 の両方が安定版になれば、これは不要になります。
  • TypeScriptを使用している場合、一時的に React の型を上書きする必要があります。詳細は React 19 RC アップグレードガイド を参照してください。

React 19

  • reactreact-dom の最小バージョンは現在 19 です。
  • useFormStateuseActionState に置き換えられました。useFormState フックは React 19 でまだ利用可能ですが、非推奨であり、将来のリリースで削除されます。useActionState が推奨され、pending 状態を直接読み取るなどの追加プロパティが含まれています。詳細情報
  • useFormStatusdatamethodaction などの追加キーが含まれるようになりました。React 19 を使用していない場合、pending キーのみが利用可能です。詳細情報
  • React 19 アップグレードガイドでさらに詳しく読むことができます。

非同期リクエストAPI(破壊的変更)

以前は同期的だったランタイム情報に依存する Dynamic API が、現在は非同期になっています:

移行の負担を軽減するために、プロセスを自動化する codemod が利用可能であり、API は一時的に同期的にアクセスできます。

cookies

推奨される非同期の使用方法

import { cookies } from 'next/headers'
 
// 以前
const cookieStore = cookies()
const token = cookieStore.get('token')
 
// 現在
const cookieStore = await cookies()
const token = cookieStore.get('token')

一時的な同期的使用方法

app/page.tsx
TypeScript
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
 
// 以前
const cookieStore = cookies()
const token = cookieStore.get('token')
 
// 現在
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// 開発中に警告がログに記録されます
const token = cookieStore.get('token')

headers

推奨される非同期の使用方法

import { headers } from 'next/headers'
 
// 以前
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// 現在
const headersList = await headers()
const userAgent = headersList.get('user-agent')

一時的な同期的使用方法

app/page.tsx
TypeScript
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
 
// 以前
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// 現在
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// 開発中に警告がログに記録されます
const userAgent = headersList.get('user-agent')

draftMode

推奨される非同期の使用方法

import { draftMode } from 'next/headers'
 
// 以前
const { isEnabled } = draftMode()
 
// 現在
const { isEnabled } = await draftMode()

一時的な同期的使用方法

app/page.tsx
TypeScript
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
 
// 以前
const { isEnabled } = draftMode()
 
// 現在
// 開発中に警告がログに記録されます
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode

params & searchParams

非同期レイアウト

app/layout.tsx
TypeScript
// 以前
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
}
 
// 現在
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
}

同期的レイアウト

app/layout.tsx
TypeScript
// 以前
type Params = { slug: string }
 
export default function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = params
}
 
// 現在
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
}

非同期ページ

app/page.tsx
TypeScript
// 以前
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
}
 
// 現在
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'
 
// 以前
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
}
 
// 現在
import { use } from 'react'
 
type Params = Promise<{ slug: string }>
type SearchParams = { [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
}
// 以前
export default function Page({ params, searchParams }) {
  const { slug } = params
  const { query } = searchParams
}
 
// 現在
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
}

ルートハンドラ

app/api/route.ts
// 以前
type Params = { slug: string }
 
export async function GET(request: Request, segmentData: { params: Params }) {
  const params = segmentData.params
  const slug = params.slug
}
 
// 現在
type Params = Promise<{ slug: string }>
 
export async function GET(request: Request, segmentData: { params: Params }) {
  const params = await segmentData.params
  const slug = params.slug
}
app/api/route.js
// 以前
export async function GET(request, segmentData) {
  const params = segmentData.params
  const slug = params.slug
}
 
// 現在
export async function GET(request, segmentData) {
  const params = await segmentData.params
  const slug = params.slug
}

runtime 設定(破壊的変更)

runtime セグメント設定は以前、edge に加えて experimental-edge の値をサポートしていました。両方の設定は同じことを指し、オプションを簡素化するため、experimental-edge を使用するとエラーが発生します。これを修正するには、runtime 設定を edge に更新してください。これを自動的に行うコードモッドが利用可能です。

fetch リクエスト

fetch リクエストはデフォルトではもはやキャッシュされません。

特定の fetch リクエストをキャッシュするには、cache: 'force-cache' オプションを渡すことができます。

app/layout.js
export default async function RootLayout() {
  const a = await fetch('https://...') // キャッシュされない
  const b = await fetch('https://...', { cache: 'force-cache' }) // キャッシュされる
 
  // ...
}

レイアウトまたはページ内のすべての fetch リクエストをキャッシュするには、export const fetchCache = 'default-cache' セグメント設定オプションを使用できます。個々の fetch リクエストが cache オプションを指定した場合、そちらが優先されます。

app/layout.js
// これはルートレイアウトであるため、アプリ内のすべての fetch リクエストで、
// 独自の cache オプションを設定していない限りキャッシュされます。
export const fetchCache = 'default-cache'
 
export default async function RootLayout() {
  const a = await fetch('https://...') // キャッシュされる
  const b = await fetch('https://...', { cache: 'no-store' }) // キャッシュされない
 
  // ...
}

ルートハンドラ

ルートハンドラGET 関数は、デフォルトではもはやキャッシュされません。GET メソッドをキャッシュするには、ルートハンドラファイルで export const dynamic = 'force-static' のようなルート設定オプションを使用できます。

app/api/route.js
export const dynamic = 'force-static'
 
export async function GET() {}

クライアントサイドルーターキャッシュ

<Link> または useRouter によるページ間のナビゲーション時、ページセグメントは、クライアントサイドルーターキャッシュから再利用されなくなりました。ただし、ブラウザの戻る・進む操作や共有レイアウトでは引き続き再利用されます。

ページセグメントをキャッシュするには、staleTimes 設定オプションを使用できます:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
      static: 180,
    },
  },
}
 
module.exports = nextConfig

レイアウトローディング状態は引き続きナビゲーション時にキャッシュされ再利用されます。

next/font

@next/font パッケージは、組み込みの next/font に置き換えられました。インポートを安全かつ自動的にリネームするコードモッドが利用可能です。

app/layout.js
// 以前
import { Inter } from '@next/font/google'
 
// 現在
import { Inter } from 'next/font/google'

bundlePagesRouterDependencies

experimental.bundlePagesExternals は現在安定版となり、bundlePagesRouterDependencies に名前が変更されました。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 以前
  experimental: {
    bundlePagesExternals: true,
  },
 
  // 現在
  bundlePagesRouterDependencies: true,
}
 
module.exports = nextConfig

serverExternalPackages

experimental.serverComponentsExternalPackages は現在安定版となり、serverExternalPackages に名前が変更されました。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 以前
  experimental: {
    serverComponentsExternalPackages: ['package-name'],
  },
 
  // 現在
  serverExternalPackages: ['package-name'],
}
 
module.exports = nextConfig

Speed Insights

Next.js 15 では、Speed Insights の自動インストゥルメンテーションが削除されました。

引き続き Speed Insights を使用するには、Vercel Speed Insights クイックスタートガイドに従ってください。

NextRequest 地理位置情報

NextRequestgeo および ip プロパティは、これらの値はホスティングプロバイダによって提供されるため、削除されました。この移行を自動化するコードモッドが利用可能です。

Vercel を使用している場合は、代わりに @vercel/functionsgeolocation および ipAddress 関数を使用できます:

middleware.ts
import { geolocation } from '@vercel/functions'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  const { city } = geolocation(request)
 
  // ...
}
middleware.ts
import { ipAddress } from '@vercel/functions'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  const ip = ipAddress(request)
 
  // ...
}