안녕하세요, 소플입니다.

얼마 전에 Vercel Ship 24 행사가 있었습니다.

해당 행사에서 Next.js의 다음 버전인 Next.js 15 RC (Release Candidate) 가 공개되었는데요,

이번 매거진에서는 올해 릴리즈 될 Next.js 15에서 바뀌는 부분들에 대해서 한 번 알아보도록 하겠습니다.

영어로 된 공식 블로그 문서는 아래 링크에서 보실 수 있습니다.

Next.js 15 RC
https://nextjs.org/blog/next-15-rc

React 19 RC

먼저 Next.js 15 RC의 가장 큰 변화는 아무래도 React 19 RC를 지원하는 것이 아닐까 싶습니다.

React 19에 대해서는 이전 매거진인 2024 리액트 컨퍼런스를 참고하시면 파악하는데 도움이 되실 겁니다.

그리고 Next.js 15 RC에서는 리액트 19에서 새롭게 추가된 기능들을 사용할 수 있습니다.

React Compiler (Experimental)

이번 리액트 컨퍼런스에서 발표에 가장 큰 분량을 차지했던 것이 바로 React Compiler입니다.

수년간 Meta에서 React Compiler를 개발해왔고 이제 드디어 정식 릴리즈를 앞두고 있는데요,

Next.js 15 RC에서는 아래와 같이 React Compiler를 사용해볼 수 있습니다.

먼저 babel-plugin-react-compiler을 설치합니다.

npm install babel-plugin-react-compiler

다음으로 next.config.ts 파일을 아래와 같이 수정하면 됩니다.

const nextConfig = {
  experimental: {
    reactCompiler: true,
  },
};
 
module.exports = nextConfig;

참고로 현재 Next.js에서는 React Compiler를 Babel 플러그인 형태로만 사용할 수 있으며,

이로 인해 빌드 시간이 늘어날 수 있습니다.

🔗 관련 링크: React Compiler 매거진
https://www.frontoverflow.com/magazine/5/%EB%A6%AC%EC%95%A1%ED%8A%B8%20%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%20(React%20Compiler)

Hydration error improvements

이제 Hydration error 해결 방법에 대한 제안과 함께 에러가 발생한 부분의 소스 코드가 함께 표시된다고 합니다.

먼저 아래 화면은 Next.js 14.1의 Hydration error 메시지입니다.

Next.js 14.1 Hydration error 메시지

그리고 아래 화면은 Next.js 15 RC에서 업데이트 된 모습입니다.

Next.js 15 RC Hydration error 메시지

조금 더 자세한 정보를 표현해주기 때문에 에러를 수정하기에 용이해보입니다.

Caching updates

Next.js 15에서는 fetch 요청, GET Route Handlers, 그리고 Client Router Cache가 기본적으로 캐싱되지 않는 것으로 변경되었습니다.

이전과 동일하게 캐싱을 사용하고 싶으면 옵션을 통해 적용 가능합니다.

fetch 요청은 기본적으로 캐싱되지 않음

Next.js에서는 Web fetch APIcache 옵션을 사용하여, 서버 사이드 fetch 요청이 프레임워크의 persistent HTTP 캐시와 어떻게 상호작용할지 설정하게 됩니다.

fetch('https://...', { cache: 'force-cache' | 'no-store' });
  • no-store: 요청이 있을 때마다 원격 서버에서 리소스를 가져오고 캐시를 업데이트하지 않음
  • force-cache: 캐시(존재하는 경우) 또는 원격 서버에서 리소스를 가져오고 캐시를 업데이트 함

Next.js 14에서는 cache 옵션이 제공되지 않을 경우, force-cache가 기본값으로 사용되었습니다.
(dynamic function이나 dynamic config option을 사용하지 않는 한)

💡 Dynamic Functions
cookies(): 쿠키의 값을 읽거나 쓰기 위한 서버 함수
headers(): 헤더의 값을 읽기위한 서버 함수

Next.js 15에서는 cache 옵션이 제공되지 않을 경우 no-store가 기본 값으로 사용됩니다.

즉, fetch 요청이 기본적으로 캐싱되지 않는다는 것입니다.

그리고 다음과 같은 방법으로 fetch 요청에 개별적으로 캐싱을 적용할 수 있습니다.

  • 단일 fetch 호출에 대해 cache 옵션force-cache로 설정
  • 단일 route에 대해 dynamic route config 옵션force-static으로 설정
  • 자체 캐시 옵션을 명시적으로 지정하지 않는 경우에, Layout 또는 Page의 모든 fetch 요청을 재정의하기 위해 fetchCache route config 옵션default-cache로 설정

