Nextjs 15 에서 레이아웃 잡는 방법 (layout.js)

Nextjs 15 에서 레이아웃 잡는 방법 (layout.js)
TILPosted On Apr 22, 202518 min read

layout.js 이해하기

Next.js에서 layout.js 파일은 애플리케이션의 **레이아웃(화면 구성 틀)**을 정의하는 데 사용돼요. 쉽게 말해, 여러 페이지에서 공통적으로 사용되는 UI 구조를 한 곳에 모아서 관리할 수 있게 해주죠.

예를 들어, 아래 코드를 살펴볼게요:

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

여기서 DashboardLayout 컴포넌트는 내부에 들어올 콘텐츠(여기서는 children)를 <section> 태그 안에 감싸서 렌더링해요. 이 의미는, DashboardLayout 위에 두고 싶거나 반복적으로 쓰이는 헤더, 사이드바 같은 요소를 여기에 추가하면 더 편하게 관리할 수 있다는 점이죠.


루트 레이아웃 (Root Layout)이란?

Next.js 13버전부터는 앱 폴더 안에 layout.js 파일을 두는 게 많아졌는데, 그 중에서도 루트 레이아웃은 가장 최상단에 위치하는 레이아웃입니다. 이 루트 레이아웃은 앱 전체에 공통으로 적용할 html, body 태그를 정의하고 전역 스타일, 폰트, 메타데이터, 그리고 공통 UI(예: 네비게이션 바)를 배치할 때 사용돼요.

예를 들어, 이렇게 작성할 수 있죠:

export default function RootLayout({ children }) {
  return (
    <html lang="ko">
      <body>
        {/* 공통 헤더 */}
        <header>
          <nav>내 네비게이션 바</nav>
        </header>
        
        {/* 페이지 컨텐츠 */}
        <main>{children}</main>
        
        {/* 공통 푸터 */}
        <footer>© 2024 내사이트</footer>
      </body>
    </html>
  )
}

이렇게 하면, 모든 페이지가 이 루트 레이아웃 안에서 렌더링되고, 중복되는 UI 코드를 매번 작성하지 않아도 돼서 유지보수가 훨씬 쉬워집니다.


추가로 알아두면 좋은 팁

  • 중첩 레이아웃(Nested Layout): Next.js는 여러 개의 layout.js 파일을 각 폴더마다 만들 수 있어요. 예를 들어, app/dashboard/layout.js는 대시보드 구역에만 적용되는 레이아웃으로 설정할 수 있답니다.
  • 서버 컴포넌트: 기본적으로 layout.js는 서버 컴포넌트로 동작해요. 그래서 클라이언트 전용 기능(이벤트 핸들러 등)은 use client 지시자를 따로 써서 클라이언트 컴포넌트로 만들어야 해요.
  • 스타일링: 루트 레이아웃에 글로벌 CSS를 불러오거나, 구글 폰트 같은 외부 폰트를 적용하기 좋은 위치입니다.

Next.js의 레이아웃 시스템을 잘 활용하면 코드가 깔끔해지고, 프로젝트가 커져도 구조를 쉽게 관리할 수 있어요. 한 번 직접 만들어보고 중첩 레이아웃도 시험해보길 추천합니다! 😊

자, 이번에는 React 컴포넌트에서 자주 사용되는 RootLayout 예제를 살펴볼게요. 위 코드는 Next.js 13부터 도입된 새로운 app 디렉토리 구조에서 루트 레이아웃을 정의하는 기본적인 방법입니다.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

이 컴포넌트는 부모 컴포넌트로부터 children이라는 prop을 받아서, 그 안에 어떤 하위 컴포넌트가 들어와도 감싸주는 역할을 합니다.

주요 내용 정리

Props타입설명
childrenReact.ReactNode (필수)하위 컴포넌트나 JSX를 포함하는 prop

추가로 알아두면 좋은 점!

  • <html lang="en"> 태그는 페이지 전체 언어 설정을 나타내서 SEO와 접근성 측면에서 매우 중요해요. 상황에 맞게 lang 속성을 적절히 바꿔주세요.
  • RootLayout은 모든 페이지에 공통적으로 적용되는 레이아웃이 들어가는 곳이에요. 예를 들어 네비게이션 바, 푸터, 글로벌 스타일 등을 넣을 수 있죠.
  • React에서 props 타입을 지정할 땐 보통 TypeScript를 사용해 이런 형태로 명시해 줍니다. 이를 통해 컴포넌트 사용 시 타입 안정성을 확보할 수 있어요.

