
다른 라우터처럼 TanStack Router는 중첩된 라우트 트리를 사용하여 URL과 렌더링할 올바른 컴포넌트 트리를 매칭한다.
TanStack Router는 라우트 트리를 구축하기 위해 두 가지 방식을 지원한다.
두 방법 모두 동일한 기능을 지원하지만 파일 기반 라우팅은 더 적은 코드로 더 나은 결과를 구현할 수 있어 권장되는 방법이다.
중첩 라우팅은 URL을 사용해 중첩된 컴포넌트 트리를 렌더링할 수 있게 해주는 강력한 개념이다.
예를 들어, /blog/posts/123 라는 URL이 주어졌을 때 다음과 같은 계층 구조를 매칭할 수 있다.
/blogposts$postId그리고 다음과 같은 컴포넌트 트리를 렌더링할 수 있다.
<Blog>
<Posts>
<Post postId="123" />
</Posts>
</Blog>
중첩 라우팅을 구성하기 위해 TanStack Router는 라우트 트리라는 라우트 계층 구조를 사용하여 경로를 조직하고 매칭하며, 매칭된 경로를 컴포넌트 트리로 구성한다.
중첩 라우팅 구조는 복잡한 애플리케이션에서 URL 구조와 컴포넌트 트리를 명확하게 유지하는 데 매우 유용하며, 개발자는 URL의 변화에 따라 자연스럽게 컴포넌트 트리를 업데이트 할 수 있다.
기본적으로 라우트 경로는 대부분의 웹이 작동하는 방식과 동일하게 대소문자를 구분하지 않는다. 예를 들어 about.tsx와 ABOUT.tsx는 기본적으로 동일 경로로 간주된다.
대소문자를 구분하여 경로를 매칭하고 싶은 경우 라우트의 caseSensitive 옵션을 true로 설정할 수 있다.
import { createRoute } from '@tanstack/react-router'
// 기본적으로 대소문자 구분 없음
export const aboutRoute = createRoute({
path: 'about',
component: About,
})
// 대소문자 구분 설정
export const specialRoute = createRoute({
path: 'SpecialPath',
component: SpecialComponent,
caseSensitive: true,
})
출처 : Route Trees & Nesting | TanStack Router React Docs
루트 라우트는 모든 라우팅 트리의 최상위 라우트로 다른 모든 라우트를 자식으로 챕슐화한다.
No Path
Always Matched
Always Rendered
경로가 없지만(No Path) 다른 라우트와 동일한 기능에 접근할 수 있다, → components, loader, search param validation … etc.
루트 라우트를 생성하기 위해서 createRootRoute() 생성자를 호출하고, 라우트 파일에서 Route 변수로 내보낸다.
import { createRootRoute } from '@tanstack/react-router'
export const Route = createRootRoute()
또한 createRootRouteWithContext<TContext>() 함수를 통해 type-safe 한 방법으로 전체 라우터에 대한 의존성 주입을 할 수 있다.
💡 라우트의 구조
루트 라우트를 제외한 모든 라우트는FileRoute클래스를 사용하여 구성된다.
FileRoute클래스는 파일 기반 라우팅을 사용할 때 타입 안정성을 제공하는Route클래스의 wraooer이다.import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts')({ component: PostsComponent, })
createFileRoute함수는 파일 라우트의 경로를 문자열로 지정하는 하나의 인자를 받는다.이 경로는 TanStack Router 플러그인 또는 Router CLI를 통해 자동으로 작성되고 관리된다. 따라서 새로운 라우트를 생성하거나 라우트를 이동하거나, 이름을 변경할 때 경로는 자동으로 업데이트 된다.
정적 라우트는 특정 경로와 정확히 일치하는 라우트로 /about, /settings, /settings/profile, /settings/notifications 경로는 모두 정적 라우트이다. 정적 라우트는 경로와 정확히 일치하여 제공된 컴포넌트를 렌더링한다.
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutComponent,
})
function AboutComponent() {
return <div>About</div>
}
인덱스 라우트는 부모 라우트가 정확히 일치하고 자식 라우트가 일치하지 않을 때 타겟이 된다.
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/')({
component: PostsIndexComponent,
})
function PostsIndexComponent() {
return <div>Please select a post!</div>
}
위 코드에서 posts.index.tsx 파일은 posts 디렉토리 아래에 위치하여 URL이 /posts와 정확히 일치할 때 매칭된다. 이 경우 PostsIndexComponent가 렌더링된다.
경로 세그먼트가 $로 시작하고 레이블이 뒤따르는 경우, 동적 경로 세그먼트가 되어 URL의 해당 부분을 애플리케이션에서 사용할 수 있도록 params객체에 캡쳐한다. 예를 들어 /posts/123 경로는 /posts/$postId라우트와 매칭되며 param 객체는 { postId: '123' }이 된다.
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
// 로더에서 사용
loader: ({ params }) => fetchPost(params.postId),
// Or 컴포넌트에서 사용
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
return <div>Post ID: {postId}</div>
}
splat 라우트는 경로가 $로만 이루어진 라우트로, URL 경로의 남은 부분을 모두 캡쳐하여 params 객체의 _splat 속성에 저장한다. 예를 들어, 경로 트리에 files/$ 스플랫 라우트가 있으면, URL 경로가 /files/documents/hello-world일 때 params 객체는 다음과 같은 값을 갖는다.
{
'_splat': 'documents/hello-world'
}
경로가 _로 시작하는 파일 라우트는 "경로 없음(Pathless)" 또는 "레이아웃(Layout)" 라우트로 간주된다. 이러한 라우트는 URL에 매칭되지 않지만, 자식 라우트를 추가적인 컴포넌트 및 로직으로 감쌀 때 사용된다.
💡 사용 예시
• 자식 라우트를 레이아웃 컴포넌트로 감싸기
• 자식 라우트를 표시하기 전에 로더 요구 사항 적용
• 자식 라우트에 검색 매개변수 검증 및 제공
• 자식 라우트에 오류 컴포넌트 또는 대기 요소 제공
• 모든 자식 라우트에 공유 컨텍스트 제공
_layout 경로는 URL 경로에 직접 매칭되지 않지만, 자식 경로를 감싸서 공통 레이아웃을 제공하는 데 사용된다. 예를 들어, URL이 /layout-a일 때, 실제로 매칭되는 경로는 /layout-a가 아니라 /layout-a를 감싸는 _layout이다.
import { Outlet, createFileRoute } from '@tanstack/react-router'
// '_layout' 경로를 정의한다. 이 경로는 자식 경로를 감싸는 레이아웃 역할을 한다.
export const Route = createFileRoute('/_layout')({
component: LayoutComponent,
})
// LayoutComponent는 자식 경로를 감싸는 레이아웃 컴포넌트이다.
function LayoutComponent() {
return (
<div>
<h1>Layout</h1>
{/* Outlet은 자식 컴포넌트를 렌더링하는 자리 표시자 역할을 한다. */}
<Outlet />
</div>
)
}
위의 설정에 따르면, URL이 /layout-a일 때 _layout 경로가 자식 경로 layout-a를 감싸게 된다. 결과적으로 렌더링되는 컴포넌트 트리는 다음과 같다.
<Layout>
<LayoutA />
</Layout>
Non-nested routes는 부모 파일 라우트 세그먼트에 _를 접미사로 붙여서 생성할 수 있다. 이는 특정 라우트가 부모 라우트의 경로를 벗어나 완전히 다른 컴포넌트 트리를 렌더링 할 필요가 있을 때 유용하다.
경로 매칭 중에 _는 무시되므로 /posts와 /posts_는 동일한 경로로 간주된다. 하지만, 컴포넌트 트리를 구성할 때 _는 중첩되지 않은(non-nested) 라우트를 나타내므로 /posts와 /posts_는 다른 라우트로 간주된다
/posts와/posts_는 동일한 경로이지만, 다른 라우트로 간주된다.
즉, 경로는 똑같이 /posts/… 로 나타나지만 사실상 다른 라우트 트리를 가지고 있는 것이라고 볼 수 있다.
NotFoundRoutes는 특정 조건에서 렌더링되는 특별한 유형의 라우트로 명시적인 경로 트리의 일부는 아니지만, 경로 매칭이 실패했을 때 유용하게 사용 가능하다.
• 가능한 경로 매칭을 초과하는 URL 경로 세그먼트가 있을 때
• 초과된 경로 세그먼트를 캡처할 동작 세그먼트나 스플랫 라우트가 없을 때
• 부모 라우트가 매칭될 때 렌더링할 인덱스 라우트가 없을 떄
• 라우터에 notFountRoute가 제공된 경우
component, pendingComponent, errorComponents를 렌더링아래 예시는 /posts 및 /about 경로 외의 모든 경로에 대해 NotFoundComponent를 렌더링한다.
//main.tsx
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';
import './index.css';
import NotFound from './shared/NotFound';
const router = createRouter({ routeTree, defaultNotFoundComponent: NotFound });
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}
const rootElement = document.getElementById('root')!;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);
}
//NotFound.tsx
export default function NotFound(): JSX.Element {
return (
<div className='bg-[red]'>
<h1 className='text-white'>⚠️404 - Not Found😿⚠️</h1>
</div>
);
}
라우트 트리에
NotFoundeRoute를 포함하지 않는다.
라우터 생성 시defaultNotFoundComponent옵션에 컴포넌트를 제공하여, 모든 경로에서 작동하도록 설정한다.