メインコンテンツまでスキップ

Version 15

14 から 15 へのアップグレード

Next.js バージョン 15 にアップデートするには、upgrade codemod を使用できます:

Termnal
npx @next/codemod@canary upgrade latest

手動で行いたい場合は、最新の Next と React RC をインストールしてください。例えば:

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

Good to know:

  • ピア依存警告が表示された場合、reactreact-dom を推奨バージョンに更新するか、警告を無視するために --force または --legacy-peer-deps フラグを使用する必要があります。この問題は、Next.js 15 と React 19 の両方が安定版になれば必要なくなります。

React 19

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

React 19 用の型の更新

TypeScript を使用している場合は、一時的に React の型をオーバーライドする必要があります。以下は package.json に必要な型オーバーライドの例です。React 19 RC アップグレードガイドで詳細を参照してください。

{
"dependencies": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
},
"overrides": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
}
}

非同期リクエスト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
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
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
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'

// 以前
const { isEnabled } = draftMode()

// 後
// 開発中に警告が表示されます
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode

params & searchParams

非同期レイアウト

app/layout.tsx
// 以前
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
// 以前
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
// 以前
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 = 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
}
// 以前
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 設定 (破壊的変更)

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

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
// これは root レイアウトなので、アプリ内のすべての fetch リクエストは
// 特定のキャッシュオプションがなければキャッシュされます。
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 に置き換えられました。codemod が利用可能 であり、安全かつ自動的にインポートをリネームできます。

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

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

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

NextRequest ジオロケーション

NextRequestgeoip プロパティは、これらの値がホスティングプロバイダーによって提供されるため削除されました。codemod がこのマイグレーションを自動化するために利用可能です。

Vercel を使用している場合、代わりに [@vercel/functions](https://vercel.com/docs/functions/vercel-functions-package) から geolocationipAddress` 関数を使用できます:

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)

// ...
}