본문으로 건너뛰기

React Router v7에서 마이그레이션하는 방법

이 가이드에서는 애플리케이션을 React Router v7에서 TanStack Router로 마이그레이션하는 단계별 과정을 설명합니다. React Router 의존성 제거부터 TanStack Router의 타입 안전 라우팅 패턴 구현까지 전체 마이그레이션 과정을 다룹니다.

빠른 시작

예상 소요 시간: 앱 복잡도에 따라 2~4시간
난이도: 중급
사전 요구 사항: React 기본 지식 및 기존 React Router v7 앱

완료할 작업

  • React Router v7 의존성과 컴포넌트 제거
  • TanStack Router 설치 및 구성
  • 라우트 정의를 파일 기반 라우팅으로 변환
  • 탐색 컴포넌트와 훅 업데이트
  • 타입 안전 라우팅 패턴 구현
  • 검색 매개변수와 동적 라우트 처리
  • React Router v7의 새 기능을 TanStack Router의 대응 기능으로 마이그레이션

전체 마이그레이션 과정

1단계: 마이그레이션 준비

변경하기 전에 환경과 코드베이스를 준비합니다.

1.1 백업 브랜치 생성

git checkout -b migrate-to-tanstack-router
git push -u origin migrate-to-tanstack-router

1.2 TanStack Router 설치(React Router는 임시로 유지)

# Install TanStack Router
npm install @tanstack/react-router

# Install development dependencies
npm install -D @tanstack/router-plugin @tanstack/react-router-devtools

1.3 번들러에 라우터 플러그인 설정

Vite 사용자는 vite.config.ts를 업데이트합니다.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
plugins: [
tanstackRouter(), // Add this before react plugin
react(),
],
})

다른 번들러번들러 구성 가이드를 참고하세요.

2단계: TanStack Router 구성 생성

2.1 라우터 구성 파일 생성

프로젝트 루트에 tsr.config.json을 생성합니다.

{
"routesDirectory": "./src/routes",
"generatedRouteTree": "./src/routeTree.gen.ts",
"quoteStyle": "single"
}

2.2 routes 디렉터리 생성

mkdir src/routes

3단계: React Router v7 구조 변환

3.1 현재 React Router v7 설정 확인

React Router v7에는 몇 가지 새로운 패턴이 도입되었습니다. 다음 항목을 확인합니다.

  • 새로운 데이터 API와 함께 사용하는 createBrowserRouter
  • 프레임워크 모드 구성
  • 서버 측 렌더링 설정
  • 새로운 loaderaction 함수
  • defer 사용(v7에서 간소화됨)
  • 타입 안전 라우팅 기능

3.2 루트 라우트 생성

src/routes/__root.tsx를 생성합니다.

import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'

export const Route = createRootRoute({
component: () => (
<>
{/* Your existing layout/navbar content */}
<div className="p-2 flex gap-2">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</div>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
),
})

3.3 인덱스 라우트 생성

홈 페이지용으로 src/routes/index.tsx를 생성합니다.

import { createFileRoute } from '@tanstack/react-router'

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

function Index() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
)
}

3.4 React Router v7 로더 변환

React Router v7에서는 로더 패턴이 간소화되었습니다. 다음과 같이 마이그레이션합니다.

React Router v7:

// app/routes/posts.tsx
export async function loader() {
const posts = await fetchPosts()
return { posts } // v7 removed need for json() wrapper
}

export default function Posts() {
const { posts } = useLoaderData()
return <div>{/* render posts */}</div>
}

TanStack Router 대응 코드: src/routes/posts.tsx를 생성합니다.

import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts')({
loader: async () => {
const posts = await fetchPosts()
return { posts }
},
component: Posts,
})

function Posts() {
const { posts } = Route.useLoaderData()
return <div>{/* render posts */}</div>
}

3.5 동적 라우트 변환

React Router v7:

// app/routes/posts.$postId.tsx
export async function loader({ params }) {
const post = await fetchPost(params.postId)
return { post }
}

export default function Post() {
const { post } = useLoaderData()
return <div>{post.title}</div>
}

