Next.js 14 주요 피쳐들 정리
https://nextjs.org/blog/next-14
- Turbopack
- 53% faster local server startup
- 94% faster code updates with Fast Refresh
- Server Actions
- Integrated with caching & revalidating
- Simple function calls, or works natively with forms
- Partial Prerendering
1. Turbopack & Next dev
1-1. 배경
로컬 개발 환경의 퍼포먼스를 빠르게 하기 위해 (Pages, App Router 둘 다)
- 이를 위해 next dev를 Turbopack을 사용하도록 점진적으로 다시 작성했고, next dev에 대한 5,000개의 integration tests를 통과했음.
1-2. 성능은 얼마나 좋아졌나?
- 로컬 서버 startup 속도 53.3% 향상 (약 1.5배)
- Fast Refresh에 속도 94.7% 향상 (약 2배)
- large application에 대해서 테스트했기 때문에, 실제로 이와 비슷한 효과를 볼 수 있을 것.
1-3. 현재 상황
- 90%의 테스트가 성공, 테스트 pass 100%에 도달하면 stable 상태로 minor release 계획
2. Server Actions (Stable): Progressively enhanced mutations
2-1. 배경
- Next.js 9버전부터 api routes로 간단한 backend endpoints를 작성할 수 있게 되었음.
- 하지만, 지금은 data mutation을 위해 2가지 작업이 필요해서 DX상 약간 좋지 못함.
- api routes를 작성하고 (ex, DB 통신 로직)
- client 컴포넌트에서 form을 핸들링하는 코드 작성.
- 이런 data mutation(form)을 작성할 때 DX을 개선하고,
- 느린 네트워크 환경, 낮은 배터리 환경의 디바이스에서 form 제출의 UX를 개선
2-2. 어떻게?
-
아이디어: api routes만 안 만들어도 더 편하겠다.
- server에서 동작하는 action을 React Component단에서 작성하자
-
React canary를 이용
-
To-Be
이전에 Page router에서 작성해야했던 api/create.ts 와 같은 api routes를 없애고, 하나의 React Component에서 server action을 정의할 수 있게 되었다.
// app/page.tsx
export default function Page() {
async function create(formData: FormData) {
'use server';
const id = await createItem(formData);
}
return (
<form action={create}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
);
}
2-3. 몇 가지 부수적 효과들
- client <-> server간 end-to-end type-safety를 보장할 수 있다.
- 'only one' network roundtrip
- 대개 server state를 변경하면 1) server 요청 - 응답, 2) refresh / routing 의 단계를 거치는데, 이는 2번의 network roundtrip이 요구된다.
- 여기서 server action을 사용하면, server 요청 처리 이후 routing을 원 큐에 해줄 수 있기 때문에, roundtrip이 1개로 줄어든다.
- 결국, 위에서 이야기했던 '느린 네트워크 환경', '낮은 배터리'에서 form mutation UX가 좋아질 수 있다.
2-4. 이게 가능한 이유는
- server actions은 App Router 모델과 결합되어있어서, 아래 것들을 할 수 있기 때문
- caching, revalidating, redirecting, cookies
3. Partial Prerendering (Preview): Fast initial static response + streaming dynamic content
아직 사용할 수는 없지만, vercel에서 next step으로 지켜보는 중
3-1. Partial Prerendering
- 페이지에서 static한 요소로 먼저 응답한 뒤, dynamic한(api 요청이 필요한) 요소는 이후 streaming으로 보내는 렌더링 방식
- fast initial static response + streaming dynamic content
3-2. 이게 왜 필요할까 - SSG, SSR, CSR
3-2-1. SSG
전부 정적인 페이지를 컴파일 타임에 미리 만들어뒀다가 client에게 보냄
- 장
- 빠름, 요청이 들어올 때 만드는 시간이 필요 없기 때문
- 단
- 정적인 페이지 밖에 안 됨. 아니면 미리 못 만들기 때문
3-2-2. SSR
페이지를 요청할 때 마다, 페이지를 생성 - api 요청이 필요하면, api 요청을 해서 받아온 뒤 만들어서 client에게 전송.
- 장
- 단
- 페이지를 받기까지(TTFB) 오래 걸림, client는 흰 화면을 오래 봐야 한다.
- hydration 과정이 필요해서 TTI도 느림
3-2-3. CSR
페이지를 요청하면, 빈 html, js, css을 내려주고, client에서 js로 페이지를 만듦.
- 장
- 화면이 보이면, 바로 interaction 가능
- 단
- SEO 안 됨(거의 - google에서 js까지 실행시킨다고 하지만 잘 안 되는 것이 대다수였음),
- js 로드 - 실행 - api 요청&응답 - 렌더링 방식이기 때문에 SSR보다 FCP가 느릴 수 있다.
3-2-4. 왜 필요한가? - UX
유저 경험을 좋게 만들기 위해
- 대부분의 페이지는 static & dynamic한 요소가 함께 포함되어 있다.
- 위 basic한 렌더링 방법들은 한 번에 화면을 그리는 데에 집중되어 있는데, 그럴 필요가 없음.
- streaming 방식으로 static한 것을 먼저 내려줘서 TTFB, FCP를 빠르게하고,
- 이후 동적인 요소를 내려주면 유저가 더 빠르게 화면을 확인할 수 있어서 UX에 좋다.
3-3. 어떻게 구현할까?
-
React Suspense
- Suspense를 사용하면 dynamic한 요소를 로드하기 전에 static한 fallback으로 사용자에게 먼저 streaming 할 수 있다.
- 이후, dynamic한 요소를 내려줄 수 있을 때 dynamic한 요소를 streaming
-
예시
export default function Page() {
return (
<main>
<header>
<h1>My Store</h1>
<Suspense fallback={<CartSkeleton />}>
<ShoppingCart />
</Suspense>
</header>
<Banner />
<Suspense fallback={<ProductListSkeleton />}>
<Recommendations />
</Suspense>
<NewProducts />
</main>
);
}
- 초기에 partial prerendering할 static한 html
<main>
<header>
<h1>My Store</h1>
<div class="cart-skeleton">
</div>
</header>
<div class="banner" />
<div class="product-list-skeleton">
</div>
<section class="new-products" />
</main>
-
<!-- Hole -->
에 어떻게 dynamic한 요소를 맵핑할지는 아직..
추후 업데이트할 내용