프론트엔드 역량/CS 기초, 알고리즘, 자료구조, 시스템 디자인

[라이브러리] tanstack-react-router

FS29 2025. 4. 2. 14:50

❓ 리액트는 왜 파일 기반 라우팅을 지양할까?

  • 타입 안정성

명시적으로 라우트 트리를 정의하면 TypeScript와의 통합이 훨씬 수월해집니다. 각 라우트에 대해 명확한 타입 정보를 제공할 수 있어, 잘못된 경로나 컴포넌트 참조로 인한 런타임 오류를 예방할 수 있습니다.

  • 구조적 명시

파일 기반 라우팅은 폴더 및 파일 구조에 의존하지만, 명시적인 라우트 정의는 라우팅 계층 구조와 레이아웃을 한눈에 파악할 수 있도록 도와줍니다. 이를 통해 복잡한 중첩 라우팅이나 에러 처리 로직 등을 체계적으로 관리할 수 있습니다.

  • 유연한 구성

라우터를 코드 상에서 직접 관리하면 라우트 간의 복잡한 관계나 동적 라우팅, 권한 체크 등 추가 로직을 쉽게 삽입하고 유연하게 대처할 수 있습니다.

 

❓ 그럼에도 불구하고 탄스택리액트라우터를 사용하는 이유는 무엇일까?

  • 빠른 개발 및 자동화

파일 시스템의 디렉토리 구조가 라우트 경로와 직접적으로 매핑되므로, 개발자가 별도의 라우트 정의 코드를 작성하지 않아도 됩니다. 이는 개발 속도를 높이고 보일러플레이트 코드를 줄여줍니다.

  • 컨벤션 기반 설정

파일 기반 라우팅은 '관습에 의한 구성(convention over configuration)' 철학을 따릅니다. 규칙만 잘 정해지면 팀 전체가 일관된 방식으로 라우트를 관리할 수 있어 대규모 팀이나 프로젝트에서 유지보수가 용이합니다.

  • 단순한 프로젝트 구조

모든 프로젝트가 복잡한 라우팅 로직이나 중첩, 동적 라우팅 등을 필요로 하지 않습니다. 규모가 작거나 구조가 단순한 애플리케이션의 경우 파일 기반 라우팅이 오히려 빠르고 직관적일 수 있습니다.

  • 커뮤니티 및 생태계의 지원

Next.js와 같은 여러 프레임워크와 도구들이 파일 기반 라우팅을 지원합니다. 이를 통해 관련 플러그인이나 확장 기능을 쉽게 활용할 수 있으며, TypeScript 통합 등 다양한 기능도 제공합니다.


2. 본론

등장개요

tanstack-react-router는 TanStack 팀에서 개발한 React 기반 라우팅 라이브러리로, 선언적이고 타입 안전한 방식으로 라우트와 레이아웃을 구성할 수 있도록 도와줍니다.

이 라이브러리를 사용할 때 보통 두 가지 주요 파일이 등장합니다.

  • __root.tsx

애플리케이션의 최상위(root) 컴포넌트로, 라우터 설정 및 전역 레이아웃(예: 네비게이션 바, 푸터, 에러 처리 등)을 정의합니다.

  • routeTree.gen.ts

라우트 트리(route tree)를 자동으로 생성하는 파일입니다. 애플리케이션의 각 라우트(페이지)와 그 계층 구조를 선언적으로 구성한 결과물을 담고 있으며, 빌드 과정이나 CLI 도구를 통해 자동 생성됩니다.


각 파일 및 라이브러리의 의의와 역할

1. tanstack-react-router

  • 역할: React 애플리케이션 내에서 복잡한 라우팅 로직을 쉽게 관리할 수 있도록 도와줍니다.
  • 특징
    • 선언적 API와 TypeScript 지원을 통해 코드 안정성과 유지보수성을 높임.
    • 중첩 라우트, 동적 라우팅, 레이아웃 관리 등 다양한 기능 제공.

2. __root.tsx

  • 역할: 애플리케이션의 "루트" 컴포넌트로, 라우터를 감싸는 전역 레이아웃을 설정합니다.
  • 주요 내용
    • 보통 RouterProvider 컴포넌트를 사용하여 생성된 라우터 인스턴스를 하위 컴포넌트에 전달합니다.
    • 전역 상태, 에러 경계, 로딩 처리 등의 공통 요소들을 포함할 수 있습니다.