이 간단한 구조를 기반으로, 여러분만의 멋진 레이아웃을 만들어 보세요! 😊

레이아웃 컴포넌트는 children 프로퍼티를 받아야 하고, 이를 사용해서 렌더링해야 해요. 렌더링 시점에 children에는 현재 레이아웃이 감싸고 있는 라우트의 하위 세그먼트들이 들어오게 되죠. 주로 자식 레이아웃 컴포넌트(있다면)나 페이지 컴포넌트가 될 거예요. 그리고 상황에 따라 로딩(Loading)이나 에러(Error) 같은 특별한 파일들도 포함될 수 있답니다.


params (선택사항)

params는 비동기적으로 동적 라우트 파라미터를 얻을 때 사용해요. 루트 세그먼트부터 현재 레이아웃까지의 경로에 해당하는 모든 동적 파라미터들을 객체 형태로 받아올 수 있죠.

예를 들자면, 아래처럼 사용할 수 있어요:

export default async function Layout({
  params,
}: {
  params: Promise<{ team: string }>
}) {
  const { team } = await params;
  // 이제 team 변수를 사용해서 팀별 UI를 구성할 수 있어요
}

여기서 알아두면 좋은 점!

  • Layout 컴포넌트가 children을 받아 렌더링하는 구조 덕분에 중첩된 레이아웃을 쉽게 다룰 수 있어요.
  • 비동기 params를 통해 페이지마다 필요한 데이터를 라우팅 단계에서 미리 받아와서, UI를 더 빠르게 그리고 사용자 맞춤형으로 보여줄 수 있답니다.
  • 이 방식을 사용하면 레이아웃별로 공통 요소(헤더, 사이드바 등)를 한 번만 선언하고, 그 안에 컨텐츠를 중첩해서 관리할 수 있어 유지보수가 훨씬 용이해져요.

이런 구조를 이해하면, Next.js 같은 최신 프레임워크의 라우팅과 레이아웃 시스템을 훨씬 더 능숙하게 다룰 수 있게 됩니다!

아래는 Next.js의 동적 라우팅 예시입니다. 각 경로에 해당하는 URL과 넘겨받는 params 값을 보여주고 있어요.

Example RouteURLparams
app/dashboard/[team]/layout.js/dashboard/1Promise<{ team: '1' }>
app/shop/[tag]/[item]/layout.js/shop/1/2Promise<{ tag: '1', item: '2' }>
app/blog/[...slug]/layout.js/blog/1/2Promise<{ slug: ['1', '2'] }>

여기서 중요한 점! params는 Promise 형태로 전달되기 때문에 async/await를 사용하거나 React의 use 함수 같은 비동기 핸들링이 필요합니다.
Next.js 버전 14 및 이전에서는 params가 동기적 prop이었는데, 버전 15부터는 비동기 형태로 바뀌었어요. 다만 아직 하위 호환성을 위해 동기식 접근도 가능하지만, 앞으로는 deprecated 될 예정이라 미리 비동기로 바꿔서 사용하는 게 좋습니다.


루트 레이아웃 (Root Layout)

Next.js의 앱 디렉터리 구조에서는 반드시 최상위에 app/layout.js 파일이 있어야 합니다. 이 파일이 애플리케이션의 공통 레이아웃을 담당하며, 페이지 전반에 걸쳐 공통적으로 적용할 UI(헤더, 푸터, 네비게이션 등)를 넣는 곳이죠.


추가 팁!
라우터 파라미터를 Promise로 받는다는 점이 처음엔 헷갈릴 수 있지만, 이 덕분에 서버 사이드 렌더링 환경에서 비동기로 데이터를 받아와 처리하는데 유연성을 얻게 됩니다. layout.js뿐 아니라, 각 컴포넌트에서 비동기 작업을 자연스럽게 하도록 돕는 구조라는 걸 기억하세요!

