Next.js 13 업데이트 내용 총 정리

2023.09.22
11분
댓글

글을 시작하며

Next.js 12 버전이 공개된 후 약 1년 만에 13 버전이 출시되었습니다.
혹시 프로젝트를 최신 버전으로 세팅하고 app/ 구조를 마주하며 당황했던 경험이 있지 않으신가요?

230921-234426

13 버전의 가장 큰 변화로는 디렉토리 구조가 app/ 기반의 구조로 변경되었다는 점이 있습니다.
이번 글을 통해 13 버전의 변경점들과 그것들을 잘 활용할 수 있는 방법에 대해 배워보겠습니다.


app/Directory (beta)

230921-235647

Next.js 13 버전의 메인 업데이트 사항입니다.

기존 12 버전까지는 우리는 pages 디렉토리 내부에 폴더와 파일을 생성하여 라우팅을 설정하였습니다.
하지만 이제는 pages 디렉토리가 없어지고 app 디렉토리가 베이스 디렉토리가 됩니다!

React Server Components와 새롭게 생기는 Data fetching 기능, 그리고 Layouts, Streaming 등을 유연하게 제공하기 위해 변경되었습니다.


React Server Components

서버 컴포넌트를 사용하면 클라이언트로 보내는 JavaScript 양을 줄일 수 있어서 초기 페이지 로딩 속도가 빨라집니다.

서버 컴포넌트를 사용하려면 어떻게 해야 할까요? 🤔

app 디렉토리 안에 있는 모든 컴포넌트들은 기본적으로 서버 컴포넌트 방식입니다.
따라서 별도의 설정 없이 바로 서버 컴포넌트를 이용하여 성능을 높일 수 있습니다!

주의할 점은 아래의 상황에서는 반드시 클라이언트 컴포넌트를 사용해야 합니다.

  • useState, useEffect 훅을 사용하는 경우
  • 특정 브라우저 API에 의존성이 있는 경우
  • 특정 Event Listeners를 추가하는 경우

사용법은 "use client"를 최상단에 적어주는 것입니다.

'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked the Count++ button {count} times</p>
      <button onClick={() => setCount(count + 1)}>Count++</button>
    </div>
  );
}

Data Fetching

fetch() Web API를 사용할 수 있게 되면서 SSR data fetching 방식이 변경되었습니다.
기존의 getStaticProps, getServerSideProps, getStaticProps 방식을 fetch() 옵션으로 대체할 수 있습니다.

// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
fetch(URL, { cache: 'force-cache' });

// This request should be refetched on every request.
// Similar to `getServerSideProps`.
fetch(URL, { cache: 'no-store' });

// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
fetch(URL, { next: { revalidate: 10 } });

{ cache: 'force-cache' }

  • fetch의 디폴트 옵션이며 getStaticProps의 역할과 같습니다. 빌드 시에 가져온 데이터가 사용되고 revalidate하기 전까지 데이터가 캐싱되는 방식입니다.

{ cache: 'no-store' }

  • getServerSideProps의 역할과 같습니다. 서버 단에서 매 요청시마다 새로 fetch하게 됩니다.

{ next : { revalidate: 10 } }

  • next 옵션을 통해 캐시 되고 있는 데이터들을 revalidate 시킬 수 있습니다.

이러한 fetch 방식이 가능해진 이유는 서버 컴포넌트와 클라이언트 컴포넌트가 나뉘었기 때문입니다.


Layouts

Layouts를 사용하게 되면 공통 레이아웃의 상태를 유지하고 불필요한 리렌더링을 방지할 수 있다고 합니다.

폴더 경로 안에 layout.tsx 컴포넌트만 추가하면 공통 레이아웃을 적용할 수 있습니다.

// app/layout.tsx
import { ReactNode } from 'react';
import DataNav from './DataNav';

export default function Layout({ children }: { children: ReactNode }) {
  const ids = [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }];
  if (!ids) return null;

  return (
    <div>
      <DataNav ids={ids} />
      <div>{children}</div> // 실제 컨텐츠
    </div>
  );
}

전체적인 컨텐츠 영역은 {children}을 통해서 배치되며, 동일한 라우팅 경로에서 컴포넌트가 변경되어도 최대한 렌더링이 일어나지 않고 유지됩니다.


Streaming

서버 사이드 단에서 컴포넌트를 점진적으로 렌더링 한 뒤 스트리밍 방식으로 클라이언트에게 전달하는 방식입니다.

