업그레이드: 버전 15
업그레이드: 버전 15 | Next.js
섹션 제목: “업그레이드: 버전 15 | Next.js”출처 URL: https://nextjs.org/docs/app/guides/upgrading/version-15
버전 15로 업그레이드하는 방법
섹션 제목: “버전 15로 업그레이드하는 방법”마지막 업데이트 2026년 2월 20일
14에서 15로 업그레이드
섹션 제목: “14에서 15로 업그레이드”Next.js 버전 15로 업데이트하려면 upgrade 코데모드를 사용할 수 있습니다:
pnpmnpmyarnbun
Terminal
pnpm dlx @next/codemod@canary upgrade latest수동으로 진행하려면 최신 Next 및 React 버전을 설치했는지 확인하세요:
pnpmnpmyarnbun
Terminal
pnpm add 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 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(호환성 파괴 변경)”런타임 정보에 의존하던 기존의 동기 Dynamic API가 이제 비동기로 동작합니다:
cookiesheadersdraftModelayout.js,page.js,route.js,default.js,opengraph-image,twitter-image,icon,apple-icon의params.page.js의searchParams
마이그레이션 부담을 줄이기 위해 코데모드가 제공되며 해당 API를 일시적으로 동기 방식으로 접근할 수 있습니다.
cookies
섹션 제목: “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')임시 동기 사용법
섹션 제목: “임시 동기 사용법”app/page.tsx
JavaScriptTypeScript
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
섹션 제목: “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')임시 동기 사용법
섹션 제목: “임시 동기 사용법”app/page.tsx
JavaScriptTypeScript
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
섹션 제목: “draftMode”권장 비동기 사용법
섹션 제목: “권장 비동기 사용법” import { draftMode } from 'next/headers'
// Before const { isEnabled } = draftMode()
// After const { isEnabled } = await draftMode()임시 동기 사용법
섹션 제목: “임시 동기 사용법”app/page.tsx
JavaScriptTypeScript
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
섹션 제목: “params & searchParams”비동기 레이아웃
섹션 제목: “비동기 레이아웃”app/layout.tsx
JavaScriptTypeScript
// 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 }동기 레이아웃
섹션 제목: “동기 레이아웃”app/layout.tsx
JavaScriptTypeScript
// 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 }비동기 페이지
섹션 제목: “비동기 페이지”app/page.tsx
JavaScriptTypeScript
// 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 }라우트 핸들러
섹션 제목: “라우트 핸들러”app/api/route.ts
JavaScriptTypeScript
// 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 구성(호환성 파괴 변경)”runtime segment configuration은 이전에 edge 외에도 experimental-edge 값을 지원했습니다. 두 구성은 동일한 의미이므로 옵션을 단순화하기 위해 이제 experimental-edge를 사용하면 오류가 발생합니다. 이를 해결하려면 runtime 구성을 edge로 업데이트하십시오. 이를 자동으로 처리해 주는 codemod을 사용할 수 있습니다.
fetch requests
섹션 제목: “fetch requests”fetch requests는 이제 기본적으로 캐시되지 않습니다.
특정 fetch 요청을 캐시에 넣으려면 cache: 'force-cache' 옵션을 전달하면 됩니다.
app/layout.js
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' segment config option을 사용할 수 있습니다. 개별 fetch 요청에서 cache 옵션을 지정하면 해당 값이 우선합니다.
app/layout.js
// 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
섹션 제목: “Route Handlers”Route Handlers의 GET 함수는 더 이상 기본적으로 캐시되지 않습니다. GET 메서드를 캐시하도록 설정하려면 Route Handler 파일에서 export const dynamic = 'force-static'과 같은 route config option을 사용할 수 있습니다.
app/api/route.js
export const dynamic = 'force-static'
export async function GET() {}Client-side Router Cache
섹션 제목: “Client-side Router Cache”<Link> 또는 useRouter로 페이지 사이를 이동할 때, page 세그먼트는 더 이상 클라이언트 측 라우터 캐시에서 재사용되지 않습니다. 그러나 브라우저 뒤로/앞으로 이동과 공유 레이아웃에서는 계속 재사용됩니다.
페이지 세그먼트를 캐시에 넣으려면 staleTimes 구성 옵션을 사용할 수 있습니다.
next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = { experimental: { staleTimes: { dynamic: 30, static: 180, }, }, }
module.exports = nextConfigLayouts와 loading states는 여전히 캐시되며 탐색 시 재사용됩니다.
next/font
섹션 제목: “next/font”@next/font 패키지는 기본 제공 next/font로 대체되어 제거되었습니다. import 이름을 안전하게 자동 변경해 주는 codemod이 제공됩니다.
app/layout.js
// Before import { Inter } from '@next/font/google'
// After import { Inter } from 'next/font/google'bundlePagesRouterDependencies
섹션 제목: “bundlePagesRouterDependencies”experimental.bundlePagesExternals는 안정화되면서 이름이 bundlePagesRouterDependencies로 변경되었습니다.
next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = { // Before experimental: { bundlePagesExternals: true, },
// After bundlePagesRouterDependencies: true, }
module.exports = nextConfigserverExternalPackages
섹션 제목: “serverExternalPackages”experimental.serverComponentsExternalPackages는 안정화되면서 이름이 serverExternalPackages로 변경되었습니다.
next.config.js
/** @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에 대한 자동 계측이 제거되었습니다.
Speed Insights를 계속 사용하려면 Vercel Speed Insights Quickstart 가이드를 따르십시오.
NextRequest Geolocation
섹션 제목: “NextRequest Geolocation”호스팅 제공자가 이 값을 제공하므로 NextRequest의 geo 및 ip 속성이 제거되었습니다. 이 마이그레이션을 자동화하는 codemod가 제공됩니다.
Vercel을 사용하는 경우 @vercel/functions의 geolocation 및 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)
// ... }Was this helpful?
supported.
Send