Next.js에서 Root Layout을 설정할 때 몇 가지 꼭 알아둬야 할 핵심 포인트들이 있어요. 제가 직접 코드를 보면서 정리해봤는데요, 실제로 개발할 때 참고하면 딱 좋을 것 같아서 공유합니다!

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}

1. htmlbody 태그는 Root Layout에서 꼭 정의해야 해요

Root Layout은 애플리케이션 전체의 뼈대가 되기 때문에, <html>, <body> 태그를 직접 작성해줘야 합니다.
그런데 이 밖에 head 태그나 그 안에 들어가는 title, meta 같은 요소들은 직접 넣으면 안 된다는 점!
이걸 next.js에서 Metadata API라는 걸로 관리해줘서, 머리 정보들을 자동으로 스트리밍 처리하고 중복 제거까지 해준답니다. 이게 꽤 편하니까 꼭 활용해 보세요.

2. head 태그는 Metadata API 사용하기

직접 head 태그에 title, meta 이런 거 넣으면 안 되고, next.js가 제공하는 Metadata API를 써서 데이터 설정해야 해요.
예를 들어, layout이나 page에서 export const metadata = { title: '홈페이지' } 이렇게 지정할 수 있습니다.
이 방식 덕분에 로딩 속도는 빨라지고 SEO도 좋아지죠!

3. 여러 Root Layout 만들기! (Route Groups)

Routes를 그룹으로 묶어서 각 그룹마다 Root Layout을 다르게 만들 수 있어요.
예를 들어, 쇼핑몰 영역과 블로그 영역이 각각 app/(shop)/layout.jsapp/(marketing)/layout.js를 쓴다고 하면,
이 두 구역 사이를 이동할 때는 완전한 페이지 리로드가 발생해요. (즉, 클라이언트 사이드 네비게이션이 아니라 서버에서 다시 불러오는 방식)
이 점은 여러 개의 Root Layout을 사용할 때 발생하는 특성입니다.


궁금증 해결 : Layout에서 Request 객체에 접근할 수 있나요?

Root Layout이나 Layout 컴포넌트에서 직접 HTTP Request 객체에 접근하는 건 불가능합니다.
Next.js 앱 디렉토리(app router)는 서버 컴포넌트 기반이고, 서버 컴포넌트에서는 요청 정보를 직접 다루는 게 제한돼 있거든요.

만약 요청에 포함된 데이터(query, headers, cookies 등)를 사용하고 싶다면, fetch 함수나 서버 컴포넌트에서 사용할 수 있는 다양한 Next.js API를 활용해서 데이터를 미리 받아오는 패턴을 주로 사용합니다.

예를 들어:

export default async function RootLayout({ children }) {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return (
    <html>
      <body>
        {/* 여기서 data를 활용할 수 있어요 */}
        {children}
      </body>
    </html>
  );
}

또한, 특정 요청 헤더나 쿠키 등이 필요하면 Next.js가 제공하는 cookies()headers() 함수를 사용할 수도 있으니 참고하세요.


Next.js 13의 앱 라우터 구조가 처음엔 조금 낯설지만, 이런 Root Layout 기본 규칙을 이해하고 나면 훨씬 유연하게 프로젝트를 구성할 수 있답니다!
궁금한 점 있으면 언제든 말씀해 주세요. 개발 힘내시고 좋은 하루 보내세요~ 😊

Next.js에서 레이아웃(layout)은 원시 요청 객체(raw request object)에 직접 접근할 수 없도록 의도적으로 제한되어 있습니다. 대신, 서버 전용 함수(server-only functions)를 통해 헤더(headers)와 쿠키(cookies) 정보는 가져올 수 있어요.

이유는 간단해요. 레이아웃은 페이지 간 이동 시 불필요한 연산을 줄이기 위해 한 번 렌더링된 뒤 재사용될 수 있도록 설계되어 있어요. 만약 레이아웃에서 매 요청마다 원시 요청 객체에 접근해 무거운 작업을 수행하게 된다면, 그만큼 성능에 악영향이 가기 때문이죠.

또한, 이런 제한 덕분에 레이아웃이 여러 페이지에서 일관적이고 예측 가능한 방식으로 동작하도록 보장합니다. 개발자 입장에선 디버깅도 훨씬 간편해지는 장점이 있어요.

