バージョン 15
14から15へのアップグレード
Next.js バージョン 15 にアップデートするには、`upgrade` コードモッドを使用できます。
npx @next/codemod@canary upgrade
手動で行う場合は、最新の Next & React RC をインストールしていることを確認してください。例:
npm i next@canary react@rc react-dom@rc eslint-config-next@rc
知っておくと良いこと
- ピア依存関係の警告が表示された場合は、`react` と `react-dom` を推奨バージョンにアップデートするか、`--force` または `--legacy-peer-deps` フラグを使用して警告を無視する必要がある場合があります。Next.js 15 と React 19 の両方が安定版になると、これは不要になります。
- TypeScript を使用している場合は、React の型を一時的にオーバーライドする必要があります。詳細は、React 19 RC アップグレードガイド を参照してください。
React 19
- `react` と `react-dom` の最小バージョンは 19 になりました。
- `useFormState` は `useActionState` に置き換えられました。`useFormState` フックは React 19 でも引き続き使用できますが、非推奨となっており、将来のリリースで削除される予定です。`useActionState` が推奨され、`pending` 状態を直接読み取るなどの追加のプロパティが含まれています。詳細はこちら。
- `useFormStatus` には、`data`、`method`、`action` などの追加キーが含まれるようになりました。React 19 を使用していない場合は、`pending` キーのみ使用できます。詳細はこちら。
- React 19 アップグレードガイド で詳細をご覧ください。
非同期リクエストAPI (破壊的変更)
ランタイム情報に依存する従来の同期 Dynamic API は、 désormais 非同期 になりました。
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'
// 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 UnsafeUnwrappedDraftMode
params
& 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 = { [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
}
// Before
export async function GET(request, segmentData) {
const params = segmentData.params
const slug = params.slug
}
// After
export async function GET(request, segmentData) {
const params = await segmentData.params
const slug = params.slug
}
runtime
設定(破壊的変更)
runtime
セグメント設定 は、以前は edge
に加えて experimental-edge
の値をサポートしていました。どちらの設定も同じものを指しているため、オプションを簡素化するために、experimental-edge
が使用されている場合は désormais エラーが発生します。これを修正するには、runtime
設定を edge
に更新してください。これを自動的に行うための コードモッド が利用可能です。
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
// ...
}
ルートハンドラー
ルートハンドラー 内の 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
に置き換えられました。インポートを安全かつ自動的に名前変更するための コードモッド が利用可能です。
// Before
import { Inter } from '@next/font/google'
// After
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
experimental.bundlePagesExternals
は désormais 安定版となり、bundlePagesRouterDependencies
に名前が変更されました。
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
bundlePagesExternals: true,
},
// After
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
experimental.serverComponentsExternalPackages
は安定版となり、serverExternalPackages
に名前が変更されました。
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// After
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
スピードインサイト
Next.js 15 では、スピードインサイトの自動インストゥルメンテーションが削除されました。
スピードインサイトを引き続き使用するには、Vercel スピードインサイトクイックスタートガイドに従ってください。
NextRequest
のジオロケーション
NextRequest
の geo
プロパティと ip
プロパティは、これらの値がホスティングプロバイダーによって提供されるため、削除されました。コードモッドを使用して、この移行を自動化できます。
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)
// ...
}
これは役に立ちましたか?