[Next.js 13] Pages and Layouts

김태성·2023년 11월 29일
0

Next.js 13 - 레이아웃(layout.js)란?

Next.js 13부터 도입된 layout.js 파일은 여러 페이지(page.js) 들에 공통적으로 적용되는 UI를 정의하는 컴포넌트이다.
layout.js는 하위의 page.js 및 layout.js 를 자식(children)으로 감싸서 화면을 렌더링 한다.
따라서 여러 레이아웃을 만들어 두고, 부모-자식 레이아웃 구조로 중첩하여 적용하는 방식도 가능하다.

또한 레이아웃(Layout)에 정의된 UI와 상태 값들은, (경로 이동-Navigation 등이 발생하더라도) 계속해서 유지되며 재렌더링 되지 않고 재활용된다.

layout.js(레이아웃) 정의 방법

layout.js 를 정의하는 방법은 다음과 같다.

/app 디렉터리, 혹은 그 내부의 중첩된 폴더에 layout.js 파일을 생성한다.
layout.js 파일 내부에 React 컴포넌트 함수를 정의하고, Prop으로 children을 받도록 한다.
정의한 컴포넌트 함수를 export default 처리 한다.

예를 들어, /app/dashboard/layout.js 파일은 아래와 같이 정의할 수 있다.

export default function DashboardLayout({
children, // will be a page or nested layout
}: {
children: React.ReactNode;
}) {
return (

<section>
  {/* Include shared UI here e.g. a header or sidebar */}
  <nav></nav>

  {children}
</section>

);
}

이렇게 정의한 layout.js 컴포넌트는 children 프롭을 통해 page.js 컴포넌트나 자식 layout.js 를 내부에 렌더링 한다.

루트 레이아웃(Root Layout)

루트 레이아웃은 /app 디렉토리에 정의된 최상위 layout.js 파일로, 모든 라우트 경로에 적용된다.
즉, /app 경로 및 하위의 중첩 폴더에 정의된 모든 page.js 및 layout.js 컴포넌트들은 루트레이아웃의 자식(children)이 되어 렌더링된다.

루트 레이아웃은 가장 상위의 껍데기(?)로서 html 과 body 태그를 포함하며,
개발자가 정의한 다양한 리액트 컴포넌트 들을 자식(children)으로 렌더링 한다.

/app/layout.tsx 파일(루트 레이아웃):
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

단, 루트 레이아웃은 다음 사항을 반드시 준수해야 한다.

/app 디렉토리는 반드시 루트 레이아웃(layout.js)을 포함해야 한다.

다른 중첩 폴더에 layout.js가 있더라도, 반드시 /app 최상위 레벨에 루트 layout.js 파일이 존재해야 한다.
필요에 따라 (Route Group)으로 구분하여 정의할 수도 있다.

<html><body> 태그를 포함해야 한다.

오직 루트 레이아웃만 이 태그들을 포함할 수 있다.
다른 하위의 레이아웃은 이 태그들을 포함할 수 없다.

반드시 Server Component여야 한다.

루트 레이아웃은 Client Component로 전환해선 안 된다.
루트 레이아웃을 제외한 하위의 다른 레이아웃들은 Client Component로 전환할 수 있다.

중첩 레이아웃(Nesting Layout)

각각의 접속 경로 폴더 (Route Segment)마다 별도의 layout.js 파일을 정의할 수 있다.

이렇게 정의한 layout.js는 해당 경로(폴더)에 정의된 page.js를 감싸는 레이아웃이 되며,
해당 경로(Route Segment)로 접속할 때만 적용된다.

예를 들어, app/dashboard/layout.tsx 레이아웃은 http://domain.com/dashboard 경로로 접속할 때만 적용된다.

레이아웃은 상위에서 하위로 계속하여 중첩 적용된다.
즉, 상위 레이아웃은 chidren 프롭을 통해 하위 레이아웃을 내부에 렌더링하는 한다. 

예를 들어, app/dashboard/layout.tsx 파일을 정의하면,

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

dashboard 레이아웃 (app/dashboard/layout.tsx)는 상위의 루트 레이아웃(app/layout.tsx)에 의해 감싸지며,
결과적으로 아래와 같은 구조로 렌더링 된다.

layout.js 컴포넌트의 파라미터

layout.js 컴포넌트는 2개의 파라미터를 Props으로 받는다.

children (required)

레이아웃이 감싸서 렌더링하는 자식 컴포넌트(React.ReactNode 타입)가 들어온다.
대표적으로 "중첩 layout.js", "자식 page.js", "loading.js", "error.js" 등이 들어올 수 있다.

params (optional)

