https://nextjs.org/blog/next-15
Next.js 15 버전이 릴리스 되었다. Next.js 15의 변경점을 알아본다.
npx @next/codemod@canary upgrade latest
codemode CLI를 통해 최신의 스테이블 버전 또는 프리 릴리스 버전으로 코드베이스를 업그레이드할 수 있다.
전통적인 SSR 에서 서버는 렌더링 전에 요청을 기다린다. 하지만 개인화 데이터에 의존하지 않는 컴포넌트 까지 요청을 기다리는 것은 불필요하다. (한 컴포넌트 때문에 페이지 전체가 dynamic rendering 일 필요는 없다.)
이상적으로 서버는 유저가 요청 하기 전에 최대한 많이 준비해놓는것이 좋다. (static rendering)
향후 최적화(partial prerendering) 를 위해 언제 요청을 기다려야하는지 알 필요가 있다.
그.래.서.
headers, cookies, params, searchParams를 비동기로 바꿨다.
import { cookies } from 'next/headers';
export async function AdminPanel() {
const cookieStore = await cookies();
const token = cookieStore.get('token');
// ...
}
해당 breaking change 에 영향을 을 받은 API들은 다음과 같다.
마이그레이션 쉽게할 수 있는 방법은 아래 커멘드.
npx @next/codemod@canary next-async-request-api .
Next.js App Router 는 최상의 성능을 위해 기본적으로 캐시를 사용하고, 설정을 해제할 수 있었다.
15 버전에서는 GET Route Handler, Client Router Cache 는 캐시하지 않는것을 디폴트로 한다.
Next14 에서 Get Route Handlers 는 dynamic function or dynamic config option을 사용하지 않는다면 캐싱되는것이 디폴트였다.
Next15 는 default 로 캐싱되지 않는다.
만약 캐시를 사용하고 싶다면, 정적 라우트 설정인 export dynamic = "force-static"를 통해 캐시를 사용할 수 있다.
Next14.2 버전에서 router cache를 커스텀 하기 위해서 experimental staleTimes flag 가 추가되었습니다.
Next15에서 staleTime 의 디폴트가 0으로 변경 되었다.
네비게이션으로 페이지를 이동할때 클라이언트는 항상 최신 데이터를 반영한다. 하지만 여전히 변함없이 유지되는 동작들이 있다.
Next15에서는, the App Router 에 React 19 RC, 를 사용한다. (React Team 과의 긴밀한 협업을 통해 React 19가 안정적이라는 확신을 얻었다고 한다.)
안정적인 마이그레이션을 보장하기위해 codemods and automated tools 를 준비했고 한다.
Next15는 React Compiler를 지원한다.
Next 15 에선 Hydration error 메세지를 솔루션과 함께 좀더 친절하게 보여준다.
as is (v 14.1)
to be (v 15)
next dev --turbo 는 스테이블 하며 빠른 개발 환경을 제공 할 수 있게 되었다.
Nextjs는 개발 모드에서 어떤 route가 static이고 dynamic인지 개발자가 인지할 수 있게 하려고 Static Route Indicator를 display 한다.
import Form from 'next/form';
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
);
}
원래는.. 같은 기능을 구현할려면 이렇게 많이 작성했어야했음
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
export default function Form(props) {
const action = props.action
const router = useRouter()
useEffect(() => {
// if form target is a URL, prefetch it
if (typeof action === 'string') {
router.prefetch(action)
}
}, [action, router])
function onSubmit(event) {
event.preventDefault()
// grab all of the form fields and trigger a `router.push` with the data URL encoded
const formData = new FormData(event.currentTarget)
const data = new URLSearchParams()
for (const [name, value] of formData) {
data.append(name, value as string)
}
router.push(`${action}?${data.toString()}`)
}
if (typeof action === 'string') {
return <form onSubmit={onSubmit} {...props} />
}
return <form {...props} />
}