// __root.tsx
import React from 'react';
import { RouterProvider } from '@tanstack/react-router';
import { router } from './router'; // 라우터 설정 파일

function Root() {
  return <RouterProvider router={router} />;
}

export default Root;

필수조건

  • 최상위 컴포넌트로 사용되어야 하며, 올바른 라우터 인스턴스가 전달되어야 합니다.
  • 전역 레이아웃이나 에러 처리 로직을 포함할 때 하위 라우트와의 충돌에 주의해야 합니다.

3. routeTree.gen.ts

  • 역할: 라우트들의 계층 구조를 선언적으로 구성한 결과를 담은 자동 생성 파일입니다.
  • 주요 내용
    • CLI 도구나 빌드 스크립트를 통해 자동 생성되며, 라우터가 어떤 경로와 컴포넌트를 렌더링할지 결정합니다.
// routeTree.gen.ts
import { createRouteTree } from '@tanstack/react-router';

export const routeTree = createRouteTree([
  {
    path: '/',
    element: <HomePage />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: 'about',
        element: <AboutPage />,
      },
      // 추가 라우트 ...
    ],
  },
]);

필수조건

  • 각 라우트에 필요한 element와 (선택적으로) errorElement를 올바르게 지정해야 합니다.
  • 프로젝트의 라우트 정의와 일치하도록 파일 구조 및 설정이 이루어져야 합니다.

tanstack-react-router와 __root.tsx, routeTree.gen.ts 간의 관계

  • 라우트 정의와 구성
    • routeTree.gen.ts 파일에 모든 라우트(페이지)와 그 계층 구조가 선언됩니다.
    • 이를 기반으로 별도의 파일(예: router.ts)에서 createReactRouter 함수를 사용해 라우터 인스턴스를 생성합니다.
// router.ts
import { createReactRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';

export const router = createReactRouter({ routeTree });

  • 애플리케이션 최상위 구성
    • __root.tsx는 생성된 router 인스턴스를 RouterProvider에 전달하여 전역 라우팅을 활성화합니다.
  • 상호 보완적 역할
    • routeTree.gen.ts는 라우트 데이터(경로, 계층 구조)를 제공하고,
    • tanstack-react-router는 이 데이터를 기반으로 실제 라우팅 로직을 구현하며,
    • __root.tsx는 이 시스템을 애플리케이션에 통합해 사용자에게 제공하는 역할을 합니다.

3. 사용법 및 문법

패키지 설치

npm install @tanstack/react-router

자동 생성 설정

  • 프로젝트에 맞게 routeTree를 자동 생성하는 스크립트나 CLI 도구를 설정합니다. (자세한 내용은 TanStack Router 공식문서 참고)

코드 예시

  1. 라우트 트리 생성 (routeTree.gen.ts)
// routeTree.gen.ts
import { createRouteTree } from '@tanstack/react-router';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';
import ErrorPage from './pages/ErrorPage';

export const routeTree = createRouteTree([
  {
    path: '/',
    element: <HomePage />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: 'about',
        element: <AboutPage />,
      },
      // 추가 라우트들...
    ],
  },
]);

  1. 라우터 인스턴스 생성 (router.ts)
