[Next.js] Next.js 14 버전 변경사항

문지은·2024년 2월 23일
3
post-thumbnail

2023년 10월 26일에 Next.js 14 버전이 공개되었다. (13 버전 공부한지도 얼마 안됐는데..?)

Next.js는 React.js 기반의 프레임워크로, 한국뿐만 아니라 전 세계에서도 많은 인기를 끌고 있는 라이브러리이다.

간단히 Next.js에 대해 알아보고, 14버전의 주요 변화점을 정리해보자.

Next.js 소개

Next.js 공식 웹사이트에서는 Next.js를 “빠른 웹 애플리케이션을 제작할 수 있는 빌딩 블록을 제공하는 유연한 React 프레임워크”라고 정의하고 있다.

최신 애플리케이션 구축할때 고려해야할 몇가지 사항(Building Blocks)을 아래와 같이 정의하였고, 각 부분에 대해 직접 구축할지 아니면 다른 라이브러리를 사용할지 결정해야 한다.

  • 사용자 인터페이스(User Interface) – 사용자가 애플리케이션을 소비하고 상호 작용하는 방식.
  • 라우팅(Routing) – 사용자가 애플리케이션의 여러 영역을 탐색하는 방법.
  • 데이터 가져오기(Data Fetching) – 데이터의 저장 위치 및 데이터 가져오기 방법.
  • 렌더링(Rendering) – 정적 또는 동적 콘텐츠를 렌더링하는 시기와 위치.
  • 연동(Integrations) – 타사 서비스(CMS, 인증, 결제 등) 이용 및 연결 방법.
  • 인프라(Infrastructure) – 애플리케이션 코드를 배포, 저장 및 운영하는 곳(Serverless, CDN, Edge 등).
  • 성능(Performance) – 고객(사용자)를 위해 애플리케이션을 최적화하는 방법.
  • 확장성(Scalability) – 팀, 데이터, 트래픽이 증가함에 따라 애플리케이션을 유연하게 조정하는 방법.
  • 개발자 경험(Developer Experience) – 팀의 애플리케이션 구축 및 유지 관리 경험.

Next.js 13 : App router의 등장

Next.js는 React 프레임워크로서 다양한 기능을 제공하고 있는데, 그중 가장 대표적인 것이 바로 라우팅이다.

많은 프레임워크는 라우팅을 위해 디렉토리 구조와 파일 규칙을 강제한다. Next.js 역시 예전부터 라우팅을 위한 파일을 웹사이트 구조에 맞춰서 루트 디렉토리에 있는 pages 라는 폴더 내부에 생성하도록 하였다.

그러나 Next.js 13.4 부터 App router 가 기본 설정으로 적용되면서, 라우팅의 많은 규칙이 달라졌다.

라우팅 디렉토리 및 파일 규칙

  • Pages Router 에서는 정적인 공통 마크업은 _document에 작성하고, 모든 페이지가 공유하는 로직은 _app에 작성했던 것과 달리, App router 방식에서는 해당 파일이 사라지고 디렉토리 단위로 적용되는 layout이라는 개념이 생겼다.
  • 파일 경로와 이름에 따라 사이트 주소가 설정되는 규칙도 디렉토리 구조로 경로를 구분하고, 파일은 page 라는 이름을 갖도록 변경되었다.


이미지 출처 : https://yozm.wishket.com/magazine/detail/2324/

메타데이터 적용 방법

  • 라우팅 규칙이 변경됨에 따라 SEO에서 필수적인 메타 태그 적용 방법도 달라졌다.
  • 기존 Pages Router 방식에서는 일반적인 HTML 과 유사하게 헤드 태그에 메타 태그를 작성했는데, App Router 방식에서는 레이아웃 및 페이지 파일에서 별도로 Metadata를 export 하도록 만들었다.
  • 이제 App Router에서는 메타 태그를 따로 작성하지 못하는 것은 물론, next/head 의 Head 태그도 사용할 수 없다.


이미지 출처 : https://yozm.wishket.com/magazine/detail/2324/

