TanStack Router는 Tanner Linsley가 개발한 타입스크립트 기반의 현대적인 라우팅 라이브러리입니다. 이전에는 React Location이라는 이름으로 알려졌으며, 현재는 React, Solid, Vue, Svelte 등 다양한 프레임워크를 지원합니다. 타입 안전성과 코드 기반 라우팅을 중심으로 설계되어 개발자에게 더 나은 개발 경험을 제공합니다.
TanStack Router는 전체 라우팅 시스템에서 완벽한 타입 안전성을 제공합니다. 라우트 매개변수, 검색 매개변수, 해시 등에 대한 타입이 자동으로 유추되어 타입 오류를 방지합니다.
// 모든 라우트 매개변수가 타입 체크됩니다
const userRoute = new Route({
path: '/users/$userId',
component: ({ params }) => {
// params.userId가 자동으로 string 타입으로 추론됩니다
return <div>사용자 ID: {params.userId}</div>
}
})
Next.js나 Remix와 달리 파일 시스템 기반 라우팅이 아닌 코드 기반 라우팅을 사용합니다. 이를 통해 더 명확한 제어와 유연성을 제공합니다.
라우트 정의로부터 자동으로 맵핑된 라우트 유틸리티를 생성하여 타입 안전한 네비게이션을 가능하게 합니다.
복잡한 UI를 위한 중첩 라우트를 지원하여 레이아웃 공유와 컴포넌트 재사용을 용이하게 합니다.
라우트별 데이터 로딩 및 변경 기능을 제공하여 컴포넌트와 데이터 페칭 로직을 효과적으로 결합할 수 있습니다.
npm install @tanstack/react-router
# 또는
yarn add @tanstack/react-router
# 또는
pnpm add @tanstack/react-router
import {
Outlet,
Router,
Route,
RootRoute,
RouterProvider
} from '@tanstack/react-router'
// 루트 라우트 정의
const rootRoute = new RootRoute({
component: () => (
<div>
<nav>
<a href="/">홈</a>
<a href="/about">소개</a>
</nav>
<hr />
<Outlet /> {/* 중첩 라우트가 렌더링될 위치 */}
</div>
)
})
// 인덱스 라우트 정의
const indexRoute = new Route({
getParentRoute: () => rootRoute,
path: '/',
component: () => <div>홈 페이지</div>
})
// 추가 라우트 정의
const aboutRoute = new Route({
getParentRoute: () => rootRoute,
path: '/about',
component: () => <div>소개 페이지</div>
})
// 라우트 트리 정의
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])
// 라우터 생성
const router = new Router({ routeTree })
// 타입 선언 (선택사항이지만 권장)
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
// 앱에 라우터 제공
function App() {
return <RouterProvider router={router} />
}
// 매개변수를 사용한 동적 라우트
const userRoute = new Route({
getParentRoute: () => rootRoute,
path: '/users/$userId',
component: ({ params }) => <div>사용자 ID: {params.userId}</div>
})
// 부모 레이아웃 라우트
const usersLayoutRoute = new Route({
getParentRoute: () => rootRoute,
path: '/users',
component: () => (
<div>
<h2>사용자 관리</h2>
<Outlet /> {/* 중첩된 자식 라우트가 렌더링될 위치 */}
</div>
)
})
// 자식 라우트
const usersIndexRoute = new Route({
getParentRoute: () => usersLayoutRoute,
path: '/',
component: () => <div>사용자 목록</div>
})
const userDetailRoute = new Route({
getParentRoute: () => usersLayoutRoute,
path: '$userId',
component: ({ params }) => <div>사용자 상세: {params.userId}</div>
})
// 라우트 트리에 추가
const routeTree = rootRoute.addChildren([
indexRoute,
aboutRoute,
usersLayoutRoute.addChildren([
usersIndexRoute,
userDetailRoute
])
])
const postsRoute = new Route({
getParentRoute: () => rootRoute,
path: '/posts',
loader: async () => {
// 비동기 데이터 로딩
const posts = await fetch('/api/posts').then(r => r.json())
return { posts }
},
component: () => {
// 로드된 데이터 사용
const { posts } = useLoaderData()
return (
<div>
<h2>게시물 목록</h2>
{posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
})
import { Link, useNavigate } from '@tanstack/react-router'
// 링크 컴포넌트 사용
function Navigation() {
return (
<nav>
<Link to="/">홈</Link>
<Link to="/about">소개</Link>
<Link to="/users/123" params={{ userId: '123' }}>사용자 상세</Link>
</nav>
)
}
// 프로그래밍 방식의 네비게이션
function ActionButton() {
const navigate = useNavigate()
return (
<button onClick={() => navigate({ to: '/users/123' })}>
사용자 페이지로 이동
</button>
)
}
SPA(Single Page Application) 개발 시 가장 흔하게 겪는 문제 중 하나는 배포 환경에서 특정 페이지를 직접 방문하거나 새로고침할 때 404(Not Found) 오류가 발생하는 것입니다. 이 문제는 TanStack Router를 사용할 때도 마찬가지로 발생할 수 있습니다.
이 문제가 발생하는 근본적인 이유는 클라이언트 사이드 라우팅과 서버 사이드 라우팅의 불일치 때문입니다
SPA의 작동 방식: TanStack Router는 클라이언트 사이드 라우팅을 사용합니다. 즉, 브라우저가 JavaScript를 실행하여 URL을 해석하고 적절한 컴포넌트를 렌더링합니다.
서버의 작동 방식: 사용자가 /users/123과 같은 URL을 직접 입력하거나 새로고침을 하면, 브라우저는 해당 경로에 대한 파일을 서버에 요청합니다.
불일치 발생: 서버는 /users/123에 해당하는 실제 파일이나 디렉토리를 찾을 수 없으므로 404 오류를 반환합니다. 실제로 서버에는 index.html 파일만 존재하고, 다른 라우트들은 클라이언트 측에서 JavaScript에 의해 처리되기 때문입니다.
이 문제를 해결하는 핵심 원리는 모든 요청을 index.html로 리다이렉트하는 것입니다. 이렇게 하면 서버는 항상 메인 HTML 파일을 제공하고, 클라이언트 측 라우터가 현재 URL을 기반으로 적절한 컴포넌트를 렌더링할 수 있습니다.
/users/123과 같은 URL을 방문합니다.index.html을 제공합니다.index.html을 로드하고 JavaScript를 실행합니다./users/123)을 확인합니다.이 방식은 서버가 모든 URL 요청을 index.html로 리다이렉트함으로써 클라이언트 측 라우팅과 서버 측 라우팅 간의 불일치를 해결합니다.
Netlify에서는 두 가지 방법으로 설정할 수 있습니다
_redirects 파일 사용프로젝트의 public 폴더(빌드 후 배포 폴더)에 _redirects 파일을 생성하고 다음 내용을 추가합니다:
/* /index.html 200
이 설정은 모든 경로(/*)에 대한 요청을 /index.html로 리다이렉트하면서 상태 코드 200(성공)을 반환하도록 지시합니다.
netlify.toml 파일 사용프로젝트 루트에 netlify.toml 파일을 생성하고 다음 내용을 추가합니다:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Vercel에서는 vercel.json 파일을 프로젝트 루트에 생성합니다
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}
firebase.json 파일에 다음 설정을 추가합니다
{
"hosting": {
"public": "build",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
S3 정적 웹사이트 호스팅을 사용하는 경우, CloudFront 배포를 설정하고 오류 페이지를 구성해야 합니다
/index.html로 리다이렉트하고 응답 코드를 200으로 설정자체 서버에서 Nginx를 사용하는 경우
server {
listen 80;
server_name yourapp.com;
root /path/to/your/app;
location / {
try_files $uri $uri/ /index.html;
}
}
Apache 서버를 사용하는 경우 .htaccess 파일을 다음과 같이 설정합니다
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
TanStack Router는 타입 안전성과 코드 기반 라우팅을 중심으로 설계된 강력한 라우팅 라이브러리입니다. 하지만 대부분의 SPA와 마찬가지로 배포 시 새로고침 문제가 발생할 수 있습니다. 이 문제는 서버 설정을 통해 모든 요청을 index.html로 리다이렉트함으로써 해결할 수 있습니다.
각 호스팅 서비스마다 필요한 설정이 다르지만, 원리는 동일합니다: 클라이언트 측 라우터가 URL을 처리할 수 있도록 서버가 항상 메인 HTML 파일을 제공해야 합니다. 이 설정을 적용하면 사용자가 어떤 URL을 방문하더라도 애플리케이션이 정상적으로 로드되고 작동하게 됩니다.
TanStack Router를 활용하여 타입 안전하고 유연한 라우팅 시스템을 구축하세요. 그리고 배포 시 필요한 서버 설정도 잊지 마세요!