라우팅 개념
TanStack Router는 복잡하고 동적인 라우팅 시스템을 쉽게 구축할 수 있도록 여러 강력한 라우팅 개념을 지원합니다.
각 개념은 유용하고 강력하며, 다음 섹션에서 하나씩 자세히 살펴보겠습니다.
라우트 구조
루트 라우트를 제외한 모든 라우트는 createFileRoute 함수로 구성합니다. 이 함수는 파일 기반 라우팅을 사용할 때 타입 안전성을 제공합니다.
React
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: PostsComponent,
})
Solid
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/')({
component: PostsComponent,
})
createFileRoute 함수는 파일 라우트의 경로를 문자열로 나타낸 단일 인수를 받습니다.
❓❓❓ "잠깐, 라우트 파일의 경로를 createFileRoute에 전달해야 하나요?"
맞습니다! 하지만 걱정하지 않아도 됩니다. 이 경로는 TanStack Router Bundler Plugin 또는 Router CLI를 통해 라우터가 자동으로 작성하고 관리합니다. 따라서 새 라우트를 만들거나 라우트를 이동하거나 이름을 바꾸면 경로도 자동으로 업데이트됩니다.
이 pathname이 필요한 이유는 TanStack Router의 강력한 타입 안전성과 관련이 있습니다. 이 pathname이 없으면 TypeScript는 현재 어떤 파일에 있는지 알 수 없습니다! (TypeScript에 이를 위한 내장 기능이 있으면 좋겠지만 아직 없습니다 🤷♂️)
루트 라우트
루트 라우트는 전체 트리의 최상위 라우트이며 다른 모든 라우트를 자식으로 포함합니다.
- 경로가 없습니다.
- 항상 일치합니다.
component가 항상 렌더링됩니다.
루트 라우트에는 경로가 없지만 다음을 포함해 다른 라우트와 동일한 모든 기능에 접근할 수 있습니다.
- 컴포넌트
- 로더
- 검색 매개변수 검증
- 기타
루트 라우트를 만들려면 createRootRoute() 함수를 호출하고 라우트 파일에서 이를 Route 변수로 export합니다.
React
// Standard root route
import { createRootRoute } from '@tanstack/react-router'
export const Route = createRootRoute()
// Root route with Context
import { createRootRouteWithContext } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
export interface MyRouterContext {
queryClient: QueryClient
}
export const Route = createRootRouteWithContext<MyRouterContext>()
Solid
// Standard root route
import { createRootRoute } from '@tanstack/solid-router'
export const Route = createRootRoute()
// Root route with Context
import { createRootRouteWithContext } from '@tanstack/solid-router'
import type { QueryClient } from '@tanstack/solid-query'
export interface MyRouterContext {
queryClient: QueryClient
}
export const Route = createRootRouteWithContext<MyRouterContext>()
TanStack Router의 Context에 관한 자세한 내용은 라우터 Context 가이드를 참고합니다.
기본 라우트
기본 라우트는 특정 경로와 일치합니다. 예를 들어 /about, /settings, /settings/notifications는 경로와 정확히 일치하므로 모두 기본 라우트입니다.
/about 라우트를 살펴보겠습니다.
React
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutComponent,
})
function AboutComponent() {
return <div>About</div>
}
Solid
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/about')({
component: AboutComponent,
})
function AboutComponent() {
return <div>About</div>
}
기본 라우트는 단순하고 명확합니다. 경로와 정확히 일치하며 제공된 컴포넌트를 렌더링합니다.
인덱스 라우트
인덱스 라우트는 부모 라우트가 정확히 일치하고 일치하는 자식 라우트가 없을 때 부모 라우트를 대상으로 합니다.
/posts URL의 인덱스 라우트를 살펴보겠습니다.
React
import { createFileRoute } from '@tanstack/react-router'
// Note the trailing slash, which is used to target index routes
export const Route = createFileRoute('/posts/')({
component: PostsIndexComponent,
})
function PostsIndexComponent() {
return <div>Please select a post!</div>
}
Solid
import { createFileRoute } from '@tanstack/solid-router'
// Note the trailing slash, which is used to target index routes
export const Route = createFileRoute('/posts/')({
component: PostsIndexComponent,
})
function PostsIndexComponent() {
return <div>Please select a post!</div>
}
URL이 정확히 /posts일 때 이 라우트가 일치합니다.
동적 라우트 세그먼트
레이블이 뒤따르는 $로 시작하는 라우트 경로 세그먼트는 동적이며 URL의 해당 부분을 params 객체에 캡처해 애플리케이션에서 사용할 수 있게 합니다. 예를 들어 pathname이 /posts/123이면 /posts/$postId 라우트와 일치하고 params 객체는 { postId: '123' }가 됩니다.
이러한 params는 라우트 구성과 컴포넌트에서 사용할 수 있습니다. posts.$postId.tsx 라우트를 살펴보겠습니다.
React
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
// In a loader
loader: ({ params }) => fetchPost(params.postId),
// Or in a component
component: PostComponent,
})
function PostComponent() {
// In a component!
const { postId } = Route.useParams()
return <div>Post ID: {postId}</div>
}
Solid
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/$postId')({
// In a loader
loader: ({ params }) => fetchPost(params.postId),
// Or in a component
component: PostComponent,
})
function PostComponent() {
// In a component!
const { postId } = Route.useParams()
return <div>Post ID: {postId()}</div>
}
🧠 동적 세그먼트는 경로의 각 세그먼트에서 작동합니다. 예를 들어
/posts/$postId/$revisionId경로의 라우트를 만들 수 있으며 각$세그먼트가params객체에 캡처됩니다.
Splat / Catch-All 라우트
경로가 $ 하나뿐인 라우트를 "splat" 라우트라고 합니다. $부터 끝까지 URL pathname의 남은 부분을 항상 모두 캡처하기 때문입니다. 캡처된 pathname은 특수한 _splat 속성을 통해 params 객체에서 사용할 수 있습니다.
예를 들어 files/$ 경로를 대상으로 하는 라우트는 splat 라우트입니다. URL pathname이 /files/documents/hello-world이면 params 객체의 특수한 _splat 속성에 documents/hello-world가 포함됩니다.
{
'_splat': 'documents/hello-world'
}
⚠️ 라우터 v1에서는 이전 버전과의 호환성을 위해 splat 라우트를
_splat키 대신*로 표시하기도 합니다. 이는 v2에서 제거됩니다.
🧠 왜
$를 사용하나요? Remix와 같은 도구를 통해 와일드카드를 나타내는 가장 일반적인 문자가*이지만 파일 이름이나 CLI 도구와 잘 호환되지 않는다는 것을 알게 되었습니다. 따라서 이러한 도구와 마찬가지로 대신$를 사용하기로 했습니다.
선택적 경로 매개변수
선택적 경로 매개변수를 사용하면 URL에 있을 수도 있고 없을 수도 있는 라우트 세그먼트를 정의할 수 있습니다. {-$paramName} 구문을 사용하며 특정 매개변수를 선택 사항으로 만드는 유연한 라우팅 패턴을 제공합니다.
React
// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech`
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/{-$category}')({
component: PostsComponent,
})
function PostsComponent() {
const { category } = Route.useParams()
return <div>{category ? `Posts in ${category}` : 'All Posts'}</div>
}
Solid
// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech`
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/{-$category}')({
component: PostsComponent,
})
function PostsComponent() {
const { category } = Route.useParams()
return <div>{category ? `Posts in ${category()}` : 'All Posts'}</div>
}
이 라우트는 /posts(category는 undefined)와 /posts/tech(category는 "tech") 모두와 일치합니다.
하나의 라우트에 여러 선택적 매개변수를 정의할 수도 있습니다.
React
// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech`
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({
component: PostsComponent,
})
Solid
// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech`
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({
component: PostsComponent,
})
이 라우트는 /posts, /posts/tech 및 /posts/tech/hello-world와 일치합니다.
🧠 선택적 매개변수가 있는 라우트는 정확히 일치하는 라우트보다 우선순위가 낮습니다. 따라서
/posts/featured와 같이 더 구체적인 라우트가/posts/{-$category}보다 먼저 일치합니다.
레이아웃 라우트
레이아웃 라우트는 자식 라우트를 추가 컴포넌트와 로직으로 감싸는 데 사용합니다. 다음과 같은 경우에 유용합니다.
- 자식 라우트를 레이아웃 컴포넌트로 감싸기
- 자식 라우트를 표시하기 전에
loader요구 사항 적용하기 - 자식 라우트에 검색 매개변수 검증 및 제공하기
- 자식 라우트에 오류 컴포넌트나 대기 요소의 대체 항목 제공하기
- 모든 자식 라우트에 공유 컨텍스트 제공하기
- 그 외에도 다양한 용도로 사용할 수 있습니다.
app.tsx라는 레이아웃 라우트 예제를 살펴보겠습니다.
routes/
├── app.tsx
├── app.dashboard.tsx
├── app.settings.tsx
위 트리에서 app.tsx는 두 자식 라우트인 app.dashboard.tsx와 app.settings.tsx를 감싸는 레이아웃 라우트입니다.
이 트리 구조를 사용해 자식 라우트를 레이아웃 컴포넌트로 감쌉니다.
React
import { Outlet, createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/app')({
component: AppLayoutComponent,
})
function AppLayoutComponent() {
return (
<div>
<h1>App Layout</h1>
<Outlet />
</div>
)
}
Solid
import { Outlet, createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/app')({
component: AppLayoutComponent,
})
function AppLayoutComponent() {
return (
<div>
<h1>App Layout</h1>
<Outlet />
</div>
)
}
다음 표는 URL에 따라 렌더링되는 컴포넌트를 보여줍니다.
| URL 경로 | 컴포넌트 |
|---|---|
/app | <AppLayout> |
/app/dashboard | <AppLayout><Dashboard> |
/app/settings | <AppLayout><Settings> |
TanStack Router는 플랫 라우트와 디렉터리 라우트의 혼합을 지원하므로 디렉터리 안의 레이아웃 라우트를 사용해 애플리케이션의 라우팅을 표현할 수도 있습니다.
routes/
├── app/
│ ├── route.tsx
│ ├── dashboard.tsx
│ ├── settings.tsx
이 중첩 트리에서 app/route.tsx 파일은 두 자식 라우트인 app/dashboard.tsx와 app/settings.tsx를 감싸는 레이아웃 라우트의 구성입니다.
레이아웃 라우트를 사용하면 동적 라우트 세그먼트에 컴포넌트 및 로더 로직을 적용할 수도 있습니다.
routes/
├── app/users/
│ ├── $userId/
| | ├── route.tsx
| | ├── index.tsx
| | ├── edit.tsx
경로 없는 레이아웃 라우트
레이아웃 라우트와 마찬가지로 경로 없는 레이아웃 라우트는 자식 라우트를 추가 컴포넌트와 로직으로 감싸는 데 사용합니다. 그러나 경로 없는 레이아웃 라우트는 URL에서 일치하는 path가 필요하지 않으며, URL에서 일치하는 path 없이 자식 라우트를 추가 컴포넌트와 로직으로 감싸는 데 사용합니다.
경로 없는 레이아웃 라우트는 "pathless"임을 나타내기 위해 밑줄(_)을 접두사로 사용합니다.
🧠
_접두사 뒤의 경로 부분은 라우트의 ID로 사용됩니다. 모든 라우트를 고유하게 식별할 수 있어야 하며, 특히 TypeScript를 사용할 때 타입 오류를 피하고 자동 완성을 제대로 구현하려면 필요합니다.
_pathlessLayout.tsx라는 라우트 예제를 살펴보겠습니다.
routes/
├── _pathlessLayout.tsx
├── _pathlessLayout.a.tsx
├── _pathlessLayout.b.tsx
위 트리에서 _pathlessLayout.tsx는 두 자식 라우트인 _pathlessLayout.a.tsx와 _pathlessLayout.b.tsx를 감싸는 경로 없는 레이아웃 라우트입니다.
_pathlessLayout.tsx 라우트는 경로 없는 레이아웃 컴포넌트로 자식 라우트를 감싸는 데 사용합니다.
React
import { Outlet, createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/_pathlessLayout')({
component: PathlessLayoutComponent,
})
function PathlessLayoutComponent() {
return (
<div>
<h1>Pathless layout</h1>
<Outlet />
</div>
)
}
Solid
import { Outlet, createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/_pathlessLayout')({
component: PathlessLayoutComponent,
})
function PathlessLayoutComponent() {
return (
<div>
<h1>Pathless layout</h1>
<Outlet />
</div>
)
}
다음 표는 URL에 따라 렌더링되는 컴포넌트를 보여줍니다.
| URL 경로 | 컴포넌트 |
|---|---|
/ | <Index> |
/a | <PathlessLayout><A> |
/b | <PathlessLayout><B> |
TanStack Router는 플랫 라우트와 디렉터리 라우트의 혼합을 지원하므로 디렉터리 안의 경로 없는 레이아웃 라우트를 사용해 애플리케이션의 라우팅을 표현할 수도 있습니다.
routes/
├── _pathlessLayout/
│ ├── route.tsx
│ ├── a.tsx
│ ├── b.tsx
그러나 레이아웃 라우트와 달리 경로 없는 레이아웃 라우트는 URL 경로 세그먼트를 기준으로 일치하지 않습니다. 따라서 이러한 라우트는 경로의 일부로 동적 라우트 세그먼트를 지원하지 않으며 URL에서 일치시킬 수 없습니다.
따라서 다음과 같이 작성할 수 없습니다.
routes/
├── _$postId/ ❌
│ ├── ...
대신 다음과 같이 작성해야 합니다.
routes/
├── $postId/
├── _postPathlessLayout/ ✅
│ ├── ...
비중첩 라우트
비중첩 라우트는 부모 파일 라우트 세그먼트에 _를 접미사로 붙여 만들 수 있으며, 라우트를 부모에서 분리하고 자체 컴포넌트 트리를 렌더링하는 데 사용합니다.
다음과 같은 플랫 라우트 트리를 살펴봅니다.
routes/
├── posts.tsx
├── posts.$postId.tsx
├── posts_.$postId.edit.tsx
다음 표는 URL에 따라 렌더링되는 컴포넌트를 보여줍니다.
| URL 경로 | 컴포넌트 |
|---|---|
/posts | <Posts> |
/posts/123 | <Posts><Post postId="123"> |
/posts/123/edit | <PostEditor postId="123"> |
posts.$postId.tsx라우트는posts.tsx라우트 아래에 일반적인 방식으로 중첩되며<Posts><Post>를 렌더링합니다.posts_.$postId.edit.tsx라우트는 다른 라우트와 동일한posts접두사를 공유하지 않으므로 최상위 라우트처럼 처리되며<PostEditor>를 렌더링합니다.
라우트에서 파일 및 폴더 제외
파일 이름에 - 접두사를 붙이면 라우트 생성에서 파일과 폴더를 제외할 수 있습니다. 이를 통해 라우트 디렉터리에 로직을 함께 배치할 수 있습니다.
다음과 같은 라우트 트리를 살펴봅니다.
routes/
├── posts.tsx
├── -posts-table.tsx // 👈🏼 ignored
├── -components/ // 👈🏼 ignored
│ ├── header.tsx // 👈🏼 ignored
│ ├── footer.tsx // 👈🏼 ignored
│ ├── ...
posts 라우트에서 제외된 파일을 import할 수 있습니다.
React
import { createFileRoute } from '@tanstack/react-router'
import { PostsTable } from './-posts-table'
import { PostsHeader } from './-components/header'
import { PostsFooter } from './-components/footer'
export const Route = createFileRoute('/posts')({
loader: () => fetchPosts(),
component: PostComponent,
})
function PostComponent() {
const posts = Route.useLoaderData()
return (
<div>
<PostsHeader />
<PostsTable posts={posts} />
<PostsFooter />
</div>
)
}
Solid
import { createFileRoute } from '@tanstack/solid-router'
import { PostsTable } from './-posts-table'
import { PostsHeader } from './-components/header'
import { PostsFooter } from './-components/footer'
export const Route = createFileRoute('/posts')({
loader: () => fetchPosts(),
component: PostComponent,
})
function PostComponent() {
const posts = Route.useLoaderData()
return (
<div>
<PostsHeader />
<PostsTable posts={posts} />
<PostsFooter />
</div>
)
}
제외된 파일은 routeTree.gen.ts에 추가되지 않습니다.
경로 없는 라우트 그룹 디렉터리
경로 없는 라우트 그룹 디렉터리는 경로와 관계없이 라우트 파일을 함께 그룹화하는 방법으로 ()를 사용합니다. 순전히 구성을 위한 것이며 라우트 트리나 컴포넌트 트리에 어떤 영향도 주지 않습니다.
routes/
├── index.tsx
├── (app)/
│ ├── dashboard.tsx
│ ├── settings.tsx
│ ├── users.tsx
├── (auth)/
│ ├── login.tsx
│ ├── register.tsx
위 예제에서 app 및 auth 디렉터리는 순전히 구성을 위한 것이며 라우트 트리나 컴포넌트 트리에 어떤 영향도 주지 않습니다. 관련 라우트를 함께 그룹화해 더 쉽게 탐색하고 구성하는 데 사용합니다.
다음 표는 URL에 따라 렌더링되는 컴포넌트를 보여줍니다.
| URL 경로 | 컴포넌트 |
|---|---|
/ | <Index> |
/dashboard | <Dashboard> |
/settings | <Settings> |
/users | <Users> |
/login | <Login> |
/register | <Register> |
보시다시피 app 및 auth 디렉터리는 순전히 구성을 위한 것이며 라우트 트리나 컴포넌트 트리에 어떤 영향도 주지 않습니다.