라우팅
React 앱은 하나의 컴포넌트 트리를 하나의 HTML 문서에 렌더링합니다. 실제 사이트에는 홈페이지, 소개 페이지, 상세 페이지 등이 각각의 URL을 가지고 있습니다. 라우팅은 이 두 가지를 연결하는 방식입니다. URL이 어떤 컴포넌트를 렌더링할지 결정하고, 앱 내에서 클릭하면 URL이 업데이트됩니다. React Router는 대부분의 React 앱이 사용하는 라이브러리이며, 이 강좌에서도 밴 렌탈 앱인 VanLife를 구축하며 전체 섹션을 할애합니다. 이 장에서는 핵심 요소를 다루며, 중첩 라우트와 라우트 매개변수에서 이를 바탕으로 확장합니다.
클라이언트 사이드 라우팅: MPA vs SPA
전통적인 다중 페이지 애플리케이션(MPA)에서는 매번 네비게이션이 왕복 요청입니다. 브라우저가 서버에 /about을 요청하고, 서버가 HTML 페이지를 조립해 전송하며, 브라우저는 현재 페이지를 버리고 새 페이지를 로드합니다. 이 전체 페이지 바뀜은 클릭할 때마다 눈에 띄는 깜박임으로 보입니다.
단일 페이지 애플리케이션(SPA)은 하나의 문서를 한 번만 로드합니다. 이름이 좀 오해를 살 수 있는데, 사용자 입장에서는 여전히 많은 페이지를 가진 사이트일 수 있습니다. 단일이란 문서가 하나라는 뜻입니다. 첫 로드 이후로는 React 앱 자체가 각 URL에서 무엇을 표시할지 결정합니다. /about으로 이동하면 앱 내에서 컴포넌트만 바뀌며, 새로운 문서 요청도 없고 깜박임도 없습니다. 뷰가 새로운 데이터가 필요하면 앱이 백그라운드에서 JSON을 가져와 제자리에서 업데이트하는데, 이는 데이터 가져오기에서 다루는 패턴입니다.
**클라이언트 사이드 라우팅**은 이를 가능하게 하는 메커니즘입니다. 라이브러리가 URL을 감시하고 네비게이션을 가로채서 브라우저의 리로드 대신 일치하는 컴포넌트를 렌더링합니다. 문서를 살려둔다는 것은 실질적인 이점이 있습니다. 전체 페이지 로드는 모든 React 상태를 삭제하지만, 하나의 문서 안에 머무르면 매번 네비게이션할 때 상태가 유지됩니다.
버전 노트
이 강좌는 React Router 6을 가르칩니다. 버전 7은 React Router와 Remix를 통합했고 패키지를 통합했으므로 react-router가 현재 import 경로입니다. react-router-dom은 v7에서 재내보내기로 남아 있으며, v6과 이 강좌가 사용하는 패키지입니다. 이것이 이 핸드북의 모든 예제가 여기서 import하는 이유이고, v8은 이를 삭제했으므로 새로 설치하면 이 컴포넌트들을 react-router에서 import합니다.
React Router는 선언적, 데이터, 프레임워크 세 가지 모드를 문서화합니다. 선언적은 이 장에서 다루는 BrowserRouter와 Route 컴포넌트입니다. 데이터는 createBrowserRouter를 로더 및 액션과 함께 사용하고, 프레임워크는 타입이 지정된 라우트 모듈, 코드 분할 및 서버 렌더링을 추가하는 Vite 플러그인입니다. 이 장의 컴포넌트 및 훅 이름은 그 버전들 전체에서 변경되지 않습니다.
BrowserRouter, Routes, Route
세 개의 컴포넌트가 기본 설정을 정의합니다. BrowserRouter는 앱을 감싸고 라우팅을 활성화하며, Routes는 라우트 정의를 보관하고, 각 Route는 하나의 경로를 하나의 요소에 매핑합니다.
import { BrowserRouter, Routes, Route } from 'react-router-dom'
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
)
}내부적으로 BrowserRouter는 context 공급자입니다. 앱을 이것으로 감싸면 아래의 모든 컴포넌트가 라우터의 도구에 접근할 수 있으므로 맨 위에 위치합니다. 일부 코드베이스는 import 시 이름을 바꿉니다 (import { BrowserRouter as Router }). 전체 이름이 길기 때문입니다.
각 Route는 두 개의 props를 받습니다. path는 도메인 뒤의 URL 부분입니다. 홈페이지는 /, 소개 페이지는 /about입니다. element는 URL이 해당 경로와 일치할 때 렌더링할 JSX이며, 실제 요소로 작성합니다. Home 대신 <Home />입니다. URL이 /about일 때 React Router는 Routes 컴포넌트 위치에 <About />를 렌더링합니다. Routes 바깥의 모든 것은 모든 페이지에서 렌더링됩니다.
매칭되는 라우트가 없는 경로를 방문하면 아무것도 렌더링되지 않습니다. 이 간극은 아래의 캐치올 라우트로 해결됩니다.
Link: 페이지 로드 없이 네비게이션하기
페이지 간을 이동하는 HTML 방식은 앵커 태그인데, SPA에서는 정확히 잘못되었습니다. <a href="/about">를 클릭하면 전체 페이지 로드가 트리거되어 실행 중인 앱과 모든 상태를 버립니다. React Router의 답은 Link 컴포넌트입니다.
import { Link } from 'react-router-dom'
function Header() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
)
}Link는 href 대신 to prop을 받으며, 영어처럼 읽힙니다. 다음 라우트로의 링크라는 뜻입니다. 브라우저에서는 여전히 실제 앵커 태그를 렌더링하므로 스크린 리더, 우클릭 메뉴, CSS 요소 선택자 모두 일반 <a>를 봅니다. 차이는 클릭 핸들러에 있습니다. React Router가 클릭을 가로채고 URL을 업데이트하며 렌더링된 컴포넌트를 바꾸는데, 이 모든 것이 문서를 리로드하지 않습니다. 10으로 설정된 카운터는 페이지 간 이동 시 10으로 유지됩니다. React Router 앱 내에서 내부 네비게이션은 항상 Link를 통해야 합니다. 일반 앵커는 외부 URL을 위한 것입니다.
NavLink와 활성 링크 스타일링
네비게이션 바는 보통 현재 페이지를 강조합니다. NavLink는 정확히 이 목적을 위해 존재합니다. Link처럼 동작하지만, className과 style props는 단순한 값 대신 함수를 받을 수 있습니다. React Router는 링크의 라우트가 현재 URL과 일치할 때 true인 isActive 부울 값을 포함하는 객체와 함께 그 함수를 호출합니다. 이것은 render props에서 다루는 render props 패턴을 children 이외의 prop에 적용한 것입니다.
import { NavLink } from 'react-router-dom'
function Header() {
return (
<nav>
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active-link' : ''}
>
About
</NavLink>
</nav>
)
}함수가 반환하는 것이 클래스 이름이 되므로 active-link 클래스는 /about이 현재 라우트일 때만 적용되고, CSS가 나머지를 처리합니다. style prop은 인라인 스타일 객체로 같은 방식으로 작동합니다.
const activeStyles = { fontWeight: 'bold', textDecoration: 'underline' }
<NavLink
to="/about"
style={({ isActive }) => isActive ? activeStyles : null}
>
About
</NavLink>클래스 이름 또는 인라인 스타일은 팀 규약에 따른 선택입니다. 둘 다 완전히 지원되므로 프로젝트에서 이미 사용 중인 방식을 따릅니다.
캐치올 404 라우트
사용자는 존재하지 않는 경로에 도착합니다. 오래된 링크, 오타, 다른 사람의 잘못된 참조입니다. 폴백이 없으면 React Router는 아무것도 렌더링하지 않습니다. 수정 방법은 캐치올 라우트(때로는 splat 라우트라고 함)이며, 경로는 하나의 별표입니다.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>path="*"는 다른 라우트가 주장하지 않는 모든 것과 일치하며, CSS의 범용 선택자처럼 작동합니다. 일반적인 NotFound 컴포넌트는 짧은 메시지와 홈페이지로의 Link를 함께 보여줍니다. 리스트의 순서는 중요하지 않습니다. React Router 6은 모든 라우트를 URL과 비교하고 최고의 일치를 선택하므로 캐치올은 더 구체적인 것이 없을 때만 작동합니다. 마지막에 놓는 것은 규약과 가독성의 문제이지 필수는 아닙니다.
앱을 BrowserRouter로 감싸고, 페이지를 Routes 내에 Route 컴포넌트로 나열하며, 각 path는 자신의 element를 렌더링합니다.
앵커 태그 대신 Link를 사용하면 클릭이 앱을 유지하고, path="*" 라우트를 추가하면 URL을 잘못 입력한 방문자에게 빈 화면 대신 친화적인 페이지를 봅니다.
다음: 중첩 라우트와 레이아웃. 라우트가 공유 크롬을 얻고 라우트 안의 라우트가 있습니다.

