본문 바로가기
IT/react

[번역][Next.js] Layout RFC

by Josh.P 2022. 7. 29.
반응형

2022년 5월 24일에 Next.js에서 발표된 Layout RFC에 대해서 번역입니다. 원본 보기

 

Layout RFC는 2016년 이후 Next.js의 가장 큰 업데이트라고 합니다.

Motivation

Next.js에서는 Github, Discord, Reddit, 개발자 설문조사를 통해서 현재 Next.js의 라우팅 방식의 한계에 대한 피드백을 받았다. 그 결과로, 아래 두 가지 결과를 도출했다.

  • layout 생성에 대한 개발자 경험이 향상될 수 있다. 중첩(nested)되고, routes간에 공유되고, navigation에서 상태를 유지할 수 있도록 layout을 쉽게 생성할 수 있어야 한다.
  • 많은 Next.js 어플리케이션은 대시보드이거나 콘솔이다. 그래서 향상된 라우팅 솔루션의 이점을 누릴 수 있다.

Terminology

RFC는 새로운 convention과 syntax를 소개한다. 용어는 React와 표준 웹 플랫폼 용어를 기반으로 한다.

  • Tree: 계층 구조를 표현하는 convention
  • Subtree: tree의 부분이다.

  • URL Path: domain 이후에 오는 URL의 부분
  • URL Segment: slash(/)에 의해서 구별되는 URL의 부분

How Routing Currently Works

Next.js는 URL을 통해 접근할 수 있는 경로로 “Pages” 디렉토리의 개별 폴더와 파일을 매핑하는 파일 시스템을 사용한다. 각 Page 파일은 React 컴포넌트로 export되고, 파일 이름에 따라서 관련된 경로가 있다.

  • Dynamic Routes: Next.js는 [param].js, [...param].js, [[...param]].js 규칙으로 Dynamic Routes를 지원한다.
  • Layouts: Next.js는 간단한 컴포넌트 기반 layout, 컴포넌트 property 패턴을 사용한 페이지별 layout, 커스텀 app을 사용하여 하나의 글로벌 layout을 지원한다.
  • Data Fetching: Next.js는 페이지 레벨에서 사용될 수 있는 data fetching method(getStaticProps, getServerSideProps)을 제공한다. 이 method는 페이지가 정적으로 생성(getStaticProps)되거나 서버 사이드 렌더링(getServerSideProps)을 해야될 지 결정하는데 사용된다. 또한, 사이트가 빌드될 후에 정척 페이지를 만들거나 업데이트하는 Incremental Static regeneration(ISR)을 사용할 수 있다.
  • Rendering: Next.js는 3가지 rendering option(Static Generation, Server-Side Rendering, Client-Side Rendering)을 제공한다. 기본적으로 페이지는 정적으로 생성된다.

Introducing the App Folder

새로운 개선 사항을 점진적으로 적용하고, 큰 변경을 피하기 위해서 app이라는 새로운 디렉토리를 제안한다.

app 디렉토리는 pages 디렉토리와 같이 동작한다. 새로운 기능을 활용하기 위해서 어플리케이션의 일부를 점진적으로 새로운 app 디렉토리로 이동할 수 있다. 이전버전과의 호환성을 위해 pages 디렉토리의 동작은 동일하게 유지되고, 계속 지원된다.

Defining Routes

경로를 정의하기 위해 app 안에 폴더를 사용할 수 있다. 경로는 root 폴터에서 최종 leaf 폴더까지 계층 구조를 따르는 중첩(nested) 폴더의 단일 경로이다.

예를 들어, app 디렉토리에 두 개의 중첩된 폴더에 의해서 /dashboard/settings을 추가할 수 있다.

Route Segments

subtree의 각 폴더는 route segment를 표현한다. 각 route segment는 URL Path의 segment와 매핑된다.

예를 들어, /dashboard/settings 경로는 3가지 segment로 이뤄진다.

  • / root segment
  • dashboard segment
  • settings segment

