라우팅 기초
용어
- Tree: 계층구조를 보여주는 컨벤션이다. 예를 들어, 부모와 자식 컴포넌트가 있는 컴포넌트 트리, 폴더 구조 등이 있다.
- Subtree: 트리의 일부이다.
- Root: 루트 레이아웃과 같은 트리나 서브트리의 가장 첫번째 노드이다.
- Leaf: URL 경로의 마지막 부분 같은 자식이 없는 서브트리의 노드이다.
- URL Segment: 슬래시(/)로 구분지어지는 URL 경로의 부분이다.
- URL Path: 도메인 뒤에 오는 URL의 부분이다.
폴더와 파일의 역할
- 폴더는 라우트를 정의하는데 사용된다.
- 파일은 라우트 세그먼트에 보여지는 UI를 만드는데 사용된다.
파일 컨벤션
Next.js는 중첩 라우트에서 특정 행동을 하는 UI를 만들기 위한 특별한 파일 세트를 제공한다.
- layout: 세그먼트와 자식들을 위해 공유되는 UI이다.
- page: 한 라우트에 해당하는 독립적인 UI이고, 해당 라우트에 접근할 수 있도록 해준다.
- loading: 세그먼트와 자식들을 위한 로딩 UI이다.
- not-found: 세그먼트와 자식들을 위한 Not found UI이다.
- error: 세그먼트와 자식들을 위한 에러 UI이다.
- global-error: 전역 에러 UI이다.
- route: 서버사이트 API 엔드포인트이다.
- template: 특정한 리렌더된 레이아웃 UI이다.
- default: 병렬 라우트를 위한 대체 UI이다.
컴포넌트 계층 구조
중첩 라우트에선, 세그먼트의 컴포넌트는 부모 세그먼트의 컴포넌트 안에 중첩된다.
라우트 정의하기
라우트 만들기
각 폴더는 URL 세그먼트에 매핑되는 라우트 세그먼트를 나타낸다. 중첩 라우트를 만들기 위해선, 폴더를 서로 중첩하면 된다.
위 사진에서 analytics는 page.js가 없기 때문에 접근할 수 없다.
페이지와 레이아웃
페이지
페이지는 라우트에 유일한 UI이다. page.js 파일에서 컴포넌트를 내보냄으로써 페이지를 정의할 수 있다.
알면 좋은것들:
- 페이지는 항상 라우트 서브트리의 리프이다.
- 페이지들은 기본적으로 서버 컴포넌트이지만, 클라이언트 컴포넌트로 세팅할 수 있다.
- 페이지들은 데이터를 받아올 수 있다.
레이아웃
레이아웃은 여러 페이지들 사이에 공유되는 UI이다. 탐색시에, 레이아웃은 상태를 유지하고, 대화형을 유지하고, 리렌더링 되지 않는다. 레이아웃은 중첩될 수도 있다.
layout.js 파일에서 리액트 컴포넌트를 default 로 내보냄으로써 레이아웃을 정의할 수 있다. 컴포넌트는 렌더링 중에 자식 레이아웃이나 자식 페이지로 채워지는 children prop을 받아야 한다.
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>
)
}
알면 좋은것들:
- 최상단 레이아웃은 루트 레이아웃이라고 불린다. 이 필수 레이아웃은 어플리케이션의 모든 페이지에 공유된다. 루트 레이아웃은 반드시 html과 body태그를 포함해야 한다.
- 어떤 라우트 세그먼트는 본인만의 레이아웃을 정의할 수도 있다. 이 레이아웃들은 그 세그먼트내의 모든 페이지에 공유된다.
- 라우트 안의 레이아웃들은 기본적으로 중첩된다. 각 부모 레이아웃은 children prop을 사용해서 자식 레이아웃을 래핑한다.
- 레이아웃들은 기본적으로 서커 컴포넌트이지만, 클라이언트 컴포넌트로 세팅할 수 있다.
- 레이아웃들은 데이터를 받아올 수 있다.
- 부모 레이아웃과 자식 레이아웃 사이에 데이터를 전달하는 것은 불가능하다. 하지만, 라우트 안에서 동일한 데이터를 한번이상 가져올 수 있고, 리액트는 성능에 영향을 끼치지 않고 자동으로 요청의 중복을 제거한다.
- 레이아웃들은 그 밑의 라우트 세그먼트에 접근하지 않는다. 모든 라우트 세그먼트에 접근하기 위해서는, useSelectedLayoutSegment를 사용하거나 클라이언트 컴포넌트 안에서 useSelectedLayoutSegments를 사용해야 한다.
- layout.js와 page.js는 같은 폴더에 정의할 수 있다. 레이아웃이 페이지를 감싼다.
루트 레이아웃(필수)
루트 레이아웃은 app 폴더의 최상위에 정의되고, 모든 라우트에 접근할 수 있다. 이 레이아웃은 서버로부터 반환되는 초기 HTML을 수정할 수 있도록 해준다.
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
알면 좋은것들:
- app 폴더는 반드시 루트 레이아웃을 포함해야 한다.
- 루트 레이아웃은 기본적으로 서버 컴포넌트이고 클라이언트 컴포넌트로 세팅할 수 없다.
중첩 레이아웃
폴더 내부에 정의된 레이아웃은 특정 라우트 세그먼트에 적용되고 해당 라우트가 활성화됨녀 렌더링된다. 기본적으로 파일 계층 구조의 레이아웃은 중첩되어 있다. 즉, children props를 통해 하위 레이아웃을 감싼다.
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
알면 좋은것들:
- 오직 루트 레이아웃만 <html>과 <body> 태그를 포함할 수 있다.
템플릿
탬플릿은 각 하위 레이아웃이나 페이지를 감싼다는 점에서 레이아웃과 비슷하다. 같은 라우트에 대해서 상태를 유지하는 레이아웃과 달리, 템플릿은 각 하위 항목에 대해 새 인스턴스를 만든다. 즉, 사용자가 템플릿을 공유하는 라우트 사이를 탐색할 때 컴포넌트의 새 인스턴스가 마운트되고 DOM 요소가 다시 생성되며 상태가 유지되지 않는다.
이러한 특징은 다음 경우에 레이아웃보다 더 적합할 수 있다.
- useEffect 또는 useState에 의존하는 기능
- useEffect는 의존성 배열의 변화에 따라 호출되고, 여러 컴포넌트에서 사용되는 레이아웃은 한 인스턴스로 상태를 모두 공유하기 때문에, 컴포넌트들에게 공통적인 것이 필요하지만 독립적인 상태를 원한다면 템플릿을 사용하는 것이 맞다.
템플릿은 template.js 파일로부터 기본 리액트 컴포넌트를 내보냄으로써 정의될 수 있다. 컴포넌트는 children prop을 허용해야 한다.
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
중첩 관점에서, template.js는 레이아웃과 자식사이에 렌더링된다. 간단한 출력은 다음과 같다.
<Layout>
{/* Note that the template is given a unique key. */}
<Template key={routeParam}>{children}</Template>
</Layout>
<head> 변경하기
app 폴더에서 빌트인 SEO 관련 지원을 사용하여 title과 meta 태그 같은 <head> HTML 요소들을 변경할 수 있다.
메타데이터는 layout.js나 page.js 파일 안에서 metadata 객체나 generateMetadata 함수를 내보냄으로써 정의될 수 있다.
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}
알면 좋은것들:
- <title> 이나 <meta> 같은 <head> 태그들을 수동으로 루트 레이아웃에 추가하면 안된다. 대신에, 스트리밍과 중복제거 <head> 요소같은 고급 요구사항을 자동으로 처리하는 메타데이터 API를 사용해야 한다.
연결과 탐색
Next.js에는 라우트를 탐색하는 두가지 방법이 있다.
- <Link> 컴포넌트를 사용하기
- useRouter 훅을 사용하기
<Link> 컴포넌트
<Link>는 라우트 사이에 클라이언트-사이드로 탐색하고 prefetching을 제공하기 위해 <a> 태그를 확장한 내장 컴포넌트이다. 이것은 Next.js에서 라우트 사이를 탐색하기 위한 가장 기본적인 방법이다.
next/link를 import 함으로써 사용할 수 있고, 컴포넌트에 href prop을 전달한다.
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
<Link> 태그에 전달할 수 있는 다른 부가적인 prop들도 있다.
예시
동적 세그먼트에 연결하기
동적 세그먼트에 연결할 때, 링크 리스트를 만들기 위해서 템플릿 리터럴과 보간법을 사용할 수 있다.
블로그 포스트 리스트를 만드는 예시를 보자.
import Link from 'next/link'
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
액티브 링크 확인하기
usePathname()을 사용하여 링크가 활성화상태인지 확인할 수 있다. 예를 들어, 활성 링크에 클래스를 추가하기 위해서, 현재 pathname이 링크의 href와 맞는지 체크할 수 있다.
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
</li>
<li>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</li>
</ul>
</nav>
)
}
id로 스크롤하기
Next.js의 App 라우터의 기본 행동은 새로운 라우트의 최상단으로 스크롤하거나, 뒤나 앞으로 갈 경우 스크롤을 유지하는것이다.