GET Route Handlers는 기본적으로 캐싱되지 않음

Next 14에서는 dynamic function이나 dynamic config option을 사용하지 않는 경우에,

GET HTTP 메서드를 사용한 Route Handler가 기본적으로 캐싱되었습니다.

하지만 Next.js 15에서는 GET 함수가 기본적으로 캐싱되지 않습니다.

그리고 static route config 옵션에서 export dynamic = 'force-static' 같은 형태로 캐싱을 적용할 수 있습니다.

그리고 아래와 같은 특별한 Route Handlers는 여전히 static이 기본 값입니다.
(dynamic function이나 dynamic config option을 사용하지 않는 경우에)

Client Router Cache는 더 이상 Page 컴포넌트를 기본적으로 캐싱하지 않음

Next.js 14.2에서는 Router Cache의 커스텀 설정을 위해 실험적으로 staleTime flag를 소개했습니다.

Next.js 15에서는 이 flag에 계속 액세스할 수는 있지만, 기본 동작이 Page 세그먼트에 대해 staleTime0이 되도록 변경됩니다.

즉, 앱을 탐색할 때 클라이언트는 탐색의 일부로 활성화되는 Page 컴포넌트의 최신 데이터를 항상 반영합니다.

하지만 아래와 같은 중요한 동작들은 여전히 변경되지 않고 유지됩니다.

  • partial rendering을 계속 지원하기 위해 Shared layout 데이터는 서버에서 다시 가져오지 않음.
  • 브라우저에서 스크롤 위치를 복원할 수 있도록 back/forward 탐색은 캐시에서 복원됨.
  • loading.js는 5분 동안 캐싱된 상태로 유지됨(또는 staleTimes.static 설정 값).

그리고 아래와 같이 설정하면 이전 Client Router Cache의 동작을 사용할 수 있습니다.

const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
    },
  },
};
 
module.exports = nextConfig;

Partial Prerendering의 점진적 채택 (Experimental)

Next.js 14에서는 Partial Prerendering(PPR)이라는 기법이 소개되었는데요,

Next.js는 이제 cookies(), headers() 및 캐시되지 않은 데이터 요청과 같은 동적 기능을 사용하지 않는 경우에 기본적으로 정적 렌더링을 사용합니다.

PPR을 사용하면 모든 동적 UI를 Suspense로 감쌀 수 있으며,

새로운 요청이 들어오면 Next.js는 즉시 정적 HTML shell을 제공하고,

그 다음으로 동일한 HTTP 요청에서 동적인 부분을 렌더링하고 스트리밍합니다.

💡 Partial Prerendering
하나의 페이지에 대해 static과 dynamic 렌더링을 합쳐서 최적화하는 것
(정적인 부분을 먼저 빠르게 응답하고 이후에 동적인 부분을 불러오는 형태)

특정 레이아웃과 페이지에 PPR을 적용하기 위해서는 먼저 아래와 같이 next.config.ts 파일에 experimental.pprincremental로 설정해야 합니다.

const nextConfig = {
  experimental: {
    ppr: 'incremental',
  },
};
 
module.exports = nextConfig;

그리고 각 페이지에서 아래와 같이 experimental_ppr route config 옵션을 사용하면 됩니다.

import { Suspense } from "react";
import { StaticComponent, DynamicComponent } from "@/app/ui";
 
export const experimental_ppr = true;
 
export default function Page() {
    return {
        <>
            <StaticComponent />
            <Suspense fallback={...}>
                <DynamicComponent />
            </Suspense>
        </>
    };
}

next/after를 사용하여 응답 이후에 코드 실행 (Experimental)

사용자 요청을 처리할 때 서버는 일반적으로 응답(response)과 직접 관련된 작업을 수행합니다.

그러나 로깅, 분석 및 기타 외부 시스템 동기화와 같은 작업을 수행해야 할 수도 있습니다.

이러한 작업은 응답과 직접 관련이 없으므로 사용자가 완료될 때까지 기다릴 필요가 없습니다.

하지만 서버리스 함수는 응답이 종료되는 즉시 중단되기 때문에, 사용자에게 먼저 응답한 이후에 다른 작업들을 수행하도록 하는 것이 어렵습니다.

after()는 응답 스트리밍이 완료된 이후에 처리할 작업을 예약하여 기본 응답을 차단하지 않고 보조 작업을 실행할 수 있도록 해주는 실험적인 API입니다.

이것을 사용하려면 먼저 아래와 같이 next.config.tsexperimental.after를 추가해야 합니다.

const nextConfig = {
  experimental: {
    after: true,
  },
};
 
module.exports = nextConfig;

그리고 이후에 Server Components, Server Actions, Route Handlers, 또는 Middleware에서 아래와 같이 import해서 사용하면 됩니다.

import { unstable_after as after } from 'next/server';
import { log } from '@/app/utils';
 
export default function Layout({ children }) {
  // Secondary task
  after(() => {
    log();
  });
 
  // Primary task
  return <>{children}</>;
}

create-next-app updates

Next.js 15에서는 create-next-app이 아래 그림과 같이 새로운 디자인으로 업데이트 됐습니다.

create-next-app update

그리고 create-next-app 실행 과정에서 Turbopack을 사용할지 묻는 프롬프트가 추가되었습니다.

✔ Would you like to use Turbopack for next dev? … No / Yes

Turbopack을 활성화 하기 위해서 아래와 같이 --turbo flag를 사용할 수도 있습니다.

npx create-next-app@rc --turbo

그리고 새로운 프로젝트를 시작하는 것을 조금 더 쉽게 하기 위해서 --empty flag가 추가되었습니다.

아래와 같이 --empty flag를 사용하면 관계없는 파일과 스타일은 제거되고 미니멀한 "hello world" 페이지만 나오게 됩니다.

npx create-next-app@rc --empty

외부 패지키 번들링(bundling) 최적화 (Stable)

외부 패키지를 번들링하면 애플리케이션의 cold start ​​성능이 향상될 수 있습니다.

App Router에서는 외부 패키지들이 기본적으로 번들링 됩니다.
그리고 serverExternalPackages 옵션을 사용하면 특정 패키지만 제외할 수도 있습니다.

반면에 Pages Router에서는 외부 패키지들이 기본적으로 번들링되지 않습니다.
대신, transpilePackages 옵션을 사용해서 번들링 할 패키지 목록을 제공하면 됩니다.

그리고 이러한 App Router와 Pages Router의 옵션을 통합하기 위해, 아래와 같이 새로운 옵션이 추가되었습니다.
bundlePagesRouterDependencies는 App Router의 기본 자동 번들링과 통합시키고,
serverExternalPackages는 특정 패키지를 번들링에서 제외시킵니다.

const nextConfig = {
  // Pages Router에서 외부 패키지들을 자동으로 번들링 함
  bundlePagesRouterDependencies: true,
  // App Router와 Pages Router에서 특정 패키지들을 모두 번들링하지 않음
  serverExternalPackages: ['package-name'],
};
 
module.exports = nextConfig;

요약 및 마무리

지금까지 나왔던 내용을 간단하게 요약해보면 다음과 같습니다.

  • React
    • React 19 RC, React Compiler (Experimental) 지원 및 hydration error improvements
  • Caching
    • fetch 요청, GET Route Handlers, 그리고 Client Router Cache가 기본적으로 캐싱되지 않음
  • Partial Prerendering (Experimental)
    • PPR의 점진적 채택을 위한 레이아웃과 페이지의 새로운 설정 옵션
  • next/after (Experimental)
    • 응답 스트리밍이 완료된 이후에 코드를 실행하기 위한 실험적인 API
  • create-next-app
    • 디자인 업데이트 및 로컬 개발환경에서 Turbopack을 활성화하기 위한 flag 추가
  • Bundling external packages (Stable)
    • App과 Pages Router를 위한 새로운 설정 옵션 추가

그리고 아래 명령어를 통해 Next.js 15 RC를 지금 바로 사용할 수 있습니다! 😎

npm install next@rc react@rc react-dom@rc

이번 매거진에서는 다가올 Next.js 15버전을 앞두고 어떤 변화가 생길지 미리 알아보았습니다.

개발하시는 프로젝트에 필요한 기능들을 하나씩 살펴보고 적용해보시면 좋을 것 같습니다.

그럼 저는 다음에 또 유익한 글로 찾아뵙겠습니다!

지금까지 소플이었습니다. 감사합니다 😀

지금 가입하고 새로운 매거진을 이메일로 받아보세요!

Copyright ⓒ Soaple. All rights reserved.