[React] React.js 실무 강좌 31. React Router v6 완전 가이드 — 페이지 전환의 모든 것
리액트를 조금이라도 써본 사람이라면 언젠가 꼭 부딪히는 개념이 있습니다.
바로 라우팅(Routing) 입니다.
“컴포넌트는 잘 나누겠는데, 페이지 이동은 어떻게 하지?”
이 고민은 대부분의 리액트 입문자가 한 번쯤 겪습니다.
오늘은 React Router v6를 기준으로,
리액트에서 페이지 전환이 실제로 어떻게 동작하는지,
그리고 실무에서 어떤 식으로 구성하면 좋은지
제가 직접 프로젝트에서 겪은 경험을 바탕으로 정리해보려 합니다.
React Router란?
리액트는 기본적으로 “싱글 페이지 애플리케이션(SPA)” 구조입니다.
즉, 페이지가 전환돼도 브라우저가 새로고침되지 않습니다.
React Router는 이 SPA 구조 안에서
URL을 기반으로 다른 컴포넌트를 보여주는 역할을 합니다.
결국 라우터의 핵심은
“URL에 따라 어떤 컴포넌트를 렌더링할 것인가”
이 한 가지입니다.
기본 설치와 세팅
먼저 패키지를 설치합니다.
npm install react-router-dom
이제 App.js 안에서 라우터를 설정해보겠습니다.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
export default App;
이렇게 하면
/ 경로에선 Home 컴포넌트, /about에선 About 컴포넌트가 렌더링됩니다.
브라우저 주소창을 바꿔도 페이지가 새로고침되지 않고 자연스럽게 화면만 바뀌죠.
Link와 useNavigate로 페이지 이동하기
라우터를 설정했다면 이제 페이지 간 이동을 구현할 차례입니다.
이전엔 <a href="...">를 많이 썼지만,
SPA에서는 <Link> 컴포넌트를 써야 새로고침 없이 이동할 수 있습니다.
import { Link } from 'react-router-dom';
function Navbar() {
return (
<nav>
<Link to="/">홈</Link>
<Link to="/about">소개</Link>
</nav>
);
}
<Link>는 내부적으로 history.pushState()를 사용해
브라우저의 URL만 바꾸고, React가 해당 컴포넌트를 다시 렌더링합니다.
또는 JavaScript 코드에서 페이지를 이동시키고 싶다면
useNavigate() 훅을 씁니다.
import { useNavigate } from 'react-router-dom';
function LoginButton() {
const navigate = useNavigate();
const handleLogin = () => {
// 로그인 성공 시 메인 페이지로 이동
navigate('/');
};
return <button onClick={handleLogin}>로그인</button>;
}
중첩 라우트(Nested Routes) — 구조를 깔끔하게
React Router v6부터는 중첩 라우트 구조가 정말 깔끔해졌습니다.
예를 들어 /dashboard/profile이나 /dashboard/settings 같은 경로를 만들고 싶을 때
하위 라우트를 따로 렌더링할 수 있습니다.
import { Routes, Route, Outlet, Link } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h2>대시보드</h2>
<nav>
<Link to="profile">프로필</Link>
<Link to="settings">설정</Link>
</nav>
<Outlet /> {/* 하위 라우트가 이 위치에 렌더링됨 */}
</div>
);
}
function Profile() {
return <p>프로필 페이지</p>;
}
function Settings() {
return <p>설정 페이지</p>;
}
function App() {
return (
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
이 구조는 실제 프로젝트에서 매우 자주 사용됩니다.
특히 관리자 페이지나 회원 전용 페이지 같은 경우
Layout 컴포넌트 하나에 메뉴와 Outlet만 두고,
하위 페이지들을 전부 중첩 라우트로 구성하면 유지보수가 훨씬 편해집니다.
동적 라우팅(Dynamic Routing)
React Router에서는 동적으로 URL 파라미터를 받아올 수 있습니다.
예를 들어 /user/123 같은 URL에서 123이라는 아이디를 받아오는 방식입니다.
import { useParams } from 'react-router-dom';
function UserPage() {
const { id } = useParams();
return <div>{id}번 유저의 페이지입니다.</div>;
}
function App() {
return (
<Routes>
<Route path="/user/:id" element={<UserPage />} />
</Routes>
);
}
이 기능은 게시글, 상품, 사용자 상세 페이지 등
거의 모든 CRUD 구조에서 필수적으로 사용됩니다.
Private Route (로그인 필요 페이지)
실무에서는 로그인하지 않으면 접근할 수 없는 페이지가 필요합니다.
이때는 “Private Route” 패턴을 사용합니다.
import { Navigate } from 'react-router-dom';
function PrivateRoute({ children }) {
const isLogin = localStorage.getItem('token');
return isLogin ? children : <Navigate to="/login" replace />;
}
// 사용 예시
<Route path="/mypage" element={<PrivateRoute><MyPage /></PrivateRoute>} />
로그인이 안 된 사용자가 접근하려 하면
자동으로 /login으로 리다이렉트됩니다.
실제 프로젝트에서의 구조 팁
제가 최근 진행한 프로젝트에서는
라우팅 구조를 아래처럼 나눴습니다.
src/
├─ pages/
│ ├─ Home/
│ ├─ Login/
│ ├─ Dashboard/
│ │ ├─ index.jsx
│ │ ├─ Profile.jsx
│ │ ├─ Settings.jsx
│ └─ NotFound.jsx
└─ App.jsx
이렇게 폴더 단위로 라우트를 구성하면
코드를 찾기가 훨씬 쉬워집니다.
또한, 라우트 설정을 별도 파일로 분리해두면
페이지가 많아져도 관리가 용이합니다.
마무리하며
React Router는 단순히 페이지를 이동시키는 도구가 아닙니다.
앱의 구조를 정의하는 설계 도구입니다.
라우팅을 깔끔하게 잡아두면
상태 관리나 API 로직보다 오히려 유지보수가 쉬워지고,
협업할 때 “페이지 단위로 분리된 책임”이 명확해집니다.
다음 글에서는
이 라우팅 구조 위에 얹을 수 있는 기능인
Suspense와 Error Boundary — 비동기 로딩 처리와 오류 복구를 다뤄보겠습니다.
실제 서비스 환경에서 꼭 필요한 안정성 처리의 기본이 되는 개념입니다.