리액트 라우터 DOM 설치 및 기본 사용법(React Router v6)

리액트 라우터 DOM 설치 및 기본 사용법(React Router v6)

싱글 페이지 애플리케이션(SPA)에서 화면 전환과 URL 관리를 담당하는 표준 라이브러리가 react-router-dom입니다. 이 글에서는 설치 → 기본 셋업 → 페이지 이동 → 파라미터/검색쿼리 → 중첩 라우팅 → 404 처리 순서로 핵심만 알아봅니다. (GitHub Pages 배포 시 HashRouter 권장 안내 포함)

1) 설치

npm i react-router-dom
# yarn add react-router-dom
# pnpm add react-router-dom

설치 확인:

npm ls react-router-dom
리액트 라우터 DOM 설치 및 확인 예시

2) 라우터 선택: BrowserRouter vs HashRouter

  • BrowserRouter: 일반 서버 환경(리라이트 설정 가능)에서 추천.
  • HashRouter: GitHub Pages처럼 서버 리라이트를 못 하는 정적 호스팅에 추천(새로고침 404 방지).

본 글은 GitHub Pages 배포를 고려해 기본 예제는 HashRouter로 진행합니다. 일반 서버면 BrowserRouter로 그대로 바꿔 쓰면 됩니다.

3) 프로젝트 기본 셋업

3-1) 엔트리에서 라우터 감싸기

src/index.js (또는 main.jsx)

import React from "react";
import ReactDOM from "react-dom/client";
import { HashRouter as Router } from "react-router-dom"; // GH Pages면 HashRouter 권장
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Router>
    <App />
  </Router>
);

3-2) 라우트 매핑

src/App.jsx

import { Routes, Route, Link, NavLink } from "react-router-dom";

function Home() {
  return <h2>Home</h2>;
}

function About() {
  return <h2>About</h2>;
}

export default function App() {
  return (
    <div style={{ padding: 16 }}>
      <h1>React Router v6 Demo</h1>

      <nav style={{ marginBottom: 12 }}>
        {/* NavLink: 활성 라우트에 클래스/스타일 적용 가능 */}
        <NavLink to="/" end style={({ isActive }) => ({ fontWeight: isActive ? "bold" : "normal", marginRight: 8 })}>
          Home
        </NavLink>
        <NavLink to="/about" style={({ isActive }) => ({ fontWeight: isActive ? "bold" : "normal" })}>
          About
        </NavLink>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        {/* 404 */}
        <Route path="*" element={<h2>Not Found</h2>} />
      </Routes>
    </div>
  );
}

▶ 실행 화면 설명: 상단에 Home/About 링크가 보이고, 각 링크를 클릭하면 굵게(활성) 표시가 바뀌며 본문이 해당 페이지로 전환됩니다. 새로고침해도 해시(URL # 뒤)가 유지되어 동일 화면이 열립니다.

4) 페이지 이동(Navigate)과 파라미터 전달

4-1) 프로그래매틱 네비게이션

useNavigate()로 코드에서 이동할 수 있습니다.

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

function Login() {
  const navigate = useNavigate();
  const onSubmit = (e) => {
    e.preventDefault();
    // 인증 로직...
    navigate("/dashboard"); // 성공 시 이동
  };
  return (
    <form onSubmit={onSubmit}>
      <button type="submit">로그인</button>
    </form>
  );
}

4-2) URL 파라미터 읽기

:id 같은 동적 세그먼트를 정의하고 useParams()로 읽습니다.

// App.jsx 일부
import { Routes, Route, Link, useParams } from "react-router-dom";

function Users() {
  return (
    <div>
      <h2>Users</h2>
      <ul>
        <li><Link to="/users/1">User 1</Link></li>
        <li><Link to="/users/2">User 2</Link></li>
      </ul>
    </div>
  );
}

function UserDetail() {
  const { id } = useParams();
  return <h3>User Detail: {id}</h3>;
}

// Routes 매핑
<Routes>
  <Route path="/users" element={<Users />} />
  <Route path="/users/:id" element={<UserDetail />} />
  <Route path="*" element={<h2>Not Found</h2>} />
</Routes>

▶ 실행 화면 설명: /users 페이지에서 User 1 또는 2 링크를 누르면 주소가 /users/1, /users/2로 바뀌고 본문에 선택한 사용자 ID가 표시됩니다.

5) 쿼리스트링(검색 파라미터) 다루기

useSearchParams()로 읽기/쓰기.

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

function SearchPage() {
  const [sp, setSp] = useSearchParams();
  const q = sp.get("q") ?? "";

  const onChange = (e) => {
    setSp({ q: e.target.value }); // 주소창에 ?q=값 갱신
  };

  return (
    <div>
      <h2>Search</h2>
      <input placeholder="검색어" value={q} onChange={onChange} />
      <p>현재 검색어: <strong>{q}</strong></p>
    </div>
  );
}

▶ 실행 화면 설명: 입력값을 바꾸면 주소창의 쿼리스트링(?q=)이 함께 변경되고 새로고침해도 동일 검색어가 유지됩니다.

6) 중첩 라우팅(Nested Routing)

부모 라우트의 공통 레이아웃 아래에서 자식 라우트가 렌더링되도록 구성합니다.

import { Outlet, Link, Routes, Route } from "react-router-dom";

function DashboardLayout() {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 16 }}>
      <aside>
        <h3>Dashboard</h3>
        <ul>
          <li><Link to="overview">Overview</Link></li>
          <li><Link to="reports">Reports</Link></li>
        </ul>
      </aside>
      <main>
        {/* 자식 라우트가 이 위치에 렌더링됨 */}
        <Outlet />
      </main>
    </div>
  );
}

function Overview() { return <h2>Overview</h2>; }
function Reports() { return <h2>Reports</h2>; }

// Routes
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<Overview />} />      {/* /dashboard */}
    <Route path="overview" element={<Overview />} />
    <Route path="reports" element={<Reports />} />
  </Route>

  <Route path="*" element={<h2>Not Found</h2>} />
</Routes>

▶ 실행 화면 설명: 좌측 메뉴(Overview/Reports)를 클릭하면 우측 본문 영역만 바뀌고 URL은 /dashboard/overview, /dashboard/reports 형태로 변경됩니다.

7) 404(와일드카드) 처리

정의되지 않은 경로에는 마지막에 path="*"를 둬서 404 화면을 출력합니다.

<Routes>
  {/* ...기존 라우트 */}
  <Route path="*" element={<h2>Not Found</h2>} />
</Routes>

▶ 실행 화면 설명: 존재하지 않는 주소를 입력하면 “Not Found”가 표시됩니다.

8) GitHub Pages 배포 팁

  • HashRouter 사용 시 새로고침 404를 피할 수 있습니다.
  • package.jsonhomepage가 설정되어 있으면, gh-pages로 배포 시 정적 파일 경로가 자동 보정됩니다.

자주 만나는 오류와 해결

  • 새로고침 시 404: 정적 호스팅이면 HashRouter로 전환하거나, 서버 리라이트 설정 필요.
  • 링크 클릭해도 화면이 안 바뀜: 라우터로 앱이 감싸졌는지(<Router></Router>) 확인.
  • 활성 링크 스타일이 적용 안 됨: NavLink 사용, v6에선 activeClassName 대신 style/className 콜백.
  • 동적 파라미터가 undefined: 경로에 :id를 선언했고 useParams()를 동일 트리의 자식에서 호출했는지 확인.

참고 링크

함께 보면 좋은 게시글

위로 스크롤