참고로, 레이아웃은 searchParams(검색 파라미터)도 직접 받지 않습니다.

이 부분 꼭 알아두시면 좋아요. 만약 query string 정보를 레이아웃에서 활용하고 싶다면, 서버 함수에서 받아서 헤더나 쿠키에 저장하거나, 페이지 컴포넌트 쪽에서 처리하는 방식을 생각해 볼 수 있습니다.


간단 정리 테이블

제한 사항이유 및 설명
원시 요청 객체 접근 불가렌더링 성능 최적화와 일관성 있는 레이아웃 동작 보장
헤더와 쿠키는 서버 함수 통해 접근 가능필요한 요청 정보만 안전하게 접근할 수 있도록
레이아웃은 검색 파라미터 수신 불가검색 파라미터는 페이지에서 처리하거나 서버 함수로 받아야 함

이런 구조를 이해하면 Next.js 레이아웃을 설계할 때 어디서 어떤 정보를 받아서 처리해야 하는지 명확해져서 개발이 훨씬 수월해집니다. 필요할 땐 서버 함수 잘 활용해서 헤더, 쿠키 같은 정보만 쏙쏙 뽑아 쓰세요!

Pages와 달리 Layout 컴포넌트는 searchParams props를 받지 않아요. 그 이유는, 공통 레이아웃(shared layout)은 페이지가 이동해도 재렌더링되지 않기 때문에, 만약 searchParams를 받는다면 이전 네비게이션의 값이 그대로 남아있어서 최신 상태가 아니게 될 수 있기 때문이죠.

Next.js에서 클라이언트 사이드 네비게이션을 할 때, 공통 레이아웃 아래의 페이지 일부만 자동으로 렌더링해줘서, 예를 들어 아래와 같은 디렉터리 구조가 있다고 할 때:

dashboard/ ├── layout.tsx ├── settings.tsx └── analytics.tsx

dashboard/layout.tsx/dashboard/settings/dashboard/analytics 두 경로 모두에 걸친 공통 레이아웃으로 작동합니다. 이 말은, 사용자가 /dashboard/settings에서 /dashboard/analytics로 이동해도 dashboard/layout.tsx는 다시 렌더링되지 않는다는 거죠.

즉, 공통 레이아웃은 한 번 렌더링한 뒤 유지되어서 불필요한 렌더링 부담이 줄어들고, 페이지 전환이 더 빠르게 느껴지게 돼요.


그리고 참고로, 만약 공통 레이아웃에서 현재 URL의 query(=searchParams)를 꼭 사용해야 한다면, 레이아웃 내부가 아니라 개별 페이지 컴포넌트 내에서 useSearchParams() 같은 React 훅을 활용하는 게 좋아요. 그쪽이 항상 최신 URL 상태를 반영할 수 있답니다!

"/dashboard/settings"에서 "/dashboard/analytics"로 이동할 때, "/dashboard/analytics/page.tsx"는 서버에서 다시 렌더링이 되지만, "dashboard/layout.tsx"는 두 경로에서 공통으로 사용하는 레이아웃 컴포넌트라서 다시 렌더링되지 않습니다.

이런 최적화 덕분에, 공통 레이아웃을 공유하는 페이지 간 이동 시에는 전체 경로를 다시 그릴 필요 없이, 페이지 단위의 데이터 페칭과 렌더링만 수행되기 때문에 훨씬 빠른 네비게이션이 가능합니다. 즉, 레이아웃까지 포함한 전체 경로가 다시 렌더링되면 시간이 더 걸리지만, 공통 레이아웃이 그대로 유지되니 성능이 더 좋아지는 거죠.

하지만 여기서 주의할 점! layout.tsx가 다시 렌더링되지 않으니, 이 레이아웃 컴포넌트에 있는 searchParams prop은 navigation 후에 구버전(stale)이 될 수 있습니다.

그래서 검색 파라미터가 필요할 땐, 레이아웃에서 바로 쓰지 말고 아래 두 가지 방법 중 하나를 이용하는 걸 추천해요.

  1. Page 컴포넌트에서 전달받는 최신 searchParams prop을 사용하기
  2. 레이아웃 내 클라이언트 컴포넌트에서 useSearchParams 훅을 사용해서 최신값을 받아오기

