React Router 말고 Tanstack Router요

10tacion·2025년 2월 16일

Library

목록 보기
1/2
post-thumbnail

React Router 말고 Tanstack Router요
최근에 사용해봤던 Tanstack Router에 대해서 끄적끄적



웹에서의 라우팅

Tanstack Router는 라우팅 라이브러리이다. 그렇다면 먼저 라우팅의 정의에 대해서 정확히 알고 있어야겠다..! 검색해보자

라우팅은 위 처럼 어떠한 경로를 선택하는 프로세스를 말한다. 웹 개발 관점에서 또한 라우팅 또한 경로를 결정하는 프로세스를 말한다. 예를 들어 루트 경로/ 에서 Next 버튼을 클릭해서 /next라는 경로로 이동 했을 때, url 경로를 이동하는 프로세스 자체를 라우팅이라고 할 수 있다.


React에서 라우팅

React가 제공하는 API를 이용하면 웹 개발을 직관적이고 간편하게 할수 있다. 공식문서에는 리액트를 아래와 같이 소개하고 있다.

그렇다 리액트는 웹 및 사용자 인터페이스를 위한 라이브러리이다. 리액트에 라우팅 기능은 포함 되어 있지않다... 그렇다면 라우팅 동작을 직접 구현하거나, 잘 만들어진 라이브러리를 사용해야겠다. 다음과 같이 2가지 방안을 생각해볼 수 있겠다.

  • 라우팅 동작 직접 구현 (history API + popstate event)
  • 라우팅 라이브러리 사용 (react-router, tanstack router...)

라이브러리의 기능만을 잘 활용하는것도 물론 좋다!
하지만 특정 기능을 구현할 때 직접 구현 vs 라이브러리 사용 이라는 선택지에 대한 판단 기준을 갖기 위해서는 로우 레벨에서 해당 기능을 구현하려면 어떤 노력이 필요한지 알고 있는것도 좋은것 같다. 어떤 중요한 순간에서 판단을 내릴 때 더 도움이 될수 있을것이라고 생각한다.

또한 라이브러리는 언제든지 사라지거나 대체될 수 있기때문에 무작정 라이브러리를 사용하는것을 경계해야한다.

그래서 Tanstack Router를 바로 소개할 수도 있지만 굳이 라우팅 동작을 직접 구현하는 방법 부터 소개하고자 한다!


라우팅 동작 직접 구현 (history API + popstate event)

history API
브라우저는 현재 세션 동안 사용자가 방문한 페이지들을 내부적으로 기록하는 세션 히스토리 스택(session history stack)을 관리한다. histroy API는 유저의 이 히스토리 스택을 조작할 수 있는 메서드와 속성들을 제공한다.

이 중 pushState 메서드를 활용하면 새로운 히스토리 기록에 연결할 URL을 지정할 수 있다. 이 때 현재 페이지의 URL은 변경되지만, 페이지 전체가 새로 로드되진 않는다.

popstate event
popstate event는 하나의 document에 기록된 두개의 history entry의 활성상태에 변화가 일어날 때 발생한다. 쉽게 말하면 브라우저 세션 히스토리 스택에 저장된 두 히스토리 기록 사이를 이동할 때 발생한다는 말이다. 그래서 뒤로가기나 앞으로 가기를 눌러 현재 활성화된 history entry가 변경되면 popstate 이벤트가 발생한다.

주의 할 점은 history.pushState(), history.replaceState()로는 popstate event가 발생하지 않는다는 사실이다. 이 메서드들은 "네비게이션 동작"이 아니라 "히스토리 스택의 조작"이기 때문이다.

따라서 popstate event는 뒤로가기 또는 앞으로 가기 동작history.back() history.forward()을 하고 난 후 실행할 동작을 정의하는데 활용할 수 있겠다.

이를 통해서 라우팅 하는 기능을 간단하게 구현해보면 아래와 같다.

import React, { useState, useEffect } from 'react';

// 각 경로에 대응하는 컴포넌트
function Home() {
  return <h1>홈 페이지</h1>;
}

function About() {
  return <h1>소개 페이지</h1>;
}

function NotFound() {
  return <h1>404 - 페이지를 찾을 수 없습니다.</h1>;
}

// 경로에 따라 렌더링할 컴포넌트를 매핑하는 객체
const routes = {
  '/': <Home />,
  '/about': <About />,
};

function App() {
  const [currentPath, setCurrentPath] = useState(window.location.pathname);

  useEffect(() => {
    // 브라우저 뒤로가기나 앞으로가기 시 URL 변경 감지
    const onPopState = () => {
      setCurrentPath(window.location.pathname);
    };

    window.addEventListener('popstate', onPopState);
    return () => window.removeEventListener('popstate', onPopState);
  }, []);

  // 네비게이션 함수: URL 변경 및 상태 업데이트
  const navigate = (to) => {
    window.history.pushState({}, '', to);
    setCurrentPath(to);
  };

  return (
    <div>
      <nav>
        <button onClick={() => navigate('/')}></button>
        <button onClick={() => navigate('/about')}>소개</button>
      </nav>
      <div>
        {routes[currentPath] || <NotFound />}
      </div>
    </div>
  );
}

export default App;

라우팅 라이브러리 사용(Tanstack Router)

내가 직접 history api나 popstate event를 활용해서 라우팅을 구현하지 않아도, 멋진 라이브러리 개발자 분들께서 라우팅 동작을 잘 구현해서 인터페이스를 제공해주신다. 참 좋다.

