Next.js 13부터 도입된 layout.js 파일은 여러 페이지(page.js) 들에 공통적으로 적용되는 UI를 정의하는 컴포넌트이다.
layout.js는 하위의 page.js 및 layout.js 를 자식(children)으로 감싸서 화면을 렌더링 한다.
따라서 여러 레이아웃을 만들어 두고, 부모-자식 레이아웃 구조로 중첩하여 적용하는 방식도 가능하다.
또한 레이아웃(Layout)에 정의된 UI와 상태 값들은, (경로 이동-Navigation 등이 발생하더라도) 계속해서 유지되며 재렌더링 되지 않고 재활용된다.
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 를 내부에 렌더링 한다.
루트 레이아웃은 /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로 전환할 수 있다.
각각의 접속 경로 폴더 (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 컴포넌트는 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
훅을 사용해야 한다.
루트 레이아웃(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
참고로 Next.js 13은 나온지 얼마 안 되서 그런지,
공식문서의 내용이 수시로 바뀌고, 사라졌다 생겼다를 반복하고 있다.(2023.8 기준)
중요한 부분은 직접 문서를 확인하고 적용할 필요성이 있을듯 하다.
reference: https://curryyou.tistory.com/549 [카레유:티스토리]