서버 사이드 렌더링

  • Pages Router 방식의 페이지에서 서버 사이드 렌더링을 위해서는 getServerSideProps 라는 이름의 함수를 사용해야 했다.
  • 그러나 App Router에서는 일반적인 fetch 함수를 페이지에서 비동기로 사용하여 서버 사이드 렌더링을 구현할 수 있다.


이미지 출처 https://yozm.wishket.com/magazine/detail/2324/

더 많은 업데이트 사항은 공식 문서에서 확인할 수 있다.

Next.js 14 의 주요 변화

컴파일러 성능 향상

  • Next.js 13 부터 Pages Router 와 App Router 모두에서 로컬 개발 성능을 개선하기 위해 노력해왔다.
  • 이전 버전에서는 성능 향상을 위해 next dev 와 Next.js 의 다른 파트를 재정비하였다.
  • Next.js 14 에서는 Next.js의 모든 기능 성능 향상에 대해 초점을 맞추었고, 그것은 Rust 기반 컴파일러 안정화로 이어진다.
  • 대규모 Next.js 애플케이션인 vercel.com 웹사이트에서 테스트 하는 동안 다음 결과를 확인할 수 있다.
    • 최대 53% 속도 향상 된 로컬 서버 구동 속도
    • 최대 94% 속도 향상 된 코드 갱신 by Fast Refresh
  • 이 벤치마크는 대규모 애플리케이션에서 기대할 수 있는 성능 개선에 대한 실질적인 결과이다.
  • next dev 테스트가 90%가 통과되었으므로, next dev –turbo를 사용하면 더 빠르고 안정적인 성능을 경험할 수 있다.

Forms and Mutations

  • Next.js 9에서는 프론트엔드 코드와 함께 백엔드 엔드포인트를 빠르게 빌드하는 방법인 API Router가 도입되었다.
    • 예를 들어 api/ 디렉터리에 새 파일을 생성할 수 있다.

아래와 같은 submit 파일명으로 API Router를 만들어 주고, 클라이언트 측에서 React 와 onSubmit 과 같은 이벤트 핸들러를 사용하면 API Route로 fetch 를 만들 수 있다.

pages/api/submit.ts

import type { NextApiRequest, NextApiResponse } from 'next';
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const data = req.body;
  const id = await createItem(data);
  res.status(200).json({ id });
}

pages/index.tsx

import { FormEvent } from 'react';
 
export default function Page() {
  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
 
    const formData = new FormData(event.currentTarget);
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: formData,
    });
 
    // Handle response if necessary
    const data = await response.json();
    // ...
  }
 
  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit">Submit</button>
    </form>
  );
}
  • Next.js 14 에서는 data mutations(서버 데이터 수정) 작성하는 개발자 경험을 간소화 한다.
    • 또한 저성능 디바이스와 느린 네트워크에서 발생되는 사용자 경험을 개선한다.

Server Action 안정화

  • API Route를 수동으로 생성할 필요없이, 서버에서 보안적으로 실행되는 함수를 정의하고 React 컴포넌트에서 직접 호출할 수 있다.
  • 동일한 Next.js 프로젝트에서 사용할 거라면, 별도의 API Route 까지 만들지 않고 서버 컴포넌트에서 한 번에 데이터베이스에 값을 저장할 수 있도록 제공한 것이다.
    • ‘use server’를 함수나 파일에 작성해 두면, 함수 내용을 자동으로 서버 API로 만들어주고, 개발자는 유저에게 코드가 노출될 걱정 없이 자유롭게 데이터베이스를 관리할 수 있다.
  • 13.4 버전에서도 서버 액션을 제공했는데, 사용하기 위해서는 아래와 같이 next.config.js 에 따로 설정이 필요했다.

  • 이제 14 버전부터 서버 액션이 안정화되면서 해당 설정 없이도 서버 액션 기능을 이용할 수 있다.

예를 들어, 이전 라우터 예제를 하나의 Page 파일로 단순화할 수 있다. 단순화된 코드이다.

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>
  );
}

Caching, Revalidating, Redirecting, 그 외

서버 작업은 전체 App Router 모델에 통합되어 있으며, 다음을 수행할 수 있다:

  • revalidatePath() 또는 revalidateTag()를 사용하여 캐시된 데이터 재검증
  • redirect()를 통해 다른 경로로 리디렉션
  • useOptimistic()으로 최적화된 UI 업데이트 처리
  • useFormState()로 서버의 오류를 포착하고 표시
  • useFormStatus()로 브라우저에 로딩 상태 표시