대표적으로는 React-router가 있다. 최근에 v7으로 업데이트가 되었다.. v4였나 v5버전 부터 사용했던것 같은데 참 세월이 빠르다..
그리고 다른 라이브러리 중 하나는 Tanstack Router이다. 여러분들이 잘 아는 Tanstack query를 만든 Tanstack 팀에서 만든 라우팅 라이브러리이다. 나는 최근 이 Tanstack Router를 자주 사용한다.

Tanstack Router를 선택한 이유
Tanstack Router는 React-router에 비해서 패키지가 가볍고, type safe한 라우팅을 지원한다. 보일러플레이트 또한 확실히 간결한 느낌을 받았다.

최근 V7부터 React-router도 각 라우트 모듈에 대한 타입 생성(typegen)이 도입되어, loader, action, route params 등의 타입 안전성이 크게 개선되긴했다

나는 개인적으로 Router 상수를 만들고 정의해서 사용하는것보다 파일기반 라우팅이 더 직관적이어서 선호하는 편이다.
내가 가장 좋았던 부분은 바로 이 ⭐️ 파일기반 라우팅을 지원한다는 것이다 하하. 아무튼 아래에서 Tanstack Router를 사용하는 방법에 대해서 알아보자. (vite를 사용했을 때 기준이다)

Tanstack Router 설치 및 초기 세팅

1. 패키지 설치

pnpm add @tanstack/react-router
# or
yarn add @tanstack/react-router

2. 초기 세팅

TanStackRouterVite()
Tanstack Router의 vite 플러그인을 사용하자

//vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import path from "path";

// https://vite.dev/config/
export default defineConfig({
  plugins: [TanStackRouterVite(), react()],
  resolve: {
    alias: [{ find: "@", replacement: path.resolve(__dirname, "./") }],
  },
});

createRootRoute
createRootRoute로 루트 라우트를 정의할 수 있다. <Outlet/>으로 페이지 컴포넌트를 받아서 사용한다.

// src/routes/__root.tsx
import { createRootRoute, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { css } from "@/styled-system/css";
import "./index.css";
import "@sopt-makers/ui/dist/index.css";

export const Route = createRootRoute({
  component: () => (
    <>
      <div
        className={css({
          ...rootLayoutStyle,
        })}
      >
        <Outlet />
      </div>
      <TanStackRouterDevtools />
    </>
  ),
});

RouterProvider
라우트 프로바이더를 적용해주자.
기본적으로 Tanstack Router의 파일 기반 라우팅 기능을 사용하게 되면 프로젝트 실행 시 ./routeTree.gen 경로에 routeTree가 생성된다. 이 routeTree에서 Tanstack router는 모든 파일기반 라우팅을 한곳에 모아 라우터를 구성한다. 이 routeTree를 불러와서 router 상수를 만들어 주자. 이후<RouteProvider/>의 router에 적용해주면 된다.

// main.tsx
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";

// Import the generated route tree
import { routeTree } from "./routeTree.gen";

// Create a new router instance
const router = createRouter({ routeTree });

// Register the router instance for type safety
declare module "@tanstack/react-router" {
  interface Register {
    router: typeof router;
  }
}

// Render the app
const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) {
  const root = ReactDOM.createRoot(rootElement);
  root.render(
    <StrictMode>
      <RouterProvider router={router} />
    </StrictMode>
  );
}

초기 세팅 완성! 확실히 보일러 플레이트가 작고 너무 간결하다.

3. 파일 기반 라우팅

경로에 해당하는 폴더를 만든 후 그 폴더 내부에 index.tsx를 생성한다.(lazy loading을 적용할 경우 index.lazy.tsx) 그러면 폴더명에 따라 url 경로가 생성된다.

이후 index.tsx 내부에서 createFileRoute를 통해서 Route를 생성하여 export 해주면 라우팅 완료! (lazy loading을 적용할 경우 createLazyFileRoute 사용)

// src/routes/login-error/index.lazy.tsx
import ReLoginSection from "@/src/components/login-error/ReLoginSection";
import { css } from "@/styled-system/css";
import { createLazyFileRoute } from "@tanstack/react-router";

export const Route = createLazyFileRoute("/login-error/")({
  component: Index,
});

function Index() {
  return (
    <div className={css({ ...pageWrapperStyles })}>
      <main className={css({ ...mainWrapperStyles })}>
            ...
	  </main>
    </div>
  );
}

4. Tanstack Router 캐시 활용

Tanstack Router는 각 라우트의 loader 함수가 반환하는 값을 캐시하여 동일한 라우트를 다시 방문할 때 네트워크 요청 없이 빠르게 데이터를 가져올 수 있다. 페이지에서 간단한 데이터 페칭만 이루어진다면 이 부분을 잘 활용해서 불필요한 네트워크 요청을 줄일 수 있을것이다.
Tanstack Query 처럼 staleTimegcTime을 설정할 수 있다.

export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  staleTime: 10_000, // 10초 동안 fresh한 데이터로 취급
  gcTime: 1000 * 60 * 5, // 5분 후 캐시 자동 삭제
})

마치며

최근 새로운 기술을 사용해보고 직접 장단점을 느껴보는게 필요하다고 생각이든다. Tanstack Router 또한 그러한 맥락에서 사용한것이었지만, 너무 마음에 들어서 다른 분들께도 간단하게 소개드리고자 구구절절 글을 써봤다.. 파이팅

참고자료
https://reactrouter.com/home
https://tanstack.com/router/latest
https://developer.mozilla.org/en-US/docs/Web/API/History_API
https://developer.mozilla.org/en-US/docs/Web/API/PopStateEvent
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState

profile
늦게 자고 일찍 일어나기

0개의 댓글