React Router

SPA에서 페이지 전환을 구현한다

React Router는 Single Page Application에서 URL 기반 페이지 전환을 구현하는 라이브러리이다. BrowserRouter, Route, Link, useNavigate, useParams, 중첩 라우팅, 레이아웃 패턴, 404 처리를 다루어 AI Agent 대시보드의 다중 페이지 구조를 만드는 방법을 정리한다.

Engineering
저자

Kwangmin Kim

공개

2026년 05월 05일

1 SPA와 라우팅

전통적인 웹 사이트에서 페이지를 이동하면 서버에서 새 HTML 파일을 받아 전체 페이지를 새로고침한다.

SPA(Single Page Application)에서는 HTML 파일이 하나이다. 페이지 전환은 JavaScript가 현재 페이지의 컴포넌트를 교체하는 방식으로 이루어진다. 서버에 새 HTML을 요청하지 않으므로 전환이 빠르고 부드럽다.

그러나 SPA도 URL이 변경되어야 한다. 사용자가 /monitoring 페이지를 북마크하거나, 뒤로가기 버튼을 누르거나, URL을 직접 입력해서 접근할 수 있어야 한다. React Router가 이 URL ↔︎ 컴포넌트 매핑을 담당한다.

전통 웹:
/home         → 서버에서 home.html 로드
/monitoring   → 서버에서 monitoring.html 로드

SPA + React Router:
/home         → App 컴포넌트 안에서 HomePage 렌더링
/monitoring   → App 컴포넌트 안에서 MonitoringPage 렌더링

2 설치와 기본 설정

npm install react-router-dom

2.1 라우트 정의

// src/main.tsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./App";
import HomePage from "./pages/HomePage";
import ChatPage from "./pages/ChatPage";
import MonitoringPage from "./pages/MonitoringPage";

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "chat", element: <ChatPage /> },
      { path: "monitoring", element: <MonitoringPage /> },
    ],
  },
]);

createRoot(document.getElementById("root")!).render(
  <RouterProvider router={router} />
);

createBrowserRouter는 React Router v6.4 이상에서 권장하는 방식이다. 라우트를 객체 배열로 정의하여 구조가 명확하다.

2.2 레이아웃 컴포넌트

// src/App.tsx
import { Outlet } from "react-router-dom";
import Header from "./components/Header";
import Sidebar from "./components/Sidebar";

function App() {
  return (
    <div className="app">
      <Header />
      <div className="content">
        <Sidebar />
        <main>
          <Outlet />  {/* 자식 라우트가 여기에 렌더링된다 */}
        </main>
      </div>
    </div>
  );
}

<Outlet />은 현재 URL에 매칭되는 자식 라우트 컴포넌트가 렌더링되는 위치이다. URL이 /chat이면 <Outlet /> 자리에 <ChatPage />가 들어간다. Header와 Sidebar는 페이지가 바뀌어도 유지된다.

3 네비게이션

3.2 useNavigate: 프로그래밍 방식 이동

이벤트 핸들러나 API 호출 후 페이지를 이동할 때 사용한다.

import { useNavigate } from "react-router-dom";

function LoginPage() {
  const navigate = useNavigate();

  const handleLogin = async () => {
    const success = await login(credentials);
    if (success) {
      navigate("/chat");        // 채팅 페이지로 이동
    }
  };

  const goBack = () => {
    navigate(-1);               // 뒤로가기
  };

  return (
    <div>
      <button onClick={handleLogin}>로그인</button>
      <button onClick={goBack}>뒤로</button>
    </div>
  );
}

4 동적 라우트와 URL 매개변수

URL의 일부를 변수로 받는다.

// 라우트 정의
const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      { path: "agents/:agentName", element: <AgentPage /> },
      { path: "agents/:agentName/run/:runId", element: <RunDetailPage /> },
    ],
  },
]);
// 매개변수 사용
import { useParams } from "react-router-dom";

function AgentPage() {
  const { agentName } = useParams<{ agentName: string }>();

  return <h1>{agentName} 에이전트</h1>;
}

