8. React Router
React Router
공식사이트1. 라우터 이해
1.1. 라우터란
네트워크에서 데이터를 목적지까지 연결하는 장치다. 리액트에서는 URL 주소에 따라 다른 컴포넌트를 보여주는 역할을 한다.
1.2. 라우팅이란
데이터의 최단·최적 경로를 찾는 과정이다.

1.3. MPA란
MPA(Multi Page Application) — 여러 HTML 페이지를 각각 만들어 연결하는 방식이다. 페이지 이동 시 전체 페이지가 새로 로드된다.
1.4. SPA란
SPA(Single Page Application) — HTML 파일 하나에서 컴포넌트만 교체하는 방식이다. 변경된 부분만 업데이트되므로 빠르다.

핵심 개념 4가지:
| 개념 | 역할 |
|---|---|
loader | 페이지 진입 전 데이터를 가져오는 함수 |
useLoaderData | loader가 반환한 데이터를 컴포넌트에서 꺼내는 훅 |
action | 폼 제출(POST)을 처리하는 함수 |
useActionData | action이 반환한 결과를 컴포넌트에서 꺼내는 훅 |
2. 설치
개발 서버를 닫고 아래 명령어로 설치한다.
1npm install react-router@7설치 완료 후 서버를 다시 실행한다.
1npm run dev3. 기본 라우팅
3.1. 컴포넌트 작성
먼저 페이지로 사용할 컴포넌트 파일을 만든다. src 아래에 pages 폴더를 만들고 두 파일을 생성한다.
1src/2└── pages/3 ├── Home.jsx4 └── Products.jsxsrc/pages/Home.jsx파일을 만들고 아래 코드를 작성한다.
1const Home = () => {2 return <h1>Home</h1>;3};4export default Home;| 줄 | 설명 |
|---|---|
| 1 | Home 컴포넌트를 화살표 함수로 정의한다 |
| 2 | h1 태그를 반환한다 |
| 4 | 다른 파일에서 사용할 수 있도록 내보낸다 |
src/pages/Products.jsx파일을 만들고 아래 코드를 작성한다.
1const Products = () => {2 return <h1>Products</h1>;3};4export default Products;.js와 .jsx는 기능이 동일하다. 개발자가 컴포넌트 파일임을 쉽게 구분하기 위해 .jsx 확장자를 사용한다.
3.2. createBrowserRouter — 라우트 정의
createBrowserRouter — 라우터를 생성하는 함수다. 경로(path)와 컴포넌트(element)를 배열로 등록한다.
정보
라우터 연결 3단계:
1단계: 라우터 생성 — createBrowserRouter
2단계: 라우터와 앱 연결 — RouterProvider
3단계: 경로와 컴포넌트 매핑 — path, element
src/main.jsx에 아래 코드를 작성한다.
1import React from 'react';2import ReactDOM from 'react-dom/client';3import { createBrowserRouter, RouterProvider } from 'react-router';4import Home from './pages/Home';5import Products from './pages/Products';6
7const router = createBrowserRouter([8 {9 path: '/',10 element: <Home />,11 },12 {13 path: '/products',14 element: <Products />,15 },16]);17
18ReactDOM.createRoot(document.getElementById('root')).render(19 <RouterProvider router={router} />20);| 줄 | 설명 |
|---|---|
| 3 | react-router에서 두 함수를 가져온다 |
| 7 | createBrowserRouter에 배열을 전달해 라우터를 만든다 |
| 9 | path — 브라우저 주소창의 경로 |
| 10 | element — 해당 경로에서 렌더링할 컴포넌트. 반드시 JSX(<Home />) 형태로 작성한다 |
| 18 | RouterProvider로 라우터 설정을 앱 전체에 제공한다 |
- 브라우저 주소창에
/products를 입력하면 새로고침 없이 Products 컴포넌트로 바뀐다.
3.2.1. element vs Component
라우트에 컴포넌트를 지정하는 방법은 두 가지다.
| 속성 | 값 | 사용 시기 |
|---|---|---|
element | JSX 인스턴스 (<Home />) | props를 직접 전달할 때 |
Component | 컴포넌트 자체 (Home) | React Router가 직접 렌더링할 때 |
1// element: 반드시 JSX를 넣어야 한다2{ path: '/', element: <Home /> } // 올바름3{ path: '/', element: Home } // 잘못됨 — 함수 참조 (렌더링 안 됨)4
5// Component: 컴포넌트를 그대로 넣는다6{ path: '/', Component: Home } // 올바름7{ path: '/', Component: <Home /> } // 잘못됨 — JSX를 넣으면 안 됨3.3. Link — 링크 만들기
<a href=""> 대신 <Link to="">를 사용한다.
주의
<a> 태그는 클릭 시 전체 페이지가 새로 로드된다. SPA의 장점을 잃게 된다.
<Link>는 새로고침 없이 화면만 바꾼다.
Home.jsx를 아래와 같이 수정한다.
1import { Link } from 'react-router';2
3const Home = () => {4 return (5 <div>6 <h1>Home</h1>7 <Link to="/products">Products 페이지로 이동</Link>8 </div>9 );10};11export default Home;| 줄 | 설명 |
|---|---|
| 1 | react-router에서 Link를 가져온다 |
| 7 | to="/products" — 이동할 경로를 to 속성으로 작성한다 |
- 링크를 클릭해 화면 새로고침 없이 이동하는지 확인한다.
3.4. children + Outlet — 공통 레이아웃
네비게이션처럼 모든 페이지에서 항상 보여야 하는 UI는 레이아웃 컴포넌트에 넣고, children으로 자식 라우트를 묶는다.
<Outlet />은 자식 라우트 컴포넌트가 렌더링될 자리를 표시한다.
1단계: RootLayout 컴포넌트 만들기
1import { Outlet, Link } from 'react-router';2
3const RootLayout = () => {4 return (5 <>6 <nav>7 <Link to="/">Home</Link>{' '}8 <Link to="/products">Products</Link>9 </nav>10 <Outlet />11 </>12 );13};14export default RootLayout;| 줄 | 설명 |
|---|---|
| 1 | Outlet과 Link를 함께 가져온다 |
| 6~8 | 모든 페이지에 공통으로 보이는 네비게이션 |
| 10 | 자식 컴포넌트(Home, Products 등)가 이 위치에 렌더링된다 |
2단계: 라우터에 children으로 연결
1import RootLayout from './pages/RootLayout';2
3const router = createBrowserRouter([4 {5 path: '/',6 element: <RootLayout />,7 children: [8 { path: '', element: <Home /> },9 { path: 'products', element: <Products /> },10 ],11 },12]);| 줄 | 설명 |
|---|---|
| 6 | RootLayout이 모든 페이지의 틀이 된다 |
| 7~10 | children — 자식 라우트 배열. 부모 레이아웃 안에서 교체된다 |
이제 Home과 Products 어디서든 네비게이션이 항상 보인다.
3.5. errorElement — 에러 페이지
없는 주소로 접근하거나 오류가 발생했을 때 보여줄 컴포넌트를 errorElement에 등록한다.
1단계: Error 컴포넌트 만들기
1import { Link } from 'react-router';2
3const ErrorPage = () => {4 return (5 <div>6 <h1>404 Error</h1>7 <p>페이지를 찾을 수 없습니다.</p>8 <Link to="/">홈으로 돌아가기</Link>9 </div>10 );11};12export default ErrorPage;2단계: 라우터에 등록
1import ErrorPage from './pages/Error';2
3const router = createBrowserRouter([4 {5 path: '/',6 element: <RootLayout />,7 errorElement: <ErrorPage />,8 children: [9 { path: '', element: <Home /> },10 { path: 'products', element: <Products /> },11 ],12 },13]);| 줄 | 설명 |
|---|---|
| 7 | errorElement — 존재하지 않는 경로 접근 시 ErrorPage를 보여준다 |
주소창에 /없는경로를 입력하면 Error 컴포넌트가 나타난다.
3.6. useNavigate — 코드로 페이지 이동
<Link>는 클릭으로 이동하는 용도고, useNavigate는 버튼 클릭이나 함수 실행 후 코드에서 페이지를 이동할 때 사용한다.
참고
훅(Hook) — 리액트 함수 컴포넌트에서 기능을 사용할 수 있게 해주는 함수다. 이름이 use로 시작한다.
1import { Link, useNavigate } from 'react-router';2
3const Home = () => {4 const navigate = useNavigate();5
6 const handleClick = () => {7 navigate('/products');8 };9
10 return (11 <div>12 <h1>Home</h1>13 <button onClick={handleClick}>Products 보러 가기</button>14 <Link to="/products">링크로 이동</Link>15 </div>16 );17};18export default Home;| 줄 | 설명 |
|---|---|
| 1 | useNavigate를 추가로 가져온다 |
| 4 | useNavigate()가 반환하는 이동 함수를 navigate에 저장한다 |
| 6~8 | 버튼 클릭 시 /products로 이동하는 함수 |
| 12 | onClick — 버튼 클릭 시 handleClick을 실행한다 |
4. 동적 라우팅
4.1. useParams — URL 파라미터 받기
상품 목록에서 상품을 클릭하면 상품마다 다른 상세 페이지로 이동해야 한다. 상품이 100개면 라우트를 100개 만들 수 없으니, :id처럼 변수를 경로에 넣는다.
시나리오:
1/products → 상품 목록 페이지2/products/1 → 1번 상품 상세 페이지3/products/2 → 2번 상품 상세 페이지4/products/apple → apple 상품 상세 페이지정보
useParams — use(사용하다) + Params(파라미터, 매개변수). 경로의 동적 값을 읽는 훅이다.
1단계: ProductDetail 컴포넌트 만들기
1import { useParams } from 'react-router';2
3const ProductDetail = () => {4 console.log(useParams()); // { id: '1' } 형태로 출력된다5
6 const { id } = useParams();7
8 return (9 <div>10 <h1>ProductDetail</h1>11 <p>상품 ID: {id}</p>12 </div>13 );14};15export default ProductDetail;| 줄 | 설명 |
|---|---|
| 1 | useParams를 가져온다 |
| 4 | 콘솔에서 파라미터 객체를 확인한다. 예: \{ id: '1' \} |
| 6 | 구조 분해 할당으로 id 값을 꺼낸다 |
| 11 | 화면에 URL의 id 값을 출력한다 |
2단계: 라우터에 동적 경로 등록
:id 뒤의 값은 무엇이든 ProductDetail로 연결된다.
1import ProductDetail from './pages/ProductDetail';2
3const router = createBrowserRouter([4 {5 path: '/',6 element: <RootLayout />,7 errorElement: <ErrorPage />,8 children: [9 { path: '', element: <Home /> },10 { path: 'products', element: <Products /> },11 { path: 'products/:id', element: <ProductDetail /> },12 ],13 },14]);| 줄 | 설명 |
|---|---|
| 11 | products/:id — : 이후의 이름이 변수가 된다. 어떤 값이든 매칭된다 |
브라우저 주소창에 /products/5 또는 /products/apple을 입력하면 상세 컴포넌트가 렌더링된다.
4.2. 동적 링크 — map으로 링크 자동 생성
상품 데이터 배열을 map으로 반복하면서 각 상품 ID를 Link 경로에 넣는다. 템플릿 리터럴(백틱)로 경로를 동적으로 만든다.
1import { Link } from 'react-router';2
3const PRODUCTS = [4 { id: '1', title: '상품1' },5 { id: '2', title: '상품2' },6 { id: '3', title: '상품3' },7];8
9const Products = () => {10 return (11 <>12 <h1>Products</h1>13 <ul>14 {PRODUCTS.map((item) => (15 <li key={item.id}>16 <Link to={`/products/${item.id}`}>{item.title}</Link>17 </li>18 ))}19 </ul>20 </>21 );22};23export default Products;| 줄 | 설명 |
|---|---|
| 3~7 | PRODUCTS — 상품 데이터 배열. 실제 서비스에서는 서버에서 받아온다 |
| 14~18 | map() — 배열을 순회하여 각 상품을 <li>로 렌더링한다 |
| 16 | 템플릿 리터럴로 /products/1, /products/2 형태의 경로를 동적으로 만든다 |
4.3. 상대경로 + 뒤로가기
Link의 to 속성에서 /로 시작하지 않으면 상대 경로가 된다. ..은 한 단계 위로 이동하는 경로다.
1import { useParams, Link } from 'react-router';2
3const ProductDetail = () => {4 const { id } = useParams();5
6 return (7 <div>8 <h1>ProductDetail</h1>9 <p>상품 ID: {id}</p>10 <Link to=".." relative="path">← 목록으로 돌아가기</Link>11 </div>12 );13};14export default ProductDetail;| 줄 | 설명 |
|---|---|
| 10 | to=".." — 한 단계 위의 경로로 이동한다 |
| 10 | relative="path" — URL 경로 기준으로 이동한다. 없으면 라우트 계층 기준으로 이동해 홈으로 갈 수 있다 |
요약
relative="path"를 사용하지 않으면 /products/1에서 ..이 /products가 아닌 /(홈)으로 이동할 수 있다.
4.4. index 라우트 — 기본 페이지 지정
path: '' 대신 index: true를 사용하면 부모 경로(/)에 접속했을 때 보여줄 기본 컴포넌트를 지정할 수 있다.
1const router = createBrowserRouter([2 {3 path: '/',4 element: <RootLayout />,5 errorElement: <ErrorPage />,6 children: [7 { index: true, element: <Home /> },8 { path: 'products', element: <Products /> },9 { path: 'products/:id', element: <ProductDetail /> },10 ],11 },12]);| 줄 | 설명 |
|---|---|
| 7 | index: true — path: '' 대신 사용한다. 부모 경로(/) 접근 시 Home을 표시한다 |
5. useSearchParams — 쿼리스트링 다루기
5.1. useSearchParams란?
URL의 ? 뒤에 붙는 **쿼리스트링(query string)**을 읽고 변경하는 훅이다.
1/products?category=shoes&sort=price2 └──────────────────────┘3 쿼리스트링 부분| 용어 | 예시 | 설명 |
|---|---|---|
| 쿼리스트링 | ?category=shoes&sort=price | URL 뒤에 붙는 키=값 쌍 |
searchParams | URLSearchParams 객체 | 쿼리스트링을 읽는 객체 |
setSearchParams | 함수 | 쿼리스트링을 변경하는 함수 |
5.2. 기본 사용법 — 쿼리스트링 읽기
1import { useSearchParams } from 'react-router';2
3const Products = () => {4 const [searchParams, setSearchParams] = useSearchParams();5
6 // /products?category=shoes 일 때7 const category = searchParams.get('category'); // 'shoes'8 const sort = searchParams.get('sort'); // null (없으면 null)9
10 return (11 <div>12 <h1>Products</h1>13 <p>카테고리: {category ?? '전체'}</p>14 <p>정렬: {sort ?? '기본'}</p>15 </div>16 );17};| 줄 | 설명 |
|---|---|
| 4 | useSearchParams()는 배열을 반환한다. 첫 번째는 읽기, 두 번째는 변경 함수다 |
| 7 | searchParams.get('키') — 쿼리스트링에서 값을 읽는다. 없으면 null을 반환한다 |
| 13 | ?? — null이거나 undefined면 오른쪽 값을 사용한다 |
5.3. 쿼리스트링 변경하기
setSearchParams에 객체를 전달하면 URL의 쿼리스트링이 바뀐다. 페이지 새로고침 없이 URL만 업데이트된다.
1import { useSearchParams } from 'react-router';2
3const Products = () => {4 const [searchParams, setSearchParams] = useSearchParams();5 const category = searchParams.get('category');6
7 return (8 <div>9 <h1>Products</h1>10 <p>현재 카테고리: {category ?? '전체'}</p>11
12 <button onClick={() => setSearchParams({ category: 'shoes' })}>13 신발14 </button>15 <button onClick={() => setSearchParams({ category: 'bags' })}>16 가방17 </button>18 <button onClick={() => setSearchParams({})}>19 전체 보기20 </button>21 </div>22 );23};| 줄 | 설명 |
|---|---|
| 12 | 버튼 클릭 시 URL이 /products?category=shoes로 바뀐다 |
| 18 | 빈 객체를 전달하면 쿼리스트링을 모두 제거한다 |
5.4. 여러 파라미터 동시 사용
기존 파라미터를 유지하면서 하나만 변경하려면 함수 형태로 전달한다.
1const Products = () => {2 const [searchParams, setSearchParams] = useSearchParams();3
4 const category = searchParams.get('category');5 const sort = searchParams.get('sort');6
7 const changeSort = (value) => {8 setSearchParams((prev) => {9 prev.set('sort', value);10 return prev;11 });12 };13
14 return (15 <div>16 <h1>Products</h1>17 <p>카테고리: {category ?? '전체'} / 정렬: {sort ?? '기본'}</p>18
19 <button onClick={() => setSearchParams({ category: 'shoes', sort: sort || '' })}>20 신발21 </button>22 <button onClick={() => changeSort('price')}>가격순</button>23 <button onClick={() => changeSort('name')}>이름순</button>24 </div>25 );26};| 줄 | 설명 |
|---|---|
| 8~11 | prev — 현재 쿼리스트링 객체. set으로 특정 키만 변경하고 반환한다 |
5.5. 실전 예제 — 검색 + 필터
1import { useSearchParams, Link } from 'react-router';2
3const PRODUCTS = [4 { id: '1', title: '운동화', category: 'shoes' },5 { id: '2', title: '구두', category: 'shoes' },6 { id: '3', title: '백팩', category: 'bags' },7 { id: '4', title: '토트백', category: 'bags' },8 { id: '5', title: '볼캡', category: 'hats' },9];10
11const Products = () => {12 const [searchParams, setSearchParams] = useSearchParams();13
14 const search = searchParams.get('search') || '';15 const category = searchParams.get('category') || '';16
17 const filtered = PRODUCTS.filter((item) => {18 const matchSearch = item.title.includes(search);19 const matchCategory = category ? item.category === category : true;20 return matchSearch && matchCategory;21 });22
23 return (24 <div>25 <h1>Products</h1>26
27 <input28 type="text"29 placeholder="검색..."30 value={search}31 onChange={(e) =>32 setSearchParams((prev) => {33 if (e.target.value) prev.set('search', e.target.value);34 else prev.delete('search');35 return prev;36 })37 }38 />39
40 <div>41 <button onClick={() => setSearchParams((prev) => { prev.delete('category'); return prev; })}>전체</button>42 <button onClick={() => setSearchParams((prev) => { prev.set('category', 'shoes'); return prev; })}>신발</button>43 <button onClick={() => setSearchParams((prev) => { prev.set('category', 'bags'); return prev; })}>가방</button>44 <button onClick={() => setSearchParams((prev) => { prev.set('category', 'hats'); return prev; })}>모자</button>45 </div>46
47 <ul>48 {filtered.map((item) => (49 <li key={item.id}>50 <Link to={`/products/${item.id}`}>{item.title}</Link>51 </li>52 ))}53 </ul>54 {filtered.length === 0 && <p>결과가 없습니다.</p>}55 </div>56 );57};58export default Products;URL 변화 예시:
1/products → 전체 상품2/products?category=shoes → 신발만 표시3/products?search=운동 → "운동" 검색 결과4/products?search=운동&category=shoes → 신발 중 "운동" 검색5.6. useParams vs useSearchParams
| 구분 | useParams | useSearchParams |
|---|---|---|
| URL 위치 | 경로 안 (/products/:id) | ? 뒤 (?key=value) |
| 용도 | 특정 리소스 식별 | 필터, 검색, 정렬 |
| 값 변경 | 라우트 이동 필요 | setSearchParams로 즉시 변경 |
| 예시 URL | /products/3 | /products?category=shoes |
| 필수 여부 | 경로에 포함되어야 함 | 선택적 (없어도 됨) |
6. 전체 코드
1import React from 'react';2import ReactDOM from 'react-dom/client';3import { createBrowserRouter, RouterProvider } from 'react-router';4import RootLayout from './pages/RootLayout';5import Home from './pages/Home';6import Products from './pages/Products';7import ProductDetail from './pages/ProductDetail';8import ErrorPage from './pages/Error';9
10const router = createBrowserRouter([11 {12 path: '/',13 element: <RootLayout />,14 errorElement: <ErrorPage />,15 children: [16 { index: true, element: <Home /> },17 { path: 'products', element: <Products /> },18 { path: 'products/:id', element: <ProductDetail /> },19 ],20 },21]);22
23ReactDOM.createRoot(document.getElementById('root')).render(24 <RouterProvider router={router} />25);1import { Outlet, Link } from 'react-router';2
3const RootLayout = () => {4 return (5 <>6 <nav>7 <Link to="/">Home</Link>{' '}8 <Link to="/products">Products</Link>9 </nav>10 <Outlet />11 </>12 );13};14export default RootLayout;1import { useNavigate } from 'react-router';2
3const Home = () => {4 const navigate = useNavigate();5 return (6 <div>7 <h1>Home</h1>8 <button onClick={() => navigate('/products')}>Products 보러 가기</button>9 </div>10 );11};12export default Home;1import { Link } from 'react-router';2
3const PRODUCTS = [4 { id: '1', title: '상품1' },5 { id: '2', title: '상품2' },6 { id: '3', title: '상품3' },7];8
9const Products = () => {10 return (11 <>12 <h1>Products</h1>13 <ul>14 {PRODUCTS.map((item) => (15 <li key={item.id}>16 <Link to={`/products/${item.id}`}>{item.title}</Link>17 </li>18 ))}19 </ul>20 </>21 );22};23export default Products;1import { useParams, Link } from 'react-router';2
3const ProductDetail = () => {4 const { id } = useParams();5 return (6 <div>7 <h1>ProductDetail</h1>8 <p>상품 ID: {id}</p>9 <Link to=".." relative="path">← 목록으로 돌아가기</Link>10 </div>11 );12};13export default ProductDetail;1import { Link } from 'react-router';2
3const ErrorPage = () => {4 return (5 <div>6 <h1>404 Error</h1>7 <p>페이지를 찾을 수 없습니다.</p>8 <Link to="/">홈으로 돌아가기</Link>9 </div>10 );11};12export default ErrorPage;페이지 흐름:
1/ (Home)2 └─ 버튼 클릭 → /products (상품 목록)3 ├─ 상품1 클릭 → /products/1 (상세)4 ├─ 상품2 클릭 → /products/2 (상세)5 └─ 상품3 클릭 → /products/3 (상세)6 └─ 뒤로가기 → /products (목록)