경로에 대한 고유 UI입니다. page.tsx에서 만듭니다.
// app/blog/dashboard/page.tsx -> URL 'blog/dashboard'에 UI를 제공해줍니다.
const Page = () => <div>블로그</div>
export default Page
// app/page.tsx -> / URL '/'에 UI를 제공해줍니다.
const Page = () => <div>홈</div>
페이지에 대해 다음 사실을 알면 도움이 됩니다.
- 페이지는 모든 라우터 서브트리에 리프노드입니다.
- 페이지는 기본적으로 서버 컴포넌트로 구성되지만 클라이언트 컴포넌트로도 사용이 가능합니다.
- 페이지는 데이터 패치도 가능합니다. 이 부분은 뒤에서 더 자세히 다룹니다.
레이아웃은 여러 페이지에서 공유하는 UI입니다. 탐색하는 시점에도 레이아웃은 상태를 보존하고 인터랙티브하며 리랜더링이 일어나지 않습니다. 레이아웃 또한 중첩이 가능합니다.
레이아웃은 layout.tsx로 정의가 가능합니다. children을 props로 받을 수 있고 렌더링하는 동안 하위 레이아웃이나 하위 페이지로 채워집니다.
const DashboardLayout = ({
children, // will be a page or nested layout
}: {
children: React.ReactNode
}) => {
return (
<section>
<nav>
<ul style={{display: 'flex'}}>
<li style={{marginRight: '5px'}}>대쉬보드</li>
<li style={{marginRight: '5px'}}>친구찾기</li>
<li>설정</li>
</ul>
</nav>
{children}
</section>
)
}
export default DashboardLayout
페이지는 알아서 children에 들어갑니다.
레이아웃에 대해 아래 사실을 알면 도움이 됩니다.
- 모든 라우터 세그먼트는 옵셔널하게 layout을 정의할 수 있습니다.
- 라우터 그룹을 이용해 세그먼트 내부와 외부 모두 레이아웃을 공유할 수 있게 만들 수 있습니다.
- 레이아웃도 데이터 패칭이 가능합니다.
- 레이아웃도 디폴트는 서버 컴포넌트이고 클라이언트 컴포넌트로도 사용이 가능합니다.
- 부모 레이아웃과 자식 레이아웃 간의 데이터 전달은 불가능합니다. 하지만 경로에서 동일한 데이터를 두 번 이상 가져올 수 있고 React 성능에 영향을 주지 않고 자동으로 중복 요청은 제거합니다.
- 레이아웃은 현재 라우터 세그먼트에 접근할 수 없습니다. 라우터 세그먼트에 접속하기 위해서는 클라이언트 컴포넌트에서만 사용 가능한 함수(
useSelectedLayoutSegment
,useSelectedLayoutSegments
)들을 사용해야 합니다.
루트 레이아웃은 앱 디렉토리 최상단에 정의하고 모든 라우터에 적용이 가능합니다. 이 레이아웃은 서버에서 반환된 내부 HTML을 수정해서 반환할 수 있습니다.
import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}
루트 레이아웃에 대해 아래 사실을 알면 도움이 됩니다.
- app 디렉토리는 반드시 루트 레이아웃을 포함해야 합니다.
- 루트 레이아웃은 반드시 html과 body 태그를 포함해야 합니다.
- head 태그에 SEO 지원과 관련한 여러 정보를 넣을 수 있습니다.
- 라우터 그룹을 사용하면 다수의 루트 레이아웃을 만들 수 있습니다. 추후 다룹니다.
- 루트 레이아웃도 기본적으로 서버 컴포넌트이지만 클라이언트 컴포넌트로도 사용할 수 있습니다.
기본적으로 레이아웃은 파일 계층으로 중첩이 가능하고 children으로 들어갑니다.
// app/blog/layout.tsx
const DashboardLayout = ({
children, // will be a page or nested layout
}: {
children: React.ReactNode
}) => {
return (
<section>
<div style={{background: 'orange', width: '500px', height: '700px'}}>
{children}
</div>
</section>
)
}
export default DashboardLayout
// app/blog/dashboard/layout.tsx
const DashboardLayout = ({
children, // will be a page or nested layout
}: {
children: React.ReactNode
}) => {
return (
<section>
<nav>
<ul style={{display: 'flex'}}>
<li style={{marginRight: '5px'}}>대쉬보드</li>
<li style={{marginRight: '5px'}}>친구찾기</li>
<li>설정</li>
</ul>
</nav>
{children}
</section>
)
}
export default DashboardLayout
이렇게 부모 layout의 children으로 자식 layout이 들어가는 것을 알 수 있습니다.
템플릿도 레이아웃과 비슷하게 각각의 자식 레이아웃이나 페이지를 감싸는 용도로 사용됩니다. 템플릿은 레이아웃과 다르게 탐색할 때마다 자식 요소마다 새로운 인스턴스를 생성합니다. 이는 템플릿을 공유하는 라우터 간에 이동할 대 컴포넌트의 새로운 인스턴스가 마운트 되고 DOM 엘리먼트가 새로 생기고 상태는 보존되지 않고 효과가 재동기화 된다는 것을 말합니다.
템플릿을 레이아웃 대신 써야하는 조건은 다음과 같습니다.
- useEffect와 useState에 종속된 경우
- 프레임워크의 기본 동작을 바꿔야 하는 경우(ex. 각 페이지마다 탐색 시, 대체 콘텐츠를 다르게 표현하고 싶을 때)
권장 사항은 템플릿을 사용하지 않는 것입니다.
템플릿도 app 디렉토리 어딘가에 template.tsx
로 정의하면 사용할 수 있습니다.
const Template = ({
children,
}: {
children: React.ReactNode
}) => {
return (
<div>
{children}
</div>
)
}
export default Template
레이아웃과 템플릿이 공존할 때 결과물은 다음과 같습니다.
<Layout>
{/* Note that the template is given a unique key. */}
<Template key={routeParam}>{children}</Template>
</Layout>
SEO를 담당하는 부분입니다. 타이틀이나 메타를 사용합니다.
메타데이터는 metadata 객체나 generateMetaData 함수를 이용해 layout이나 page에 넣어줍니다.
import { Metadata } from 'next'
export const metadata: Metadata = {
title: '갱이❤️우기',
}
// Page에 title 갱이❤️우기가 심긴다.
export default function Page() {
return '...'
}
헤드 수정하기에서 알면 좋은 것들은 다음과 같습니다.
루트 레이아웃에 head나 title, meta를 직접 추가하면 안됩니다. 대신 MetadataAPI를 사용하여 스트리밍 및 head 요소 중복 제거와 같은 고급 요구 사항을 자동으로 처리합시다.