본문으로 건너뛰기

라우팅 개념

TanStack Router는 복잡하고 동적인 라우팅 시스템을 쉽게 구축할 수 있도록 여러 강력한 라우팅 개념을 지원합니다.

각 개념은 유용하고 강력하며, 다음 섹션에서 하나씩 자세히 살펴보겠습니다.

라우트 구조​

루트 라우트를 제외한 모든 라우트는 createFileRoute 함수로 구성합니다. 이 함수는 파일 기반 라우팅을 사용할 때 타입 안전성을 제공합니다.

<!-- ::start:framework -->

React

src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({
component: PostsComponent,
})

Solid

src/routes/index.tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/')({
component: PostsComponent,
})

<!-- ::end:framework -->

createFileRoute 함수는 파일 라우트의 경로를 문자열로 나타낸 단일 인수를 받습니다.

❓❓❓ "잠깐, 라우트 파일의 경로를 createFileRoute에 전달해야 하나요?"

맞습니다! 하지만 걱정하지 않아도 됩니다. 이 경로는 TanStack Router Bundler Plugin 또는 Router CLI를 통해 라우터가 자동으로 작성하고 관리합니다. 따라서 새 라우트를 만들거나 라우트를 이동하거나 이름을 바꾸면 경로도 자동으로 업데이트됩니다.

이 pathname이 필요한 이유는 TanStack Router의 강력한 타입 안전성과 관련이 있습니다. 이 pathname이 없으면 TypeScript는 현재 어떤 파일에 있는지 알 수 없습니다! (TypeScript에 이를 위한 내장 기능이 있으면 좋겠지만 아직 없습니다 🤷‍♂️)

루트 라우트​

루트 라우트는 전체 트리의 최상위 라우트이며 다른 모든 라우트를 자식으로 포함합니다.

  • 경로가 없습니다.
  • 항상 일치합니다.
  • component가 항상 렌더링됩니다.

루트 라우트에는 경로가 없지만 다음을 포함해 다른 라우트와 동일한 모든 기능에 접근할 수 있습니다.

  • 컴포넌트
  • 로더
  • 검색 매개변수 검증
  • 기타

루트 라우트를 만들려면 createRootRoute() 함수를 호출하고 라우트 파일에서 이를 Route 변수로 export합니다.

<!-- ::start:framework -->

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>()

<!-- ::end:framework -->

TanStack Router의 Context에 관한 자세한 내용은 라우터 Context 가이드를 참고합니다.

기본 라우트​

기본 라우트는 특정 경로와 일치합니다. 예를 들어 /about, /settings, /settings/notifications는 경로와 정확히 일치하므로 모두 기본 라우트입니다.

/about 라우트를 살펴보겠습니다.

<!-- ::start:framework -->

React

src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/about')({
component: AboutComponent,
})

function AboutComponent() {
return <div>About</div>
}

Solid

src/routes/about.tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/about')({
component: AboutComponent,
})

function AboutComponent() {
return <div>About</div>
}

<!-- ::end:framework -->

기본 라우트는 단순하고 명확합니다. 경로와 정확히 일치하며 제공된 컴포넌트를 렌더링합니다.

인덱스 라우트​

인덱스 라우트는 부모 라우트가 정확히 일치하고 일치하는 자식 라우트가 없을 때 부모 라우트를 대상으로 합니다.

/posts URL의 인덱스 라우트를 살펴보겠습니다.

<!-- ::start:framework -->

React

src/routes/posts.index.tsx
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

src/routes/posts.index.tsx
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>
}

<!-- ::end:framework -->

URL이 정확히 /posts일 때 이 라우트가 일치합니다.

동적 라우트 세그먼트​

레이블이 뒤따르는 $로 시작하는 라우트 경로 세그먼트는 동적이며 URL의 해당 부분을 params 객체에 캡처해 애플리케이션에서 사용할 수 있게 합니다. 예를 들어 pathname이 /posts/123이면 /posts/$postId 라우트와 일치하고 params 객체는 { postId: '123' }가 됩니다.

이러한 params는 라우트 구성과 컴포넌트에서 사용할 수 있습니다. posts.$postId.tsx 라우트를 살펴보겠습니다.

<!-- ::start:framework -->

React

src/routes/posts/$postId.tsx
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

src/routes/posts.tsx
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>
}

<!-- ::end:framework -->

🧠 동적 세그먼트는 경로의 각 세그먼트에서 작동합니다. 예를 들어 /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} 구문을 사용하며 특정 매개변수를 선택 사항으로 만드는 유연한 라우팅 패턴을 제공합니다.

<!-- ::start:framework -->

React

src/routes/posts.{-$category}.tsx
// 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

src/routes/posts.{-$category}.tsx
// 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>
}

<!-- ::end:framework -->

이 라우트는 /posts(category는 undefined)와 /posts/tech(category는 "tech") 모두와 일치합니다.

하나의 라우트에 여러 선택적 매개변수를 정의할 수도 있습니다.

<!-- ::start:framework -->

React

src/routes/posts.{-$category}.${-$slug}.tsx
// 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

src/routes/posts.{-$category}.${-$slug}.tsx
// 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,
})

<!-- ::end:framework -->

이 라우트는 /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를 감싸는 레이아웃 라우트입니다.

이 트리 구조를 사용해 자식 라우트를 레이아웃 컴포넌트로 감쌉니다.

<!-- ::start:framework -->

React

src/routes/app.tsx
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

src/routes/app.tsx
import { Outlet, createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/app')({
component: AppLayoutComponent,
})

function AppLayoutComponent() {
return (
<div>
<h1>App Layout</h1>
<Outlet />
</div>
)
}

<!-- ::end:framework -->

다음 표는 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 라우트는 경로 없는 레이아웃 컴포넌트로 자식 라우트를 감싸는 데 사용합니다.

<!-- ::start:framework -->

React

src/routes/_pathlessLayout.tsx
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

src/routes/_pathlessLayout.tsx
import { Outlet, createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/_pathlessLayout')({
component: PathlessLayoutComponent,
})

function PathlessLayoutComponent() {
return (
<div>
<h1>Pathless layout</h1>
<Outlet />
</div>
)
}

<!-- ::end:framework -->

다음 표는 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할 수 있습니다.

<!-- ::start:framework -->

React

src/routes/posts.tsx
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

src/routes/posts.tsx
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>
)
}

<!-- ::end:framework -->

제외된 파일은 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 디렉터리는 순전히 구성을 위한 것이며 라우트 트리나 컴포넌트 트리에 어떤 영향도 주지 않습니다.