ReactReact 기초 · 9기초

React Router — SPA 라우팅 구현

ReactReactRouter라우팅SPAnavigate

React Router 설치

npm install react-router-dom

기본 라우팅 설정

// src/App.tsx
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import HomePage from "./pages/HomePage";
import UserListPage from "./pages/UserListPage";
import UserDetailPage from "./pages/UserDetailPage";
import NotFoundPage from "./pages/NotFoundPage";
import Layout from "./components/Layout";

function App() {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<Layout />}>
                    <Route index element={<HomePage />} />
                    <Route path="users" element={<UserListPage />} />
                    <Route path="users/:id" element={<UserDetailPage />} />
                    <Route path="*" element={<NotFoundPage />} />
                </Route>
            </Routes>
        </BrowserRouter>
    );
}

Layout과 Outlet

// src/components/Layout.tsx
import { Outlet, NavLink } from "react-router-dom";

function Layout() {
    return (
        <div>
            <nav>
                {/* NavLink: 활성 경로에 active 클래스 자동 추가 */}
                <NavLink to="/" end>홈</NavLink>
                <NavLink to="/users">사용자</NavLink>
            </nav>

            <main>
                {/* 자식 라우트가 여기에 렌더링 */}
                <Outlet />
            </main>
        </div>
    );
}

동적 라우트

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

function UserDetailPage() {
    const { id } = useParams<{ id: string }>();
    const { data: user, loading } = useFetch<User>(`/api/users/${id}`);

    if (loading) return <Spinner />;
    return <div>{user?.name}</div>;
}

쿼리 파라미터

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

function UserListPage() {
    const [searchParams, setSearchParams] = useSearchParams();

    const page = Number(searchParams.get("page") ?? "1");
    const query = searchParams.get("q") ?? "";

    function handleSearch(newQuery: string) {
        setSearchParams({ q: newQuery, page: "1" });
    }

    function goToPage(newPage: number) {
        setSearchParams({ q: query, page: String(newPage) });
    }

    return (
        <div>
            <input value={query} onChange={e => handleSearch(e.target.value)} />
            {/* URL: /users?q=철수&page=2 */}
        </div>
    );
}

프로그래밍 방식 이동

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

function LoginForm() {
    const navigate = useNavigate();
    const { login } = useAuth();

    async function handleSubmit(e: FormEvent) {
        e.preventDefault();
        await login(email, password);
        navigate("/dashboard");           // 이동
        // navigate(-1);                  // 뒤로 가기
        // navigate("/login", { replace: true });  // 히스토리 교체
    }
}

라우트 보호 (Protected Routes)

function ProtectedRoute({ children }: { children: ReactNode }) {
    const { user } = useAuth();
    const location = useLocation();

    if (!user) {
        // 로그인 후 원래 가려던 곳으로 돌아오게 state 전달
        return <Navigate to="/login" state={{ from: location }} replace />;
    }

    return <>{children}</>;
}

// App.tsx에서
<Route path="dashboard" element={
    <ProtectedRoute>
        <DashboardPage />
    </ProtectedRoute>
} />

Link vs NavLink

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

// Link: 단순 이동
<Link to="/about">소개</Link>

// NavLink: 현재 경로와 일치 여부 스타일 적용
<NavLink
    to="/users"
    className={({ isActive }) => isActive ? "active" : ""}
>
    사용자
</NavLink>

정리

Hook/컴포넌트역할
BrowserRouter라우터 제공자
Routes, Route라우트 정의
Outlet자식 라우트 렌더링 위치
Link, NavLink페이지 이동 링크
useParamsURL 동적 파라미터
useSearchParams쿼리 파라미터
useNavigate프로그래밍 이동
Navigate리다이렉트

다음 편에서는 React 실전 프로젝트 — 지금까지 배운 모든 내용을 활용한 투두 앱을 만듭니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.