TanStack Router 대응 코드: src/routes/posts/$postId.tsx를 생성합니다.

import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post }
},
component: Post,
})

function Post() {
const { post } = Route.useLoaderData()
const { postId } = Route.useParams()
return <div>{post.title}</div>
}

3.6 React Router v7 액션 변환

React Router v7:

export async function action({ request, params }) {
const formData = await request.formData()
const result = await updatePost(params.postId, formData)
return { success: true }
}

TanStack Router 대응 코드:

export const Route = createFileRoute('/posts/$postId/edit')({
component: EditPost,
// Actions are typically handled differently in TanStack Router
// Use mutations or form libraries like React Hook Form
})

function EditPost() {
const navigate = useNavigate()

const handleSubmit = async (formData) => {
const result = await updatePost(params.postId, formData)
navigate({ to: '/posts/$postId', params: { postId } })
}

return <form onSubmit={handleSubmit}>{/* form */}</form>
}

4단계: React Router v7 프레임워크 기능 처리

4.1 서버 측 렌더링 마이그레이션

React Router v7에는 SSR을 지원하는 프레임워크 모드가 도입되었습니다. 이를 사용한다면 다음과 같이 합니다.

React Router v7 Framework Mode:

// react-router.config.ts
export default {
ssr: true,
prerender: ['/'],
}

TanStack Router 방식:

TanStack Router에는 SSR 기능이 내장되어 있습니다. SSR을 사용할 수 있도록 라우터를 설정합니다.

// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

const router = createRouter({
routeTree,
context: {
// Add any SSR context here
},
})

declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}

export { router }

서버 측 렌더링에는 TanStack Router의 내장 SSR API를 사용합니다.

// server.tsx
import { createMemoryHistory } from '@tanstack/react-router'
import { StartServer } from '@tanstack/start/server'

export async function render(url: string) {
const router = createRouter({
routeTree,
history: createMemoryHistory({ initialEntries: [url] }),
})

await router.load()

return (
<StartServer router={router} />
)
}

4.2 코드 분할 마이그레이션

React Router v7에서는 코드 분할이 개선되었습니다. TanStack Router에서는 지연 라우트를 통해 처리합니다.

React Router v7:

const LazyComponent = lazy(() => import('./LazyComponent'))

TanStack Router:

import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/lazy-route')({
component: LazyComponent,
})

function LazyComponent() {
return <div>Lazy loaded!</div>
}

5단계: 탐색 컴포넌트 업데이트

5.1 Link 컴포넌트 업데이트

React Router v7:

import { Link } from 'react-router'

<Link to="/posts/123">View Post</Link>
<Link to="/posts" state={{ from: 'home' }}>Posts</Link>

TanStack Router:

import { Link } from '@tanstack/react-router'

<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
<Link to="/posts" state={{ from: 'home' }}>Posts</Link>

5.2 탐색 훅 업데이트

React Router v7:

import { useNavigate } from 'react-router'

function Component() {
const navigate = useNavigate()

const handleClick = () => {
navigate('/posts/123')
}
}

TanStack Router:

import { useNavigate } from '@tanstack/react-router'

function Component() {
const navigate = useNavigate()

const handleClick = () => {
navigate({ to: '/posts/$postId', params: { postId: '123' } })
}
}

6단계: React Router v7 전용 기능 처리

6.1 간소화된 defer 사용 방식 마이그레이션

React Router v7에서는 래퍼 함수를 제거해 defer 사용을 간소화했습니다.

React Router v7:

export async function loader() {
return {
data: fetchData(), // Promise directly returned
}
}

TanStack Router:

TanStack Router는 지연 데이터에 다른 방식을 사용합니다. 로딩 상태를 사용합니다.

export const Route = createFileRoute('/deferred')({
loader: async () => {
const data = await fetchData()
return { data }
},
pendingComponent: () => <div>Loading...</div>,
component: DeferredComponent,
})

6.2 React Router v7의 향상된 타입 안전성 처리

React Router v7에서는 타입 추론이 개선되었습니다. TanStack Router는 더 나은 타입 안전성을 제공합니다.

