src/
├─ main.jsx
├─ App.jsx
├─ pages/
│ ├─ Home.jsx
│ ├─ Products.jsx
│ └─ ProductDetail.jsx
└─ data/
└─ products.js
Home: 첫 화면Products: 목록ProductDetail: /products/:id 상세products.js: 더미 데이터/헬퍼앱의 최상단에 BrowserRouter를 한 번만 감싼다.
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
경로와 컴포넌트를 1:1로 이어준다.
여기서는 / (홈), /products (목록), /products/:id (상세) 3가지만 둔다.
// src/App.jsx
import { Routes, Route, Link } from 'react-router-dom'
import Home from './pages/Home.jsx'
import Products from './pages/Products.jsx'
import ProductDetail from './pages/ProductDetail.jsx'
export default function App() {
const navStyle = { display: 'flex', gap: 12, padding: 16, borderBottom: '1px solid #eee' }
return (
<>
<nav style={navStyle}>
<Link to="/">Home</Link>
<Link to="/products">Products</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="products" element={<Products />} />
{/* /products/:id → 상세 */}
<Route path="products/:id" element={<ProductDetail />} />
{/* 404 */}
<Route path="*" element={<div style={{ padding: 24 }}>페이지를 찾을 수 없습니다.</div>} />
</Routes>
</>
)
}
실무에선 API를 부르겠지만, 흐름만 보려면 배열로 충분하다.
// src/data/products.js
export const products = [
{ id: 'p-100', name: 'Air Runner', price: 129000, description: '가벼운 데일리 러닝화' },
{ id: 'p-101', name: 'Trail Master', price: 159000, description: '접지력 좋은 트레일화' },
{ id: 'p-102', name: 'City Walk', price: 99000, description: '일상용 워킹화' }
]
export function getProductById(id) {
return products.find(p => p.id === id) || null
}
// src/pages/Home.jsx
export default function Home() {
return (
<section style={{ padding: 24 }}>
<h1>홈</h1>
<p>리액트 라우터(v6+) 기본 사용법과 상품 상세 라우팅을 연습합니다.</p>
</section>
)
}
Link로 /products/:id에 진입한다.
// src/pages/Products.jsx
import { Link } from 'react-router-dom'
import { products } from '../data/products.js'
export default function Products() {
return (
<section style={{ padding: 24 }}>
<h1>상품 목록</h1>
<ul style={{ marginTop: 16, display: 'grid', gap: 12 }}>
{products.map(p => (
<li key={p.id} style={{ border: '1px solid #eee', borderRadius: 8, padding: 16 }}>
<h3 style={{ margin: '0 0 8px' }}>{p.name}</h3>
<p style={{ margin: '0 0 8px', color: '#666' }}>{p.description}</p>
<strong>{p.price.toLocaleString()}원</strong>
<div style={{ marginTop: 12 }}>
<Link to={`/products/${p.id}`}>자세히 보기</Link>
</div>
</li>
))}
</ul>
</section>
)
}
핵심은 useParams()로 URL 파라미터를 읽는 것.
없으면 404 대체 UI를 보여주고, 있으면 해당 상품을 렌더링한다.
// src/pages/ProductDetail.jsx
import { useParams, useNavigate } from 'react-router-dom'
import { getProductById } from '../data/products.js'
export default function ProductDetail() {
const { id } = useParams()
const navigate = useNavigate()
const product = id ? getProductById(id) : null
if (!product) {
return (
<section style={{ padding: 24 }}>
<h1>상품을 찾을 수 없습니다.</h1>
<p style={{ color: '#666' }}>잘못된 주소이거나 삭제된 상품일 수 있습니다.</p>
<button onClick={() => navigate(-1)} style={{ marginTop: 12 }}>뒤로 가기</button>
</section>
)
}
return (
<section style={{ padding: 24 }}>
<button onClick={() => navigate(-1)} style={{ marginBottom: 12 }}>← 목록으로</button>
<h1>{product.name}</h1>
<p style={{ color: '#666', margin: '8px 0 16px' }}>{product.description}</p>
<strong style={{ fontSize: 18 }}>{product.price.toLocaleString()}원</strong>
<div style={{ marginTop: 16 }}>
<button onClick={() => alert('장바구니 담기(예시)')}>장바구니</button>
</div>
</section>
)
}
BrowserRouter<Routes><Route path="..." element={...} /></Routes>useParams()로 :id 읽기Link로 화면 전환, useNavigate()로 뒤로가기/특정 경로 이동path="*"로 대체 UI중요한 건 “복잡하게 시작하지 않기”. 지금 구조에 쿼리스트링(useSearchParams), 보호 라우트(로그인 후 접근), 코드 스플리팅(React.lazy/Suspense) 등을 천천히 얹어가면 된다.
기본기가 익숙해지면 createBrowserRouter(데이터 라우터)로 로더/액션/에러 경로를 통합하는 방식도 자연스럽게 이해된다.