Layouts

새로운 파일 convention: layout.js

지금까지 어플리케이션의 경로를 정의하는데 폴더를 사용했었다. 하지만, 빈 폴더는 자체적으로 아무 작업도 수행하지 않는다. 새로운 파일 규칙을 사용해서 이러한 경로를 렌더링할 UI를 정의하는 방법에 대해 논의해보자.

layout은 subtree의 route segment간 공유되는 UI이다. Layout은 URL paths에 영향을 주지 않고, 사용자가 형제 segment를 이동할 때 re-render되지 않는다.

layout은 기본적으로 layout.js 파일에서 React Component를 export하여 정의할 수 있다. 컴포넌트는 layout이 래핑하는 segment로 채워질 children prop을 수용해야 한다.

layout은 2가지 타입이 있다.

  • Root Layout: 모든 routes에 적용된다.
  • Regular layout: 특정 경로만 적용된다.

둘 이상의 layout을 함께 중첩하여 nested layout을 생성할 수 있다.

Root Layout

app 폴더 안에 layout.js 파일을 추가하여 모든 경로에 적용될 root layout을 생성할 수 있다.

💡 root layout은 모든 경로에 적용되므로 Custom App(`_app.js`)와 custom Document(`_document.js`)의 필요를 대체한다.

💡 root layout을 사용하여 초기 문서 shell(``, `` 태그)를 사용자 지정할 수 있다.

💡 root layout에서 data fetching 함수를 사용할 수 있다.

Regular layouts

특정 폴더 안에 layout.js 파일을 추가하여 어플리케이션의 일부분만 적용되는 layout을 생성할 수 있다.

예를 들어, dashboard 내의 route segment에만 적용되도록 dashboard 폴더 안에 layout.js를 생성할 수 있다.

Nesting layouts

Layout은 기본적으로 중첩된다.