// TanStack Router automatically infers types
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
// params.postId is automatically typed as string
const post = await fetchPost(params.postId)
return { post }
},
component: Post,
})

function Post() {
// post is automatically typed based on loader return
const { post } = Route.useLoaderData()
// postId is automatically typed as string
const { postId } = Route.useParams()
}

7단계: 기본 라우터 설정 업데이트

7.1 React Router v7 라우터 생성 방식 교체

변경 전 (React Router v7):

import { createBrowserRouter, RouterProvider } from 'react-router'

const router = createBrowserRouter([
// Your route definitions
])

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)

변경 후 (TanStack Router):

import { RouterProvider } from '@tanstack/react-router'
import { router } from './router'

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)

8단계: 검색 매개변수 처리

8.1 React Router v7의 검색 매개변수를 TanStack Router 방식으로 변환

React Router v7:

import { useSearchParams } from 'react-router'

function Component() {
const [searchParams, setSearchParams] = useSearchParams()
const page = searchParams.get('page') || '1'

const updatePage = (newPage) => {
setSearchParams({ page: newPage })
}
}

TanStack Router:

import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const searchSchema = z.object({
page: z.number().catch(1),
filter: z.string().optional(),
})

export const Route = createFileRoute('/posts')({
validateSearch: searchSchema,
component: Posts,
})

function Posts() {
const navigate = useNavigate({ from: '/posts' })
const { page, filter } = Route.useSearch()

const updatePage = (newPage: number) => {
navigate({ search: (prev) => ({ ...prev, page: newPage }) })
}
}

9단계: React Router 의존성 제거

TanStack Router에서 모든 기능이 작동하는 것을 확인한 후에 진행합니다.

9.1 React Router v7 제거

npm uninstall react-router

9.2 사용하지 않는 import 정리

코드베이스에 남아 있는 React Router import를 검색합니다.

# Find remaining React Router imports
grep -r "react-router" src/

남은 import를 모두 제거하고 TanStack Router 대응 코드로 교체합니다.

10단계: 고급 타입 안전성 추가

10.1 엄격한 TypeScript 구성

tsconfig.json을 업데이트합니다.

{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}

10.2 검색 매개변수 검증 추가

검색 매개변수가 있는 라우트에는 검증 스키마를 추가합니다.

import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const postsSearchSchema = z.object({
page: z.number().min(1).catch(1),
search: z.string().optional(),
category: z.enum(['tech', 'business', 'lifestyle']).optional(),
})

export const Route = createFileRoute('/posts')({
validateSearch: postsSearchSchema,
component: Posts,
})

프로덕션 체크리스트

마이그레이션한 애플리케이션을 배포하기 전에 다음을 확인합니다.

라우터 구성

  • 라우터 인스턴스를 생성하고 올바르게 내보냈는지
  • 라우트 트리가 성공적으로 생성되었는지
  • TypeScript 선언을 등록했는지
  • 모든 라우트 파일이 명명 규칙을 따르는지

라우트 마이그레이션

  • 모든 React Router v7 라우트를 파일 기반 라우팅으로 변환했는지
  • 동적 라우트를 올바른 매개변수 구문으로 업데이트했는지
  • 중첩 라우트의 계층 구조가 유지되는지
  • 필요한 곳에 인덱스 라우트를 생성했는지
  • 레이아웃 라우트의 컴포넌트 구조가 유지되는지

기능 마이그레이션

  • 모든 React Router v7 로더를 변환했는지
  • 액션을 적절한 패턴으로 마이그레이션했는지
  • 서버 측 렌더링을 구성했는지(해당하는 경우)
  • 코드 분할을 구현했는지
  • 타입 안전성을 향상했는지
  • 모든 Link 컴포넌트를 TanStack Router 방식으로 업데이트했는지
  • useNavigate 훅을 교체하고 테스트했는지
  • 탐색 매개변수가 올바르게 타입 지정되었는지
  • 검색 매개변수 검증을 구현했는지

코드 정리

  • React Router v7 의존성을 제거했는지
  • 사용하지 않는 import를 정리했는지
  • React Router 참조가 남아 있지 않은지
  • TypeScript 컴파일이 성공하는지
  • 모든 테스트가 통과하는지

테스트

  • 모든 라우트에 액세스할 수 있고 올바르게 렌더링되는지
  • 라우트 간 탐색이 작동하는지
  • 브라우저 뒤로/앞으로 버튼이 작동하는지
  • 검색 매개변수가 올바르게 유지되는지
  • 매개변수가 있는 동적 라우트가 작동하는지
  • 중첩 라우트 레이아웃이 올바르게 표시되는지
  • 프레임워크 기능(SSR, 코드 분할)이 해당하는 경우 작동하는지

일반적인 문제

오류: "Cannot use useNavigate outside of context"

문제: TanStack Router와 충돌하는 React Router import가 남아 있습니다.

해결 방법:

  1. 모든 React Router import를 검색합니다.
    grep -r "react-router" src/
  2. 모든 import를 TanStack Router 대응 코드로 교체합니다.
  3. React Router가 완전히 제거되었는지 확인합니다.

TypeScript 오류: 라우트 매개변수

문제: TypeScript가 라우트 매개변수가 올바르게 타입 지정되지 않았다는 오류를 표시합니다.

해결 방법:

  1. TypeScript 모듈 선언에 라우터가 등록되었는지 확인합니다.
    declare module '@tanstack/react-router' {
    interface Register {
    router: typeof router
    }
    }
  2. 라우트 파일이 Route를 올바르게 내보내는지 확인합니다.
  3. 라우트 정의와 사용 부분의 매개변수 이름이 일치하는지 확인합니다.

React Router v7 프레임워크 기능이 작동하지 않음

문제: 마이그레이션 후 SSR 또는 코드 분할 기능이 없습니다.

해결 방법:

  1. TanStack Router에는 SSR 기능이 내장되어 있으므로 풀스택 애플리케이션에는 TanStack Start를 사용합니다.
  2. 코드 분할에는 TanStack Router의 지연 라우트를 사용합니다.
  3. TanStack Router의 기본 API를 사용해 SSR을 구성합니다.
  4. 자세한 지침은 SSR 설정 가이드를 따릅니다.

라우트가 일치하지 않음

문제: 라우트가 렌더링되지 않거나 유효한 라우트에서 404 오류가 발생합니다.

해결 방법:

  1. 파일 이름이 TanStack Router 규칙을 따르는지 확인합니다.
    • 동적 라우트: $paramName.tsx
    • 인덱스 라우트: index.tsx
    • 중첩 라우트: 올바른 디렉터리 구조
  2. 라우트 트리 생성이 작동하는지 확인합니다.
  3. 라우터 플러그인이 올바르게 구성되었는지 확인합니다.

React Router v7 간소화 API를 변환할 수 없음

문제: v7의 간소화된 defer 또는 다른 기능에 직접 대응하는 기능이 없습니다.

해결 방법:

  1. 로딩 UX에는 TanStack Router의 대기 상태를 사용합니다.
  2. TanStack Router 아키텍처에 맞는 데이터 가져오기 패턴을 구현합니다.
  3. 더 나은 DX를 위해 TanStack Router의 우수한 타입 안전성을 활용합니다.

React Router v7과 TanStack Router 기능 비교

기능React Router v7TanStack Router
타입 안전성양호뛰어남
파일 기반 라우팅프레임워크 모드만내장
검색 매개변수기본스키마로 검증
코드 분할양호지연 라우트로 뛰어남
SSR프레임워크 모드TanStack Start에 내장
번들 크기작음
학습 곡선보통보통
커뮤니티성장 중

일반적인 다음 단계

TanStack Router로 성공적으로 마이그레이션한 후 다음 개선 사항을 고려해 보세요.

살펴볼 고급 기능

  • 라우트 기반 코드 분할 - 지연 로딩으로 성능 향상
  • 검색 매개변수 검증 - 타입 안전 URL 상태 관리
  • 라우트 프리로딩 - 체감 성능 향상
  • 라우트 마스킹 - 고급 URL 관리
  • TanStack Query 통합 - 강력한 데이터 가져오기