Next.js 14

agert·2023년 10월 28일
3

NextJS

목록 보기
2/2

Next.js 14 주요 피쳐들 정리

https://nextjs.org/blog/next-14

  1. Turbopack
    • 53% faster local server startup
    • 94% faster code updates with Fast Refresh
  2. Server Actions
    • Integrated with caching & revalidating
    • Simple function calls, or works natively with forms
  3. 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. 현재 상황



2. Server Actions (Stable): Progressively enhanced mutations

2-1. 배경

  • Next.js 9버전부터 api routes로 간단한 backend endpoints를 작성할 수 있게 되었음.
  • 하지만, 지금은 data mutation을 위해 2가지 작업이 필요해서 DX상 약간 좋지 못함.
    1. api routes를 작성하고 (ex, DB 통신 로직)
    2. 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. 몇 가지 부수적 효과들

  1. client <-> server간 end-to-end type-safety를 보장할 수 있다.
  2. '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에게 전송.

    • SEO
    • one roundtrip
    • 페이지를 받기까지(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
  • 예시

    • page.tsx
    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">
          <!-- Hole -->
        </div>
      </header>
      <div class="banner" />
      <div class="product-list-skeleton">
        <!-- Hole -->
      </div>
      <section class="new-products" />
    </main>
  • <!-- Hole -->에 어떻게 dynamic한 요소를 맵핑할지는 아직..



추후 업데이트할 내용

profile
스타트업에서 프러덕트 개발자로 일하고 있습니다.

0개의 댓글