이렇게 하면 클라이언트에서 렌더링될 때 최신 searchParams를 쓸 수 있어 데이터가 꼬이는 문제를 예방할 수 있답니다.


tip!

  • 클라이언트 컴포넌트에서 useSearchParams를 쓸 때는 "use client" 지시어를 꼭 선언해야 해요.
  • 만약 레이아웃이 공통 상태나 데이터를 많이 관리한다면, 이런 최적화가 큰 차이를 만들어낼 수 있어요.
  • Next.js 앱 디렉토리 구조에서는 이런 레이아웃 재사용과 부분 렌더링 최적화가 핵심이니 꼭 이해해두시면 좋아요!

더 궁금한 점 있으면 편하게 물어봐 주세요~

레이아웃(Layouts)에서 pathname에 접근할 수 없는 이유

Next.js에서 레이아웃은 기본적으로 **서버 컴포넌트(Server Components)**로 동작하는데요, 이게 왜 문제가 되냐면 클라이언트 사이드 네비게이션(페이지 간 이동) 시에 레이아웃이 다시 렌더링되지 않아요.
즉, pathname 같은 네비게이션 상태가 바뀌더라도 레이아웃은 리렌더링하지 않기 때문에, pathname 정보가 실제와 다르게 **stale(오래된 정보)**가 될 수 있습니다.

그렇다고 레이아웃을 매번 새로 서버에서 받아오면 캐싱의 이점도 날아가고, RSC(Server Components) 페이로드 크기도 커져서 비효율적이겠죠.


그럼 어떻게 해야 하나요?

이럴 때는 pathname을 필요로 하는 부분의 로직을 **클라이언트 컴포넌트(Client Component)**로 분리하는 방법이 좋아요. 클라이언트 컴포넌트는 페이지가 네비게이션할 때마다 리렌더링 되지만, 서버에서 다시 받아오진 않기 때문에 최신의 pathname 값을 바로바로 사용할 수 있어요.

Next.js는 usePathname 같은 훅도 제공해서 현재 경로를 쉽게 알 수 있게 해줍니다.


예제 코드

import { ClientComponent } from '@/app/ui/ClientComponent'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <ClientComponent />
      {/* 여기에 레이아웃의 다른 UI들 */}
      <main>{children}</main>
    </>
  )
}

ClientComponent 안에서는 이렇게 쓸 수 있겠죠:

"use client"
import { usePathname } from 'next/navigation'

export function ClientComponent() {
  const pathname = usePathname()

  return <div>현재 경로: {pathname}</div>
}

정리하며 드리는 팁!

  • 레이아웃에서 서버 컴포넌트와 클라이언트 컴포넌트를 적절히 나누자
    너무 많은 상태나 UI를 클라이언트 컴포넌트로 두면 서버 컴포넌트의 장점이 줄지만, 라우팅 상태처럼 자주 변하는 데이터는 클라이언트 컴포넌트가 적절해요.

  • usePathname 외에도 useSearchParams, useRouter 같은 훅도 비슷한 이유로 클라이언트 컴포넌트에서 써야 해요.

  • 최신 Next.js는 서버 컴포넌트와 클라이언트 컴포넌트를 잘 섞어 쓰는 패턴을 권장하니 이를 염두에 두고 컴포넌트를 설계하면 더 편하고 최적화된 앱을 만들 수 있습니다!

경로 패턴을 다룰 때, 흔히 사용하는 방법 중 하나가 바로 params(props)를 활용하는 거예요.

좀 더 구체적인 내용과 예시는 아래 'Examples' 섹션을 참고하면 도움이 될 거예요.

예시

params를 이용해 콘텐츠를 보여주기

이렇게 params를 활용하면 URL에서 특정 값을 파라미터로 받아서 동적으로 콘텐츠를 바꾸는 게 가능해져요. 예를 들어 사용자가 특정 아이템의 상세 페이지를 보게 할 때, URL에 ID를 넣고 그 ID에 맞는 정보를 렌더링할 수 있죠.