기존 SSR에서는 화면에 보여줄 데이터들을 API를 통해 fetch해서 가져올 때까지 기다려야 했습니다.

하지만 Streaming을 사용하면 고정적인 레이아웃은 먼저 렌더링 한 뒤 클라이언트로 보내고, data가 필요한 부분만 별도로 렌더링을 한다고 합니다.

게다가 data가 필요한 부분은 가져오기 전까지 알아서 로딩 상태로 표시가 됩니다.

230921-235500


Turbopack (alpha)

Next 팀은 Rust 기반의 강화된 Webpack인 Turbopack을 공개했습니다.
아직은 alpha 단계이지만 속도가 매우 빠르다는 강점을 가지고 있습니다.
13 버전부터는 Turbopack을 기본 번들러로 사용합니다!

  • Webpack보다 700배 빠른 업데이트
  • Vite 보다 10배 빠른 업데이트
  • Webpack보다 4배 빠른 cold start

230919-235424

개발에 필요한 최소한의 어셋만 번들링하기 때문에 시작 시간이 매우 빠르다고 합니다. (Vite와 유사)


New next/image (stable)

요 부분부터는 메이저하기 보다는 사소한 변경사항들입니다.
Next.js에서는 기존에 지원하던 next/image를 강화하여 아래의 기능을 지원한다고 합니다.

  • client-side Javascript 코드 감소
  • alt 태그 필수
  • 스타일링과 설정이 더 편해졌습니다.
  • 네이티브한 lazy 로딩은 hydration이 필요 없기 때문에 더 빨라졌습니다.
import Image from 'next/image';
import avatar from './avatar.png';

function Home() {
  return <Image alt="avatar" src={avatar} placeholder="blur" />;
}

그리고 13 버전부터는 기존의 next/image를 next/legacy/image path로 import 해야합니다.


New @next/font (beta)

@next/font는 구글 폰트를 기본적으로 지원합니다!
게다가 CSS 및 폰트 파일은 빌드 타임에 정적 자원으로 로드되기 때문에 브라우저에서 google로 요청을 보내지 않아도 되는 장점이 있습니다.

  • 커스텀 폰트를 포함한 폰트를 자동 최적화
  • 폰트 성능을 위해 불필요한 추가 request 제거
  • side-adjust 옵션을 이용하여 Layout Shift 제거

전역적으로 폰트를 설정하려면 _app.js의 head 태그의 스타일로 추가하거나, next.config.js 파일을 설정하는 방법이 있습니다.

import { Inter } from '@next/font/google';

const inter = Inter({ subsets: ['latin'] });

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      <style jsx global>{`
        html {
          font-family: ${inter.style.fontFamily};
        }
      `}</style>
      <Component {...pageProps} />
    </>
  );
}
module.exports = {
  experimental: {
    fontLoaders: [
      { loader: '@next/font/google', options: { subsets: ['latin'] } },
    ],
  },
};

이제 Link 컴포넌트를 사용할 때 a 태그를 넣지 않아도 사용이 가능하게 됐습니다!
오히려 이제 a 태그를 Link 안에 포함시킬 수 없게 변경되었습니다.

저도 이전 버전에서 a태그를 왜 또 넣어줘야 하는지.. 불편하다고 느껴왔는데 개선되었다니 좋습니다 ㅎㅎ

import Link from 'next/link'

// 12 버전까지
<Link href="/home">
  <a>Home</a>
</Link>

// 13 버전부터
<Link href="/home">
  Home
</Link>

글을 마치며

이번 Next.js 13 버전에서는 디렉토리 구조나 getServerSideProps 등 Next의 정체성이라 부를 수 있는 기능들이 대거 변경된 것 같습니다.

저는 이번 업데이트 내용을 보면서 이런 큰 변경 사항을 1년 만에 공개할 수 있었다는 것에 감탄...을 하며 변화를 놓치지 않고 부지런히 따라가야겠다는 생각이 들었습니다.

기존 프로젝트들을 13 버전으로 리팩토링 해야하는 시점이 올텐데 그 때 이번 포스팅을 복기하면서 13 버전의 장점들을 적절히 활용할 수 있도록 응용해 보아야겠습니다.

Next.js 쉽게 이해하기 🔍

4 / 4
Next.js를 더 잘 활용할 수 있도록 렌더링 방식의 특징들과 성능 개선 방법에 대해 알아보겠습니다!