Next.js 16에서 가장 크게 체감한 변화는 이것이다.
그중에서도 직접 코드를 짜보면서 가장 인상 깊었던 건 Cache Component였다.
처음에는 이렇게 생각했다.
그냥 서버에서 하던 fetch를 컴포넌트 안으로 옮긴 거 아닌가?
결론부터 말하면
겉보기엔 맞지만, 실제로는 렌더링 방식 자체가 바뀐 것이다.
예를 들어 이런 페이지가 있다고 해보자.
const Home = async ({ searchParams }) => {
const posts = await getPostList(await searchParams)
return (
<main>
<PostList posts={posts} />
</main>
)
}
이 구조에서의 흐름은 이렇다.
즉,
페이지 전체가 데이터 준비가 끝날 때까지 기다린다.
이게 전통적인 SSR이다.
이제 구조를 이렇게 바꿔보자.
const Home = async ({ searchParams }) => {
return (
<main>
<Suspense>
<PostList searchParams={searchParams} />
</Suspense>
</main>
)
}
그리고 실제 데이터 fetch는 여기서 한다.
export const PostList = async ({ searchParams }) => {
const posts = await getPostList(await searchParams)
return (
<section>
{posts.map(post => <PostCard key={post.id} />)}
</section>
)
}
겉보기에는
“fetch 위치만 바뀐 것”처럼 보인다.
하지만 실제로는 다르다.
이제 흐름은 이렇게 된다.
즉,
전체 블로킹이 아니라, 컴포넌트 단위 블로킹이 된다.
이게 가장 큰 변화다.
Next 16의 기본 철학은 이렇다.
모든 것은 요청 기준으로 실행된다.
즉,
예전에는 Next가 자동으로 static인지 dynamic인지 추측했다.
이제는 추측하지 않는다.
기본은 dynamic
static으로 쓰고 싶으면 직접 표시해라
그 표시가 'use cache'다.
'use cache'는 정확히 무엇을 하는가use cache는 두 가지 방식으로 사용할 수 있다.
export const getPostList = async (params) => {
'use cache'
return db.query(...)
}
이 경우 캐싱되는 것은
함수의 반환값
이다.
하지만 컴포넌트는 여전히 렌더링된다.
이 방식은 이런 데이터에 적합하다.
입력이 같으면 결과도 같아야 한다는 조건만 만족하면 된다.
export const CategoryNav = async () => {
'use cache'
const categories = await getCategoryList()
return <nav>...</nav>
}
이 경우는 다르다.
렌더링 결과 HTML까지 저장된다.
즉,
하지만 제약이 있다.
왜냐하면 요청마다 달라질 수 있기 때문이다.
컴포넌트 캐싱은
요청과 완전히 무관한 순수 UI
일 때만 안전하다.
겉보기에는 fetch 위치가 바뀐 것처럼 보이지만
실제로는 이것이 바뀌었다.
| 이전 | 지금 |
|---|---|
| 페이지 단위 블로킹 | 컴포넌트 단위 블로킹 |
| 전체 HTML 대기 | 부분 스트리밍 |
| 자동 static 최적화 | 명시적 캐싱 선언 |
Next 16은 이렇게 말하는 것과 같다.
기본은 동적이다.
정적인 경계는 네가 직접 선언해라.
그리고 그 경계는 컴포넌트 단위다.
Next.js 16의 Cache Component는
“데이터 대기와 캐싱의 기준을 페이지에서 컴포넌트로 내린 구조”
이다.
겉으로 보면 단순한 리팩토링처럼 보이지만,
실제로는 렌더링 패러다임이 바뀐 것이다.