SPA는 페이지 하나로 도는데, 어떻게 /about, /user/3 같은 주소마다 다른 화면을 보여줄까요? 그 답이 라우팅(routing)이고, 리액트에서는 React Router라는 라이브러리로 해결합니다. 차근차근 따라가 볼게요.
<BrowserRouter> · <Routes> · <Route>로 경로-컴포넌트를 매핑할 수 있어요.<Link>로 새로고침 없이 페이지를 이동시킬 수 있어요.:id)를 useParams()로 읽고, useNavigate()로 코드에서 이동시킬 수 있어요.라우팅(routing)은 URL(주소)에 따라 서로 다른 컴포넌트(화면)를 보여주는 일이에요. 예를 들어 /에서는 홈 화면, /about에서는 소개 화면, /user/3에서는 3번 사용자 화면을 띄우는 식이죠.
전통적인 웹은 주소가 바뀌면 서버에서 새 HTML 페이지를 통째로 받아 화면을 갈아끼웠어요. 하지만 SPA(단일 페이지 앱)는 페이지를 새로 받지 않아요. 대신 주소만 바꾸고, 그 주소에 맞는 컴포넌트를 자바스크립트로 그려서 갈아끼웁니다.
npm install react-router-dom으로 설치해서 씁니다.
React Router의 뼈대는 세 가지예요. <BrowserRouter>로 앱 전체를 감싸고, 그 안에 <Routes>를 두고, <Routes> 안에 <Route>를 여러 개 나열해 경로와 컴포넌트를 짝지어요.
| 구성 요소 | 역할 |
|---|---|
| <BrowserRouter> | 앱을 감싸 라우팅 기능을 켜는 최상위 껍데기. 브라우저의 주소(URL)를 관리해요. 보통 앱에 딱 한 번만 써요. |
| <Routes> | 여러 <Route>를 담는 상자. 현재 URL과 가장 잘 맞는 하나를 골라 보여줘요. |
| <Route> | path(경로)와 element(보여줄 컴포넌트)를 짝지어요. 예: path="/about" ↔ <About /> |
코드로 보면 이렇게 생겼어요. path에는 주소 문자열을, element에는 보여줄 컴포넌트를 넣습니다.
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./Home";
import About from "./About";
function App() {
return (
<BrowserRouter>
<Routes>
{/* path="/" 일 때 Home 컴포넌트를 보여줌 */}
<Route path="/" element={<Home />} />
{/* path="/about" 일 때 About 컴포넌트를 보여줌 */}
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
<Route path="/about" element={<About />} />는 "주소가 /about이면 About 컴포넌트를 그려라"라는 뜻이에요. element에는 컴포넌트 태그(<About />)를 넣는다는 점을 기억하세요.화면 사이를 오갈 때는 <a href>가 아니라 React Router가 주는 <Link>를 씁니다. 목적지는 to 속성에 적어요.
import { Link } from "react-router-dom";
function Nav() {
return (
<nav>
{/* to 에 목적지 경로를 적어요 */}
<Link to="/">홈</Link>
<Link to="/about">소개</Link>
</nav>
);
}
<Link>도 최종적으로는 <a> 태그로 그려져요. 하지만 클릭을 React Router가 가로채 새로고침을 막고 SPA 방식으로 이동시킨다는 점이 다릅니다.사용자마다 /user/1, /user/2 … 처럼 주소의 일부가 바뀌는 경우가 많죠. 이럴 때 path에 콜론(:)을 붙인 자리표시자를 씁니다. path="/user/:id"라고 하면 :id 부분이 어떤 값이든 매칭돼요.
/user/1, /user/2처럼 일부가 값에 따라 바뀌는 경로예요. 바뀌는 자리를 :id 같은 자리표시자(placeholder)로 표시하면, 그 자리에 어떤 값이 와도 같은 화면(컴포넌트)이 매칭돼요.{/* :id 자리에 아무 값이나 들어올 수 있어요 */}
<Route path="/user/:id" element={<User />} />
그럼 컴포넌트 안에서는 그 :id 값을 어떻게 읽을까요? useParams() 훅을 씁니다. URL 파라미터들을 객체로 돌려줘요.
import { useParams } from "react-router-dom";
function User() {
// /user/3 로 접속하면 id 는 "3"
const { id } = useParams();
return <h1>{id}번 사용자 페이지</h1>;
}
버튼 클릭 같은 코드 로직 안에서 이동시키고 싶을 때는 <Link>가 아니라 useNavigate() 훅을 씁니다. 이걸 프로그래밍적 이동이라고 해요.
import { useNavigate } from "react-router-dom";
function LoginButton() {
const navigate = useNavigate();
function handleLogin() {
// ...로그인 처리 후...
navigate("/dashboard"); // 코드로 페이지 이동!
}
return <button onClick={handleLogin}>로그인</button>;
}
:id로 동적 경로를 만들고 → useParams()로 그 값을 읽고 → 코드에서 이동은 useNavigate(). 이 3종 세트가 동적 라우팅의 핵심이에요.중첩 라우트(Nested Routes)는 <Route> 안에 또 <Route>를 넣는 방식이에요. 공통 레이아웃(예: 사이드바)을 부모 라우트가 감싸고, 그 안쪽 화면만 자식 라우트로 바꿔 끼울 때 씁니다. 부모가 자식이 그려질 자리를 <Outlet />로 표시해요.
그리고 정의한 어떤 경로에도 맞지 않는 주소가 들어오면? path="*"를 쓰면 "위의 모든 경로에 안 맞는 나머지"를 잡아요. 여기에 보통 NotFound(404) 화면을 연결합니다.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<User />} />
{/* 위 어떤 것에도 안 맞으면 여기로 → 404 화면 */}
<Route path="*" element={<NotFound />} />
</Routes>
path="*"는 "못 찾은 경로 전부"를 의미하는 와일드카드예요. 사용자가 오타로 /abuot 같은 주소로 들어와도 앱이 깨지지 않고 친절한 404 화면을 보여줄 수 있어요.<a>와 달리 새로고침 없이 매끄럽게 전환.