Partial Prerendering (Preview)

  • 빠른 초기 정적 응답 가진 동적 콘텐츠를 위한 컴파일러 최적화를 v14 에 적용하는 작업을 진행중이다.
    • 빠른 초기 정적 응답 가진 동적 콘텐츠는 공식홈페이지에서 dynamic content with a fast initial static response로 표현하며, next.js가 처음 페이지를 불러올때 정적인 컨텐츠를 미리 제공하는 방식
  • Partial Prerendering은 서버 사이드 렌더링(SSR), 정적 사이트 생성(SSG), 그리고 증분적 정적 재검증(ISR)에 대한 수십 년의 연구와 개발을 기반으로 한다.

Built on React Suspense

  • Partial Prerendering은 서스펜스 경계에 의해 정의된다.
  • 작동 방식을 다음 전자상거래 페이지를 통해 살펴보자.

app/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이 활성화되면, 이 페이지는 <Suspense /> 경계를 기반으로 정적인 셸(shell)을 생성한다.
    • React Suspense의 fallback은 사전 렌더링(prerendered)된다.
  • 그런 다음 셸의 Suspense fallback은 쿠키를 읽어 장바구니를 확인하거나 사용자를 기반으로 배너를 표시하는 등의 동적 컴포넌트로 대체된다.
    • 요청이 발생하면 다음과 같은 정적 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>
  • <ShoppingCart />가 사용자 세션을 확인하기 위해 Cookie를 읽기 때문에, 이 컴포넌트는 정적 셸과 동일한 HTTP 요청의 일부로 스트리밍된다. 추가적인 네트워크 왕복이 필요하지 않다.

app/cart.tsx

import { cookies } from 'next/headers'
 
export default function ShoppingCart() {
  const cookieStore = cookies()
  const session = cookieStore.get('session')
  return ...
}
  • 가장 세분화된 정적 셸을 사용하려면 Suspense 경계를 추가해야 할 수 있다.
    • 그러나 현재 이미 loading.js를 사용하고 있는 경우 이는 암묵적인 Suspense 경계이므로 정적 셸을 생성하는 데 추가 변경이 필요하지 않다.

Metadata Improvements

  • 서버에서 페이지 콘텐츠를 스트리밍하기 전에 뷰포트, 색 구성표 및 테마에 대한 중요한 메타데이터를 먼저 브라우저로 전송해야 한다.
    • 이러한 meta 태그들이 초기 페이지 콘텐츠와 함께 전송되도록 하면 부드러운 사용자 경험을 제공한다.
    • 이는 테마 색상을 변경으로 인해 페이지가 깜박이는 것을 방지해주며, 뷰포트 변경으로 인한 레이아웃 이동을 막는다.
  • Next.js 14에서는 블로킹 메타데이터와 논 블로킹 메타데이터를 분리했다.
    • 일부 메타데이터 옵션만 블로킹하며, 논 블로킹 메타데이터가 사전 렌더링된 페이지가 정적 셸을 제공하는 데 방해가 되지 않도록 하고자 하는 것이다.
  • 다음 메타데이터 옵션은 현재 더 이상 사용되지 않으며 향후 메이저 버전의 메타데이터에서 제거될 예정이다.
    • viewport : 뷰포트의 초기 확대/축소 및 기타 속성을 설정
    • colorScheme : 뷰포트의 지원 모드(밝음/어두움)를 설정
    • themeColor : 뷰포트 주변의 크롬이 렌더링할 색상을 설정
  • Next.js 14부터는 이러한 옵션을 대체하는 새로운 옵션인 viewport 및 generateViewport가 추가되었다.
    • 다른 모든 metadata 옵션은 동일하게 유지되었다.
  • 아래는 기존 13.4 버전에서 작성한 메타데이터 설정 코드이다.

  • 14로 업데이트됨에 따라 viewport를 삭제하게 되었으며, 앞으로는 아래 처럼 작성해야 한다.

💡 References

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글