バージョン15へのアップグレード方法
バージョン14から15へのアップグレード
Next.jsバージョン15に更新するには、upgrade
コードモッドを使用できます:
npx @next/codemod@canary upgrade latest
手動で行う場合は、最新のNext.jsと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(破壊的変更)
以前は同期処理だったランタイム情報に依存するDynamic APIが非同期になりました:
cookies
headers
draftMode
layout.js
、page.js
、route.js
、default.js
、opengraph-image
、twitter-image
、icon
、apple-icon
のparams
page.js
のsearchParams
移行負担を軽減するため、コードモッドが利用可能で、一時的にAPIを同期的にアクセスすることもできます。
cookies
推奨される非同期使用法
import { cookies } from 'next/headers'
// 以前
const cookieStore = cookies()
const token = cookieStore.get('token')
// 以降
const cookieStore = await cookies()
const token = cookieStore.get('token')
一時的な同期使用法
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')
import { cookies } from 'next/headers'
// 以前
const cookieStore = cookies()
const token = cookieStore.get('token')
// 以降
const cookieStore = cookies()
// 開発時には警告が表示されます
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')
一時的な同期使用法
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')
import { headers } from 'next/headers'
// 以前
const headersList = headers()
const userAgent = headersList.get('user-agent')
// 以降
const headersList = headers()
// 開発時には警告が表示されます
const userAgent = headersList.get('user-agent')
draftMode
推奨される非同期使用法
import { draftMode } from 'next/headers'
// 以前
const { isEnabled } = draftMode()
// 以降
const { isEnabled } = await draftMode()
一時的な同期使用法
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// 以前
const { isEnabled } = draftMode()
// 以降
// 開発時には警告が表示されます
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
import { draftMode } from 'next/headers'
// 以前
const { isEnabled } = draftMode()
// 以降
// 開発時には警告が表示されます
const { isEnabled } = draftMode()
params
& searchParams
非同期レイアウト
// 以前
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
}
// 以前
export function generateMetadata({ params }) {
const { slug } = params
}
export default async function Layout({ children, params }) {
const { slug } = params
}
// 以降
export async function generateMetadata({ params }) {
const { slug } = await params
}
export default async function Layout({ children, params }) {
const { slug } = await params
}
同期レイアウト
// 以前
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
}
// 以前
export default function Layout({ children, params }) {
const { slug } = params
}
// 以降
import { use } from 'react'
export default async function Layout(props) {
const params = use(props.params)
const slug = params.slug
}
非同期ページ
// 以前
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
}
// 以前
export function generateMetadata({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// 以降
export async function generateMetadata(props) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
export async function Page(props) {
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
}
ルートハンドラー
// 以前
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
}
// 以前
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'
オプションを渡します。
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
オプションを指定している場合、それが優先されます。
// これはルートレイアウトなので、アプリ内のすべての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'
などのルート設定オプションを使用できます。
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
に置き換えられました。コードモッドを使用してインポートを安全かつ自動的にリネームできます。
// 以前
import { Inter } from '@next/font/google'
// 以降
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
experimental.bundlePagesExternals
が安定版になり、bundlePagesRouterDependencies
に名称変更されました。
/** @type {import('next').NextConfig} */
const nextConfig = {
// 以前
experimental: {
bundlePagesExternals: true,
},
// 以降
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
experimental.serverComponentsExternalPackages
が安定版になり、serverExternalPackages
に名称変更されました。
/** @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
のジオロケーション (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)
// ...
}