본문 바로가기
C.W.K.
Stream
Lesson 06 of 07 · published

React Router로 SPA 라우팅

~18 min · react-router, routing, spa, history-api

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
SPA는 HTML 한 장에서 여러 화면을 바꿔 보여 줘. 라우터를 붙이면 전체 페이지를 다시 불러오지 않고도 URL, 화면, 뒤로가기를 함께 관리할 수 있어.

라우터가 필요한 이유

Vite SPA는 브라우저가 index.html을 받고 main.tsx에서 앱을 마운트하면서 시작해. 일반 링크로 /conversations/abc에 이동하면 브라우저가 문서를 다시 받아 React 상태도 처음부터 만들어야 해.

클라이언트 라우터는 앱 안의 링크 클릭을 받아 history.pushState()로 URL만 바꾸고, 그 경로에 맞는 컴포넌트를 React가 렌더링하게 해. 문서를 다시 불러오지 않으므로 상태가 유지되고 브라우저의 뒤로가기도 정상적으로 동작해.

React Router의 핵심 도구

  1. <BrowserRouter>는 앱에 라우터 Context를 제공해.
  2. <Routes><Route>는 URL과 컴포넌트의 대응을 선언해.
  3. <Link>는 문서를 다시 받지 않고 URL을 바꾸는 앵커를 렌더링해.
  4. useParams()는 현재 경로의 동적 세그먼트를 읽어.
  5. useNavigate()는 폼 제출 뒤 이동처럼 코드에서 경로를 바꿀 때 사용해.

중첩 라우트와 현재 링크

중첩 라우트에서는 부모 컴포넌트를 레이아웃으로 두고 자식 화면이 들어갈 자리에 <Outlet />을 렌더링해. 그러면 cwkPippa의 사이드바처럼 화면 전환 뒤에도 남아 있어야 하는 UI를 유지할 수 있어. NavLink는 현재 URL과 일치하는지 알려 주므로 선택된 메뉴를 표시하기 편해.

직접 URL을 열 때는 호스트 설정이 필요해

배포된 SPA에서 /conversations/abc를 바로 열면 먼저 정적 호스트가 그 경로를 받아. 실제 파일이 없으면 404가 되므로, 정적 asset이 아닌 경로는 /index.html로 재작성하도록 설정해야 React Router가 이어받을 수 있어. Vercel, Cloudflare Pages, Netlify는 각각 이 규칙을 지원하고, 서버가 없는 Tauri 앱에는 필요하지 않아.

URL로 구분할 화면이 없다면 라우터도 필요 없어. 창 하나에서 단일 보드만 보여 주는 Tauri 앱이라면 React Router를 넣지 않아도 돼. 주소로 직접 열어야 하는 화면이 여러 개일 때 추가해.

다른 선택지

TanStack Router는 타입 안전한 라우트와 검색 파라미터가 강점이고, Wouter는 작은 번들에 집중해. 일반적인 SPA에는 React Router가 무난해. 라우트 타입 추론이 핵심이면 TanStack Router를, 번들 크기를 아주 작게 유지해야 하면 Wouter를 검토해.

Code

main.tsx: 앱을 라우터로 감싸기·tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
App.tsx에서 기본 route와 중첩 route 조합하기·tsx
import { Routes, Route, Outlet, NavLink, useParams } from "react-router-dom";

function Layout() {
  return (
    <div className="flex min-h-screen">
      <nav className="w-48 border-r p-4 space-y-2">
        <NavLink
          to="/"
          end
          className={({ isActive }) => isActive ? "text-brand" : "text-muted"}
        >
          Home
        </NavLink>
        <NavLink
          to="/conversations"
          className={({ isActive }) => isActive ? "text-brand" : "text-muted"}
        >
          Conversations
        </NavLink>
      </nav>
      <main className="flex-1 p-6">
        <Outlet />
      </main>
    </div>
  );
}

function Home() { return <h1>Welcome</h1>; }
function Conversations() { return <h1>List</h1>; }
function Conversation() {
  const { id } = useParams<{ id: string }>();
  return <h1>Conversation {id}</h1>;
}

export default function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<Home />} />
        <Route path="/conversations" element={<Conversations />} />
        <Route path="/conversations/:id" element={<Conversation />} />
        <Route path="*" element={<p>404</p>} />
      </Route>
    </Routes>
  );
}

External links

Exercise

부트스트랩 프로젝트에 react-router-dom을 설치하고 Home, Conversations, Conversation/:id 화면을 연결해. NavLink로 이동할 때 새로고침이 없는지, 뒤로가기가 되는지, /conversations/abc를 직접 열어도 올바른 화면이 나오는지 확인해.
Hint
개발 서버에서는 Vite가 깊은 경로를 처리해. 프로덕션에서만 404가 난다면 정적 호스트에 index.html 경로 재작성 규칙을 추가해야 해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.