Next.js 16을 프로젝트에 적용하면서 Next.js 15의 주요 변경사항들을 공부하게 되었습니다.
해당 포스팅에서는 Breaking Changes를 중심으로 정리해보겠습니다.
가장 큰 변경사항 중 하나입니다. Next.js 14까지는 동기적으로 접근 가능했던 params와 searchParams가 이제 비동기로 변경되었습니다.
// app/users/[id]/page.tsx
export default function UserPage({
params
}: {
params: { id: string }
}) {
const userId = params.id // 바로 접근 가능
return <div>User ID: {userId}</div>
}
// app/users/[id]/page.tsx
export default async function UserPage({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params // await 필요!
return <div>User ID: {id}</div>
}
// app/search/page.tsx
export default async function SearchPage({
searchParams
}: {
searchParams: Promise<{ q: string }>
}) {
const { q } = await searchParams
return <div>검색어: {q}</div>
}
왜 이렇게 변경되었나요?
Parallel Routes를 사용할 때 각 슬롯마다 default.tsx 파일이 반드시 필요합니다.
Missing required default.js file for parallel route at /@modal
// app/@modal/default.tsx
export default function Default() {
return null
}
app/
├── layout.tsx
├── @modal/
│ ├── default.tsx ← 필수!
│ └── login/
│ └── page.tsx
└── page.tsx
// app/layout.tsx
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
)
}
왜 필요한가요?
Next.js 15부터 fetch의 기본 캐싱 동작이 변경되었습니다.
// 기본적으로 캐시됨
const res = await fetch('https://api.example.com/data')
// 기본적으로 캐시 안 됨 (no-store)
const res = await fetch('https://api.example.com/data')
// 캐시하려면 명시적으로 지정
const res = await fetch('https://api.example.com/data', {
cache: 'force-cache'
})
// 또는 revalidate 사용
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 1시간
})
마이그레이션 팁:
캐시가 필요한 fetch 호출에는 명시적으로 캐싱 옵션을 추가하세요.
Next.js 15는 React 19를 정식 지원합니다.
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending}>
{pending ? '제출 중...' : '제출'}
</button>
)
}
// React 19에서는 forwardRef 불필요
export default function Input({ ref, ...props }: Props) {
return <input ref={ref} {...props} />
}
Server Component에서 async/await을 직접 사용하는 것이 표준 패턴이 되었습니다.
export default async function Home() {
// 직접 데이터 fetch
const posts = await fetch('https://api.example.com/posts')
.then(res => res.json())
// DB 직접 접근도 가능
const users = await db.user.findMany()
return (
<div>
{posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
장점:
useEffect나 별도의 data fetching 로직 불필요Next.js 15의 변경사항들은 처음에는 번거로워 보이지만, 더 나은 성능과 개발 경험을 위한 것입니다. 특히 params를 Promise로 변경한 것은 서버 컴포넌트의 스트리밍과 Suspense를 더 효과적으로 활용하기 위한 선택으로 생각됩니다.
이번 Next.js 15 학습을 통해 프레임워크의 진화 방향을 이해할 수 있었고, 앞으로 Next.js 16의 변경점들도 차근차근 공부해봐야겠습니다! 🚀
참고 자료: