Frontend/React

React Router v6: 선언적 라우팅으로 설계하는 견고한 SPA 내비게이션

미니임 2026. 3. 1. 21:51

 

현대 웹 애플리케이션에서 'URL'은 단순한 주소 그 이상의 의미를 갖습니다. 사용자의 현재 상태를 나타내는 가장 중요한 데이터 소스이자, 공유 가능한 애플리케이션의 좌표죠. React 생태계에서 이 좌표계를 관리하는 표준인 React Router가 v6로 진화하면서 더욱 가볍고 직관적인 인터페이스를 갖추게 되었습니다.

단순히 페이지를 바꾸는 기능을 넘어, 복잡한 파라미터 구조를 어떻게 효율적으로 설계하고 관리할 수 있는지 실무적인 관점에서 깊게 파헤쳐 보겠습니다.


1. Deep Dive: 라우팅의 핵심 메커니즘

React Router v6의 핵심은 상태 기반 라우팅입니다. 브라우저의 History API를 구독하고 있다가, URL이 변경되면 이를 감지하여 현재 경로에 매칭되는 컴포넌트 트리를 다시 렌더링하는 구조입니다.

핵심 개념: 중첩 라우팅(Nested Routing)

v6에서 가장 강력해진 기능은 중첩 라우팅입니다. 이는 레이아웃의 중복을 제거하고, 특정 부분만 교체하는 방식을 선언적으로 정의하게 해줍니다.

비유로 이해하기: 백화점 안내도

라우팅을 백화점 층별 안내도라고 생각해보세요.

  • BrowserRouter: 백화점 건물 전체를 감싸는 외벽입니다.
  • Routes: 각 층으로 연결되는 엘리베이터입니다.
  • Route: 특정 매장(컴포넌트)으로 가는 표지판입니다.
  • Outlet: 매장 안의 '탈의실'이나 '카운터'처럼, 특정 영역만 상황에 따라 바뀌는 공간입니다.

2. Hands-on: 실전 이커머스 상세 페이지 구현

단순한 예제 대신, 상품 목록에서 특정 상품을 클릭했을 때 상품 ID를 파라미터로 전달하고, 쿼리 스트링을 통해 필터 상태를 유지하는 실무 로직을 구현해 보겠습니다.

Step 1: 라우트 구성 (App.js)

Routes와 Route를 사용하여 계층 구조를 정의합니다.

JavaScript
 
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import ProductList from './pages/ProductList';
import ProductDetail from './pages/ProductDetail';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* 공통 레이아웃 적용을 위한 중첩 라우팅 */}
        <Route path="/" element={<Layout />}>
          <Route index element={<ProductList />} />
          {/* :productId는 동적 파라미터(URL Params)를 의미함 */}
          <Route path="product/:productId" element={<ProductDetail />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

Step 2: 파라미터 추출 및 활용 (ProductDetail.js)

useParams와 useSearchParams 훅을 사용하여 URL 데이터를 핸들링합니다.

JavaScript
 
import { useParams, useSearchParams, useNavigate } from 'react-router-dom';

const ProductDetail = () => {
  // 1. URL 파라미터 추출 (예: /product/42 -> 42)
  const { productId } = useParams();
  
  // 2. 쿼리 스트링 관리 (예: ?color=red&size=XL)
  const [searchParams, setSearchParams] = useSearchParams();
  const color = searchParams.get('color');

  const navigate = useNavigate();

  const updateColor = (newColor) => {
    // 기존 쿼리 스트링을 유지하면서 특정 값만 변경
    setSearchParams({ color: newColor });
  };

  return (
    <section>
      <h2>상품 번호: {productId}</h2>
      <p>현재 선택된 색상: {color || '없음'}</p>
      
      <button onClick={() => updateColor('blue')}>파란색 선택</button>
      <button onClick={() => navigate(-1)}>뒤로 가기</button>
    </section>
  );
};

💡 Troubleshooting Tip

  • 404 Not Found: v6에서는 Switch 대신 Routes를 사용하며, 일치하는 경로가 없을 경우를 대비해 <Route path="*" element={<NotFound />} />를 마지막에 반드시 배치하는 것이 좋습니다.
  • Relative Paths: v6의 Link나 Maps는 현재 경로를 기준으로 상대 경로를 지원합니다. path="detail"은 /current/detail로 동작하며, 절대 경로를 원하면 /detail처럼 슬래시를 붙여야 합니다.

3. Trade-offs: 고려해야 할 사항

React Router v6는 훌륭하지만, 모든 상황에서 정답은 아닙니다.

  • Pros:
    • Bundle Size: 이전 버전 대비 크기가 약 70% 감소하여 성능에 유리합니다.
    • Ranked Routes: 경로 매칭 우선순위를 라이브러리가 자동으로 계산하므로, 작성 순서에 구애받지 않습니다.
  • Cons:
    • Breaking Changes: v5에서 v6로 전환 시 useHistory가 useNavigate로 변경되는 등 API 변화가 커서 마이그레이션 비용이 발생합니다.
    • Data Fetching: 최신 버전은 loader 기능을 제공하지만, 기존 useEffect 기반 데이터 페칭 방식과 결합할 때 설계 복잡도가 증가할 수 있습니다.

4. 결론 및 제언

React Router v6는 단순한 내비게이션 도구를 넘어, 애플리케이션의 구조를 정의하는 설계도와 같습니다. 특히 중첩 라우팅상태 기반 파라미터 관리를 적절히 활용하면, 사용자에게 매끄러운 UX를 제공함과 동시에 개발자에게는 유지보수하기 쉬운 코드 구조를 선사합니다.

이제 여러분의 프로젝트를 점검해 보세요. 혹시 불필요하게 복잡한 useEffect로 URL 상태를 동기화하고 있지는 않나요? useSearchParams를 통해 URL을 '단일 진실 공급원(Single Source of Truth)'으로 활용해 보시길 권장합니다.

반응형