// router.ts
import { createReactRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';

export const router = createReactRouter({ routeTree });

  1. 최상위 컴포넌트 구성 (__root.tsx)
// __root.tsx
import React from 'react';
import { RouterProvider } from '@tanstack/react-router';
import { router } from './router';

function Root() {
  return (
    <RouterProvider router={router}>
      {/* 전역 레이아웃 컴포넌트나 추가 Provider 등을 여기에 추가 */}
    </RouterProvider>
  );
}

export default Root;


추가 사항

  • 라우트 정의 시 각 라우트에 element와 (필요한 경우) errorElement를 반드시 지정해야 합니다.
  • 중첩 라우트를 사용할 경우, 부모 컴포넌트 내에 <Outlet /> 또는 하위 라우트를 렌더링할 컴포넌트를 포함해야 합니다.
  • 자동 생성 도구를 사용할 경우, 설정 파일(예: tanstack-router.config.js)에서 올바른 경로와 옵션을 지정해야 합니다.
  • 주의사항
    • 필수 요소(element)가 누락되면 라우터가 올바르게 동작하지 않아 런타임 에러가 발생할 수 있습니다.
    • 중첩 라우트가 충돌하거나 중복 선언되면 의도치 않은 렌더링 결과가 나타날 수 있으므로 주의해야 합니다.
    • TypeScript 사용 시 타입 정의가 올바르게 되어 있지 않으면 컴파일 단계에서 오류가 발생할 수 있습니다.

4. 이해를 돕기 위한 개발 코드와 공식 문서 비교

1. routeTree.gen.ts (자동 생성 파일)

목적

  • 파일 시스템 구조에 따라 라우트를 자동으로 생성하고, 각 라우트를 업데이트 및 연결하는 역할을 합니다.

주요 구성 요소

  • 라우트 임포트
    import { Route as AboutImport } from './app/about';
    // ... 다른 라우트 임포트
    
    
  • 각 라우트 컴포넌트를 개별 파일에서 가져옵니다.
  • 라우트 업데이트
    const AboutRoute = AboutImport.update({
      id: '/about',
      path: '/about',
      getParentRoute: () => rootRoute,
    } as any);
    
    
  • .update() 메서드를 사용하여 각 라우트에 id, path, 부모 라우트를 지정합니다.
  • 타입 선언
    declare module '@tanstack/react-router' {
      interface FileRoutesByPath {
        '/about': {
          id: '/about';
          path: '/about';
          fullPath: '/about';
          preLoaderRoute: typeof AboutImport;
          parentRoute: typeof rootRoute;
        }
        // ... 다른 라우트 타입 정의
      }
    }
    
    
  • FileRoutesByPath, FileRoutesByTo, FileRoutesById 인터페이스를 통해 타입 안정성을 보장합니다.
  • 라우트 트리 생성
    const rootRouteChildren = {
      PageRoute: PageRoute,
      AboutRoute: AboutRoute,
      // ... 다른 라우트들
    };
    
    export const routeTree = rootRoute._addFileChildren(rootRouteChildren)
      ._addFileTypes<FileRouteTypes>();
    
    
  • 루트 라우트에 자식 라우트를 추가하여 전체 라우트 트리를 구성합니다.

2. __root.tsx (루트 컴포넌트 파일)

목적

  • 애플리케이션 최상위에서 라우터를 초기화하고, 전역 레이아웃(헤더, 푸터 등)과 컨텍스트를 설정합니다.

주요 구성 요소

  • 라우트 생성
    export const Route = createRootRouteWithContext<RouterContext>()({
      component: RootComponent,
      beforeLoad: async ({ context, location }) => {
        // 인증 상태와 경로에 따라 리다이렉션 처리
        const authenticated = context.auth.authenticated;
        const pathname = location.pathname;
    
        if (authenticated && matchRoute(noAuthRoutes, pathname)) return redirect({ to: '/' });
        if (!authenticated && !(matchRoute(publicRoutes, pathname) || matchRoute(noAuthRoutes, pathname))) {
          return redirect({ to: '/auth', search: { redirect: pathname } });
        }
      },
    });
    
    
  • createRootRouteWithContext<RouterContext>()를 사용하여 루트 라우트를 생성합니다.
  • 전역 컴포넌트 구성
    function RootComponent() {
      const navigate = Route.useNavigate();
      const { user, logout } = useAuth();
      const router = useRouter();
    
      const handleLogout = async () => {
        await logout();
        await router.invalidate();
        await navigate({ to: '/auth' });
      };
    
      return (
        <div className="flex min-h-screen w-full flex-col">
          <Header username={user?.name} onClickLogout={handleLogout} />
          <main className="mx-auto flex w-full flex-1 justify-center">
            <Outlet />
          </main>
          <Footer />
          <TanStackRouterDevtools position="bottom-left" />
          <ReactQueryDevtools buttonPosition="bottom-right" />
        </div>
      );
    }
    
    
  • RootComponent 내에서 <Outlet />을 사용해 하위 라우트를 렌더링하며, Header, Footer, DevTools 등을 포함합니다.

핵심 포인트

  • beforeLoad 훅: 라우트 접근 전에 인증 상태를 체크하고, 필요한 경우 리다이렉션을 수행합니다.
  • Outlet: 하위 라우트를 렌더링하기 위한 자리 표시자 역할을 합니다.
  • 전역 컨텍스트: RouterContext를 통해 QueryClient와 auth 객체를 하위 컴포넌트에 제공합니다.

공식문서와의 비교

(1) 라우트 정의 방식

  • 공식문서
    • 보통 개발자가 직접 createReactRouter와 createRouteTree를 사용해 라우트를 선언적으로 구성합니다.
    • 예시:
    • import { createReactRouter, createRouteTree } from '@tanstack/react-router'; const routeTree = createRouteTree([ { path: '/', element: <HomePage />, children: [ { path: 'about', element: <AboutPage /> }, // 추가 라우트 ], }, ]); export const router = createReactRouter({ routeTree });
  • 제공된 코드
    • 파일 기반 라우팅을 통해 CLI나 빌드 도구가 자동으로 routeTree.gen.ts 파일을 생성합니다.
    • 각 파일의 위치가 라우트 경로와 매핑되며, .update() 메서드로 라우트 정보와 타입을 자동으로 업데이트합니다.

(2) 루트 컴포넌트 구성

  • 공식문서
    • 일반적으로 단순하게 RouterProvider를 사용하여 생성된 라우터 인스턴스를 애플리케이션 최상위에 주입합니다.
    • import { RouterProvider } from '@tanstack/react-router'; import { router } from './router'; function Root() { return <RouterProvider router={router} />; }
  • 제공된 코드
    • createRootRouteWithContext를 사용해 루트 라우트를 생성하고, beforeLoad 훅으로 인증 등 추가 로직을 수행합니다.
    • 헤더, 푸터, DevTools 등 다양한 전역 UI 컴포넌트를 포함한 더 복잡한 레이아웃 구성이 이루어집니다.

(3) 라우트 자동 생성 vs. 수동 정의

  • 공식문서
    • 주로 수동으로 라우트 트리를 선언하는 방식을 설명합니다.
    • 파일 기반 라우팅은 별도의 가이드나 플러그인 형태로 제공되는 경우가 있습니다.
  • 제공된 코드
    • 파일 기반 라우팅 방식을 채택해, 파일 경로와 라우트 경로를 자동으로 연결합니다.
    • 자동 생성 파일(routeTree.gen.ts)에서 타입 선언, 라우트 업데이트, 라우트 트리 구성 등이 자동으로 처리되어, 개발자가 별도의 라우트 구성을 작성할 필요가 없습니다.
    • 이 방식은 대규모 프로젝트나 일관된 규칙에 따라 라우트를 관리할 때 유용합니다.

5. 정리

  • 기본 문법
    • 라우트 생성: 각 라우트 파일에서 Route 객체를 가져와 .update()를 통해 id, path, 부모 라우트를 설정합니다.
    • 타입 선언: 자동 생성된 인터페이스를 통해 파일 기반 라우트의 타입 안전성을 보장합니다.
    • 라우트 트리 구성: 루트 라우트에 자식 라우트를 추가하여 전체 트리를 구성합니다.
    • 루트 컴포넌트 구성: createRootRouteWithContext를 사용하여 전역 컨텍스트와 함께 루트 컴포넌트를 정의하고, <Outlet />으로 하위 라우트를 렌더링하며, beforeLoad 훅으로 추가 로직(예: 인증 체크)을 처리합니다.
  • 공식문서와의 차이점:
    • 수동 정의 vs. 파일 기반 자동 생성: 공식 문서는 수동으로 라우트를 선언하는 방법을 설명하지만, 제공된 코드는 파일 기반 라우팅을 통해 자동으로 라우트 구성을 생성합니다.
    • 루트 컴포넌트: 공식 예제에서는 주로 RouterProvider를 사용하는 단순한 구성을 보여주지만, 제공된 코드는 커스텀 beforeLoad 훅과 전역 레이아웃(헤더, 푸터, DevTools 등)을 포함하는 더 복잡한 구성을 보여줍니다.
    • 타입 안전성 강화: 자동 생성 파일에서는 파일 경로와 라우트 매핑을 위한 상세한 타입 선언이 포함되어 있어, 컴파일 단계에서의 타입 검증이 더욱 철저합니다.