예를 들어, 위의 두 가지 layout을 결합하면, root layout(app/layout.js)은 dashboard layout에 적용된다. 그리고 dashboard/*의 모든 route segment에도 적용된다.

Pages

새로운 파일 convention: page.js

page는 route segment의 고유한 UI이다. 폴더 안에 page.js 파일을 추가하여 page를 생성할 수 있다.

예를 들어, /dashboard/* 경로에 대한 page를 생성하려면 각 폴더 안에 page.js를 추가하면 된다. 사용자가 /dashboard/settings을 방문했을 때, Next.js는 subtree 상위에 있는 layout으로 래핑된 seetings 폴더에 대한 page.js 파일을 렌더링한다.

dashboard 폴더 안에 직접 page.js 파일을 생성하여 dashboard 경로에 매핑할 수 있다.

이 경로는 2 segment로 이루어진다.

  • / root segment
  • dashboard segment

Layout and Page Behavior

  • js|jsx|ts|tsx 파일 확장자는 Pages와 Layouts으로 사용될 수 있다.
  • Page Component는 page.js의 default export 이다.
  • Layout Component는 layout.js의 default export이다 .
  • Layout Component는 반드시 children prop을 받아야한다.

Layout component가 렌더링될 때, children prop은 child layout이다 page로 채워진다.

상위 layout이 page에 도달할 때까지 가장 가까운 child layout을 선택하는 layout tree로 시각화하는 것이 더 쉬울 수 있다.

Basic Example

// Root layout (app/layout.js)
// - Applies to all routes
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  )
}

// Regular layout (app/dashboard/layout.js)
// - Applies to route segments in app/dashboard/*
export default function DashboardLayout({ children }) {
  return (
    <>
      <DashboardSidebar />
      {children}
    </>
  )
}

// Page Component (app/dashboard/analytics/page.js)
// - The UI for the `app/dashboard/analytics` segment
// - Matches the `acme.com/dashboard/analytics` URL path
export default function AnalyticsPage() {
  return (
    <main>...</main>
  )
}

위의 layout과 page 조합은 다음 컴포넌트 계층 구조로 렌더링한다.

<RootLayout>
  <Header />
  <DashboardLayout>
    <DashboardSidebar />
    <AnalyticsPage>
      <main>...</main>
    </AnalyticsPage>
  </DashboardLayout>
  <Footer />
</RootLayout>

React Server Components

이 RFC를 사용하면, React 기능을 사용하여 시작할 수 있고, Next.js 어플리케이션에 React Server Component를 점진적으로 채택할 수 있다.

새로운 라우팅 시스템 내부는 Streaming, Suspense, Transition과 같이 최근에 출시된 React 기능을 활용한다. 이 것은 React Server Component의 빌딩 블록이다.

Server Components are the new default

pageapp 디렉토리 사이의 가장 큰 변경 사항 중 하나는 기본적으로 app 내부의 파일이 서버에서 React Server Component로 렌더링 된다는 것이다.

이렇게 하면 page에서 app으로 마이그레이션할 때 React Server Component를 자동으로 채택할 수 있다.

Rendering Environments and Component Types

💡 React는 새로운 component(module) 타입을 소개했다: Server, Client, and Shared Components

새로운 React 컨벤션을 사용하여 클라이언트 사이드 javascript 번들에 포함될 컴포넌트를 세부적으로 제어할 수 있다. Client Component와 Server Component를 정의하기 위한 규칙이 정확히 무엇인지에 대해 지속적으로 논의하고 있다.

현재, app이 route의 컴포넌트(layouts과 pages)를 server, client 또는 둘 다 렌더링 할 수 있도록 허용한다는 점은 주목할만하다.

이것은 기본적으로 page는 data fetching 요구 사항이 없는 한 정적으로 생성되는 Next.js의 pages 디렉토리와 다르다. pages에서는 Next.js의 data fetching 함수(getStaticProps, getServerSideProps)나 client-side에서 데이터를 가져와서 페이지가 언제(빌드 타임 or 런타임), 어디서(server-side, client-side, combination) 렌더링하는지 유연하게 결정할 수 있다.

그러나 app 폴더에서는 렌더링 환경이 data fetching 함수와 분리되고, 컴포넌트 레벨에서 설정된다.

Interleaving Client and Server Components using the children prop

React에서는 Server Component에 서버 전용 코드가 있을 수 있으므로 Client Component 내에 Server Component를 가져오는데 제한이 있다.

예를 들어, Server Component 가져오기가 작동하지 않는다.

import ServerComponent from './ServerComponent.js';

export default function ClientComponent() {
  return (
    <>
      <ServerComponent />
    </>
  );
}

그러나, Server Component는 Client Component의 child로 전달될 수 있다. 다른 Server Component에 래핑하여 이를 동작하게 할 수 있다.

// ClientComponent.js
export default function ClientComponent({ children }) {
  return (
    <>
      <h1>Client Component</h1>
      {children}
    </>
  );
}

// ServerComponent.js
export default function ServerComponent() {
  return (
    <>
      <h1>Server Component</h1>
    </>
  );
}

// page.js
// It's possible to import Client and Server components inside Server Components
// because this component is rendered on the server
import ClientComponent from "./ClientComponent.js";
import ServerComponent from "./ServerComponent.js";

export default function ServerComponentPage() {
  return (
    <>
      <ClientComponent>
        <ServerComponent />
      </ClientComponent>
    </>
  );
}

이런 패턴을 사용하면, React는 결과(서버 전용 코드가 포함되지 않은)를 client에 전달하기 전에 서버에서 Server Component를 렌더링해야 함을 알게 된다. Client Component의 관점에서 child는 이미 렌더링 되어있다.

layout에서는 이 패턴이 children prop에 적용된다. 그래서 추가적인 wrapper component를 생성하지 않아도 된다.

예를 들어, ClientLayout 컴포넌트는 child로 Server Page 컴포넌트를 수용한다.

// The Dashboard Layout is a Client Component
// app/dashboard/layout.js
export default function ClientLayout({ children }) {
  // Can use useState / useEffect here
  return (
    <>
      <h1>Layout</h1>
      {children}
    </>
  );
}

// The Page is a Server Component that will be passed to Dashboard Layout
// app/dashboard/settings/page.js
export default function ServerPage() {
  return (
    <>
      <h1>Page</h1>
    </>
  );
}

Data fetching

layout.js 파일 내에서 Next.js의 data fetching 함수를 사용할 수 있다. data fetching method가 페이지별로 제한되는 pages 디렉토리와는 다르다.

layout은 중첩되기 때문에, route의 여러 세그먼트에서 데이터를 가져올 수도 있다.

Data fetching in Layouts

Next.js의 data fetching 함수인 getStaticPropsgetServerSideProps를 사용해서 layout.js에서 데이터를 가져올 수 있다.

예를 들어, blog layout은 sidebar 컴포넌트를 채우는데 사용될 수 있는 카테고리들을 CMS로부터 가져오는데 getStaticProps를 사용할 수 있다.

// app/blog/layout.js
export async function getStaticProps() {
  const categories = await getCategoriesFromCMS();

  return {
    props: { categories },
  };
}

export default function BlogLayout({ categories, children }) {
  return (
    <>
      <BlogSidebar categories={categories} />
      {children}
    </>
  );
}

Multiple data fetching methods in a route

route의 여러 세그먼트에서 데이터를 가져올 수도 있다. 예를 들어, 데이터를 가져오는 layout은 자체 데이터를 가져오는 page를 래핑할 수도 있다.

위의 blog 예시에 이어서, 하나의 post 페이지는 CMS로부터 post 데이터를 가져오는데 getStaticPropsgetStaticPaths를 사용할 수 있다.

// app/blog/[slug]/page.js
export async function getStaticPaths() {
  const posts = await getPostSlugsFromCMS();

  return {
    paths: posts.map((post) => ({
      params: { slug: post.slug },
    })),
  };
}

export async function getStaticProps({ params }) {
  const post = await getPostFromCMS(params.slug);

  return {
    props: { post },
  };
}

export default function BlogPostPage({ post }) {
  return <Post post={post} />;
}

app/blog/layout.jsapp/blog/[slug]/page.js는 모두 getStaticProps를 사용하기 때문에, Next.js는 build 할 때, React Server Component로 전체 /blog/[slug] 경로를 정적으로 생성하므로, 클라이언트 측 javascript가 줄어들고, hydration 속도가 빨라진다.

정적으로 생성된 경로는 클라이언트 탐색이 캐시(Server Component data)를 재사용하고, 작업을 다시 계산하지 않고, Server Component의 스냅샷을 렌더링하기 때문에 CPU 시간이 줄어들어 개선된다.

Behavior and priority

Next.js의 Data Fetching Method(getServerSidePropsgetStaticProps)는 app 폴더 내 Server Component에서 사용될 수 있다. 단일 경로의 세그먼트에 있는 다른 data fetching methods는 서로 영향을 준다.

한 세그먼트 내에서 getServerSideProps 를 사용하면, 다른 세그먼트의 getStaticProps에 영향을 준다. getServerSideProps 세그먼트에 대한 request가 이미 서버로 전송되었기 때문에, 서버는 모든 getStaticProps 세그먼트도 렌더링한다. 빌드 시 가져온 props를 재사용하므로 데이터는 여전히 정적이고, 렌더링은 next build 중에 생성된 props를 사용하여 모든 요청에 대해 on-demand 방식으로 발생한다.

한 세그먼트에서 revalidate(ISR) 방식으로 getStaticProps를 사용하면 다른 세그먼트에서 revalidate를 사용하는 getStaticProps에 영향을 준다. 한 경로에 두 개의 revalidate period가 있는 경우 더 짧은 revalidate를 우선으로 실행한다.

Data fetching and rendering with React Server Components

Server-Side Routing, React Server Component, Suspense, Streaming의 조합은 Next.js의 data fetching, rendering에 몇 가지 영향을 준다.

Parallel fetching

Next.js는 폭포수를 최소화하기 위해 병렬로 data fetching을 시작한다. 예를 들어, data fetching이 순차적이면, route의 각 중첩된 세그먼트는 이전 세그먼트가 완성되기 전까지 data fetching을 시작할 수 없다. 그러나 parallel fetching을 사용하면 각 세그먼트는 같은 시간에 data fetching을 시작한다.

rendering은 Context에 의존하기 때문에, 각 세그먼트에 대한 렌더링은 데이터가 fetch 되고, parent의 렌더링이 끝났을 때, 시작된다.

미래에는 Suspense를 사용하여 데이터가 완전히 로드되지 않은 경우에도 렌더링을 즉시 시작할 수도 있다. 데이터를 사용 가능하기 전에 읽히면 Suspense가 실행된다. React는 요청이 완료되기 전에 Server Component 렌더링을 시작하고, request의 완료되면, 결과를 넣는다.

Partial fetching and rendering

형제 route 세그먼트간 이동할 때, Next.js는 해당 세그먼트의 아래 세그먼트만 가져오고 렌더링한다. 위 세그먼트는 다시 가져오고, 렌더링할 필요가 없다. 이는 page는 layout을 공유한다는 것을 의미하고, 사용자가 형제 page간 이동할 때 layout은 유지되고, Next.js는 아래 세그먼트만 가져오고 렌더링한다.

이는 특히 React Server Component에 대해 유용하다. 그렇지 않으면, 각 페이지 이동 시, 페이지의 변경된 부분만 렌더링하는 대신, 서버에서 전체 페이지가 다시 렌더링되기 때문이다. 이는 전송되는 데이터의 양과 실행 시간을 줄여서 성능을 향상시킨다.

예를 들어 사용자가 /analytics/settings 페이지간 이동한다면, React는 페이지 세그먼트를 다시 렌더링하지만 layout은 유지한다.

Conclusion

RFC의 다음 파트에서는 아래의 내용을 설명한다

  • Instant Loading States: Server-Side routing에서 렌더링과 data fetching은 navigation이 완료되기 전에 서버에서 발생한다. 따라서 어플리케이션이 응답하지 않는 느낌이 들지 않도록 로딩 UI를 표시하는 것이 중요하다. inline 및 global loading 인티케이터와 스켈레톤과 같은 즉각적인 로딩 상태에 대한 프레임워크 수준의 지원을 제안한다.
  • Offscreen Stashing with Instant Back/Forward: React는 React 트리와 그 상태를 화면에 렌더링하지 않고, 저장하는 새로운 <Offscreen /> 컴포넌트를 추가할 계획이다. 이 컴포넌트를 활용하면, 방문한 경로를 숨기고, 방문하기 전에 경로를 미리 렌더링할 수 있어야 한다. 이러한 경로로 앞뒤로 이동하는 것은 즉각적이어야 하고, 이전에 저장된 상태를 복원해야 한다.
  • Parallel Routes: 페이지에 두 개 이상의 탭 바가 있는 경우, 개념적으로 <iframe />와 유사하게 독립적으로 이동할 수 있는 두 개 이상의 병렬로 중첩된 레이아웃이 있어야 한다.
  • Intercepting Routes: 때로는 다른 페이지 내에서 경로를 가로챌 수 있는 것은 유용하다. URL은 일반적으로 UI의 다른 부분으로 연결되지만, 이 컨텍스트 내에서 방문할 때는 그렇지 않다.
  • Streaming and selective hydration: 클라이언트로 전송되는 내용을 줄이고, 작업을 더 작은 chunk로 분할하여 새로운 라우팅 패러다임을 활성화하고, 성능을 향상시키기 위해 Server centric routing, React Server Component, Suspense, Streaming가 결합되는 방법에 대해 자세한 내용을 공유한다.
반응형

댓글