기본적으로 params는 라우트에서 토큰화된 URL 조각들을 받아서 컴포넌트에 전달해주는 역할을 합니다. react-router 같은 라이브러리를 쓴다면 자연스럽게 사용할 수 있죠.

다음 예시를 참고해볼게요:

URLparams설명
/user/123{ id: '123' }유저 아이디가 123인 페이지 보여주기
/product/456{ productId: '456' }상품 상세 페이지를 productId에 맞게 보여주기

이처럼 params를 잘 활용하면, 같은 컴포넌트가 다양한 경로 데이터를 받아 콘텐츠를 유동적으로 바꿔줄 수 있어서 프로젝트 관리하기 훨씬 편해진답니다. 필요하면 제가 더 실무 예제도 공유해드릴게요!

동적 라우트 세그먼트를 활용하면 params 프로퍼티를 통해 특정 콘텐츠를 보여주거나 가져올 수 있어요.

export default async function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}) {
  const { team } = await params
 
  return (
    <section>
      <header>
        <h1>Welcome to {team}'s Dashboard</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}

위 예제는 Next.js에서 동적 세그먼트를 받고, 그 파라미터를 비동기(await)로 풀어서 사용하는 모습이에요. 여기서 params{ team: string } 형태라고 타입이 지정돼 있죠.


클라이언트 컴포넌트에서 params 읽기

서버 컴포넌트에서는 async/await로 params를 다룰 수 있어요. 하지만 클라이언트 컴포넌트는 async 함수를 지원하지 않아서 직접 params를 await할 수 없죠. 이때는 React의 use 훅을 사용해서 Promise를 처리할 수 있는데, 다음과 같이 활용해요:

'use client'

import { use } from 'react';

export default function Dashboard({ paramsPromise }: { paramsPromise: Promise<{ team: string }> }) {
  const { team } = use(paramsPromise);

  return <h1>Welcome to {team}'s Dashboard</h1>;
}

여기서 use 훅은 React 18부터 실험적으로 지원하는 훅인데, Promise를 직접 커버할 수 있어서 비동기 데이터를 자연스럽게 렌더링할 수 있게 도와줍니다.


추가 팁!

  • Next.js 13의 app 디렉터리를 쓸 때는 동적 라우트를 만들면 자동으로 params가 들어오고, 그걸 서버 컴포넌트에서 바로 사용할 수 있어서 편리해요.
  • 클라이언트 컴포넌트에서 async 로직을 처리할 때는 보통 useEffect와 상태관리를 쓰지만, use 훅을 쓰면 좀 더 깔끔한 코드가 될 수 있어요.
  • 다만 use 훅은 아직 실험적 기능이라 프로젝트에 맞게 신중히 사용해야 합니다.

이런 방식으로 동적 라우트를 활용해서 사용자 맞춤 대시보드나 상세 페이지를 쉽게 구현할 수 있답니다!

'use client'

import { use } from 'react'

export default function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = use(params)
}

버전 히스토리

버전변경 사항
v15.0.0-RCparams가 이제 Promise로 변경되었습니다. codemod 도 제공됩니다.
v13.0.0layout이 새로 도입되었습니다.

위 코드를 보면 params가 Promise 형태로 전달되고, React 18에서 도입된 use 훅을 사용해서 그 Promise를 직접 해결(resolve)하는 방식을 사용하고 있네요.

이 방식은 서버 컴포넌트에서 비동기 데이터를 쉽게 처리할 수 있게 해줘서, 데이터 로딩 코드를 더 깔끔하게 작성할 수 있다는 장점이 있어요.

참고로 use 훅은 아직 React 공식 API가 아니기 때문에, Next.js 같은 프레임워크에서 실험적으로 지원하는 기능이에요. 그러니 프로젝트에 적용할 때는 호환성을 꼭 확인하는 게 좋아요!

그리고 params가 Promise로 바뀐 이유는 서버 컴포넌트가 비동기적으로 라우팅 파라미터를 처리할 수 있도록 하기 위해서인데요, 이를 통해 더 유연한 데이터 페칭과 페이지 렌더링이 가능해졌답니다.

더 자세한 내용은 Next.js 공식 문서나 업그레이드 가이드를 참고해보세요!