page.js와 동일하게, 동적 라우팅(Daynamic Route)의 접속 경로가 들어온다.
단, 루트 레이아웃(Root Layout)은 params 프롭을 받지 않는다. 오직 중첩 레이아웃들만 이 프랍을 받는다.

참고로, layout.js 는 page.js와 다르게 쿼리 스트링(searchParam)을 파라미터로 받지 않는다.
레이아웃은 재렌더링(Re-rendering)되지 않기 때문에, 경로 이동에 따라 쿼리 스트링이 바뀌어도 새로 갱신되지 않기 때문이다.
따라서 page.js 에서 searchParams 프랍을 사용하거나, Client Component에서 useSearchParams 훅을 사용해야 한다.

layout.js 요약 정리

루트 레이아웃(Root Layout)

루트 레이아웃(/app/layout.js)은 반드시 존재해야 한다. 루트 레이아웃은 Nextjs 웹 애플리케이션의 모든 page.js에 적용된다. 루트 레이아웃은 반드시 , ` 태그를 포함해야 한다.
루트 레이아웃은 반드시 Server Component여야 하며, Client Component로 전환 불가하다.

중첩 레이아웃(Nesting Layout)

모든 라우트 경로(route segment)에 별도의 레이아웃(layout.js)를 정의할 수 있으며,  해당 경로의 page.js에만 적용된다.
레이아웃은 기본적으로 중첩되어 적용된다. 따라서 상위 경로의 레이아웃은 하위 경로의 레이아웃을 감싼다.

서버/클라이언트 컴포넌트(Server/Client Component)

Layout은 디폴트로 서버 컴포넌트이다.

따라서, asycn 컴포넌트 함수로 정의하여, 직접 await fetch() 호출이 가능하다.
단, 부모-자식 레이아웃 간에는 데이터 전달이 불가하다.
하지만, 부모 레이아웃, 자식 레이아웃에서 동일한 fetch() 각각 호출해도 성능에 전혀 영향이 없다.
Request Memoization 를 통해 동일한 경로의 fetch() 호출이 있으면 자동으로 중복 제거(Dedupe)처리 해주기 때문이다.

Layout은 파일 최상단에 "use client"를 표기하여 Client Component로 전환할 수 있다.

단 루트 레이아웃(Root Layout)은 반드시 Server Component 여야만 한다.

SEO(검색 최적화) 지원

HTML <head> 태그의 title, description 도 개발자가 컨트롤할 수 있다.
단, 직접 루트 레이아웃에서 head 태그를 추가하고,title, meta 태그를 정의하지 말자.
Next.js에서 제공하는 MetaData API를 이용하면 된다. layout.js 및 page.js 파일에 서 사용 가능하다.

metadata 객체 : 변하지 않는 정적 메타 데이터(Static meta data)를 정의할 때 사용한다.
generateMetadata() 함수: 서버 등에서 매번 불러와야 하는 하는 동적 메타 데이터(Dynamic meta data)를 정의할 때 사용한다.

라우트 그룹(Route Groups)

/app 내부에서 폴더명을 (그룹명)으로 정의하면, 라우트 그룹화가 가능하다.
라우트 그룹마다 별도의 레이아웃을 적용할 수도 있다.

접속 경로(Route Segment)

/app 내부에서 폴더명을 /[id] 나 /[category] 등으로 정의하면, 해당 위치의 경로는 다양한 주소로 접속 가능한 동적 라우트(Dynaimc Route)가 된다.
실제로 브라우저에서 접속한 경로명 (id나 category 등)은 param 프롭으로 받을 수 있다.
단, 루트 레이아웃이 아닌 중첩 레이아웃에서만 받을 수 있다.
루트 레이아웃은 param 프랍을 통해서는 현재 접속 경로(route segment)를 파악할 수 없으므로, Client Component의 useSelectedLayoutSegment나 useSelectedLayoutSegments 훅을 이용해야 한다.

Search Params(쿼리 스트링)

Layout은 page.js와 다르게 쿼리 스트링을 searchParams 프랍으로 받지 못한다.
page.js의 searchParams 프랍이나, Client Component의 useSearchParms 훅을 이용해야 한다.

이 글은 아래의 Next.js 공식 문서 내용을 참고하여 정리한 글이다.
- Pages and Layout Docs

  • layout.js API Reference

참고로 Next.js 13은 나온지 얼마 안 되서 그런지,
공식문서의 내용이 수시로 바뀌고, 사라졌다 생겼다를 반복하고 있다.(2023.8 기준)

중요한 부분은 직접 문서를 확인하고 적용할 필요성이 있을듯 하다.

reference: https://curryyou.tistory.com/549 [카레유:티스토리]

profile
@flip_404

0개의 댓글