// /agents/qna_chatbot → agentName = "qna_chatbot"
// /agents/data_standardizer → agentName = "data_standardizer"

4.1 쿼리 매개변수

URL 뒤의 ?key=value를 읽는다.

import { useSearchParams } from "react-router-dom";

function MonitoringPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const period = searchParams.get("period") || "7d";
  const agent = searchParams.get("agent") || "all";

  const changePeriod = (newPeriod: string) => {
    setSearchParams({ period: newPeriod, agent });
  };

  return (
    <div>
      <p>기간: {period}, 에이전트: {agent}</p>
      <button onClick={() => changePeriod("30d")}>30일</button>
    </div>
  );
}

// /monitoring?period=7d&agent=qna_chatbot

5 중첩 라우팅

라우트 안에 라우트를 중첩하여 레이아웃을 계층적으로 구성한다.

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,               // 최상위 레이아웃 (Header + Sidebar)
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "agents/:agentName",
        element: <AgentLayout />,    // 에이전트별 레이아웃 (탭 네비게이션)
        children: [
          { index: true, element: <AgentOverview /> },
          { path: "chat", element: <AgentChat /> },
          { path: "documents", element: <AgentDocuments /> },
          { path: "settings", element: <AgentSettings /> },
        ],
      },
      { path: "monitoring", element: <MonitoringPage /> },
    ],
  },
]);
function AgentLayout() {
  const { agentName } = useParams();

  return (
    <div>
      <h1>{agentName}</h1>
      <nav>
        <NavLink to="">개요</NavLink>
        <NavLink to="chat">채팅</NavLink>
        <NavLink to="documents">문서</NavLink>
        <NavLink to="settings">설정</NavLink>
      </nav>
      <Outlet />  {/* AgentOverview, AgentChat 등이 여기에 렌더링 */}
    </div>
  );
}

중첩 라우팅에서 NavLinkto 값은 상대 경로이다. to="chat"은 현재 경로 뒤에 /chat을 붙인다. 이 방식은 FastAPI의 APIRouter(prefix=...)와 유사하다.

6 404 처리

매칭되는 라우트가 없을 때 보여줄 페이지를 정의한다.

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "chat", element: <ChatPage /> },
      // ... 다른 라우트들

      // 모든 미매칭 경로를 잡는다
      { path: "*", element: <NotFoundPage /> },
    ],
  },
]);

function NotFoundPage() {
  const navigate = useNavigate();
  return (
    <div>
      <h1>페이지를 찾을 수 없다</h1>
      <p>요청한 경로가 존재하지 않는다.</p>
      <button onClick={() => navigate("/")}>홈으로</button>
    </div>
  );
}

path: "*"는 와일드카드로 위에서 매칭되지 않은 모든 경로를 잡는다. 라우트 배열의 마지막에 위치해야 한다.

7 AI Agent 대시보드 라우트 구조 예시

실제 AI Agent 플랫폼에서 사용하는 라우트 구조이다.

const router = createBrowserRouter([
  {
    path: "/",
    element: <AppLayout />,
    children: [
      { index: true, element: <HomePage /> },

      // 에이전트 페이지
      { path: "agents/qna_chatbot", element: <QnaChatbotPage /> },
      { path: "agents/data_standardizer", element: <DataStandardizerPage /> },

      // 운영 페이지
      { path: "monitoring", element: <MonitoringPage /> },
      { path: "records", element: <RecordsPage /> },

      // 실험 페이지
      { path: "experiments", element: <ExperimentsPage /> },
      { path: "experiments/:experimentId", element: <ExperimentDetailPage /> },

      // 404
      { path: "*", element: <NotFoundPage /> },
    ],
  },
]);

이 구조에서 AppLayout은 Header, Sidebar를 포함하는 공통 레이아웃이고, <Outlet />에 각 페이지가 들어간다.

8 관련 주제

선행 지식

후속 주제

다른 카테고리 연결

Subscribe

Enjoy this blog? Get notified of new posts by email: