React Router v7 소개
React 애플리케이션은 싱글 페이지 애플리케이션 기반 --> 페이지가 한 개
URL 경로에 따라 동적인 애플리케이션 화면을 보여주기 위해서는 React Router 사용
ㄴ 라이브러리이면서 프레임워크의 성격을 가짐
React Router Official Documentation
4,048,871,903 Downloads on npm
reactrouter.com
React Router 모드
- Declarative
- 가장 기본이 되는 모드
- 배우기 쉽고 적용하기 쉬움
- Data
- 선언적 모드에 데이터 로딩, 패칭 기능이 추가된 형태
- 페이지 전환 시 필요한 데이터를 자동으로 로드하거나 에러 로딩 상태를 처리해주는 기능 탑재
- Framework
- 데이터 모드에 폼 처리 기능, 기본 UI가 추가된 형태
React Router v7 설치
- 패키지 설치
- npm i react-router 명령어를 사용하여 다운로드
- 폴더 구조 설계
- src/routes/index.tsx
- src/routes/pages/About.tsx
- src/routes/pages/Home.tsx
// Main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import Router from "./routes";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<Router />
</StrictMode>,
);
// src/routes/index.tsx
import { createBrowserRouter, RouterProvider } from "react-router";
import Home from "./pages/Home";
import About from "./pages/About";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "/about",
element: <About />,
},
]);
// Router로 내부 함수 이름 변경
export default function Router() {
return (
<>
<RouterProvider router={router} />
</>
);
}
- createBrowserRouter([...])
- 어떤 주소에 대해 어떤 컴포넌트를 보여줄지 객체 형태로 매핑하여 라우터 인스턴스를 생성하는 함수
- path: "/"
- 도메인 뒤에 아무것도 없는 메인 홈 경로일 때
- element: <Home />
- <Home /> 컴포넌트 호출
- <RouterProvider router={router} />
- 위에서 정의한 router 설계도를 리액트 앱 전체에 주입
- 실제로 라우팅 시스템이 작동하도록 provider 역할을 하는 컴포넌트
// src/routes/pages/Home.tsx
export default function Home() {
return (
<>
<h1>Home Component</h1>
</>
);
}
// src/routes/pages/About.tsx
export default function About() {
return (
<>
<h1>About Component</h1>
</>
);
}
React Router v7 기본
route 객체 : createBrowserRouter() 내부 배열 각각의 객체
router instance 객체 : route 객체가 묶여 하나의 네비게이션 동작을 수행하게 하는 전체적인 객체
element 속성에 JSX 문법을 직접적으로 작성하는 것도 가능
{
path: "/post",
element: <h1>Post Page</h1>,
}
컴포넌트를 전달할 때에는 element가 아닌 Component 속성을 사용하는 방법도 존재
{
path: "/",
Component: Home,
}
React Router v7 중첩 라우트와 인덱스 라우트
route를 설정할 때 children 속성을 사용하면 route를 중첩시켜서 부모-자식 관계를 정의 가능
children 속성을 사용한 부모 컴포넌트에 <Outlet /> 컴포넌트를 작성해주어야 함
<Outlet /> 위치에 자식 컴포넌트가 렌더링됨
// src/routes/index.tsx
import { createBrowserRouter, RouterProvider } from "react-router";
import DashboardHome from "./pages/dashboard/DashboardHome";
import Dashboard from "./pages/dashboard/Dashboard";
const router = createBrowserRouter([
{
path: "/dashboard",
Component: Dashboard,
children: [
{
path: "", // /dashboard 주소 --> Dashboard 컴포넌트와 DashboardHome 컴포넌트를 모두 보여줌
Component: DashboardHome,
},
],
},
]);
export default function Router() {
return (
<>
<RouterProvider router={router} />
</>
);
}
^ <Outlet /> 컴포넌트 추가
// src/routes/pages/dashboard/Dashboard.tsx
import { Outlet } from "react-router";
export default function Dashboard() {
return (
<>
<h1>Dashboard Component</h1>
<Outlet />
</>
);
}
// src/routes/pages/dashboard/DashboardHome.tsx
export default function DashboardHome() {
return (
<>
<h1>DashboardHome Component</h1>
</>
);
}
자식 컴포넌트 주소 지정 방법
1. path 속성 사용
{
path: "/dashboard",
Component: Dashboard,
children: [
{
path: "", // /dashboard 주소 --> Dashboard 컴포넌트와 DashboardHome 컴포넌트를 모두 보여줌
Component: DashboardHome,
},
],
},
2. index 속성 사용
{
path: "/dashboard",
Component: Dashboard,
children: [
{
index: true, // /dashboard 주소 --> Dashboard 컴포넌트와 DashboardHome 컴포넌트를 모두 보여줌
Component: DashboardHome,
},
],
},
* 중첩된 라우트의 path에는 /를 붙이지 X
React Router v7 레이아웃 라우트
레이아웃 라우트 : 중첩 라우트 기법과 동일하지만, 부모 라우트에 path 속성을 지정하지 않음
ㄴ 주소창의 URL 경로를 변경하지 않고, 여러 독립된 페이지에서 공통 UI를 감싸주는 껍데기 역할을 하기 때문
레이아웃으로 사용할 공통 뼈대 컴포넌트는 src/pages/layouts/ 하위에 생성

// src/routes/pages/layouts/Default.tsx
import { Outlet } from "react-router";
export default function Default() {
return (
<>
<header>
<h1>Header</h1>
</header>
<Outlet />
<footer>
<h1>Footer</h1>
</footer>
</>
);
}
// src/routes/index.tsx
import { createBrowserRouter, RouterProvider } from "react-router";
import Home from "./pages/Home";
import About from "./pages/About";
import Dashboard from "./pages/dashboard/Dashboard";
import DashboardHome from "./pages/dashboard/DashboardHome";
import DashboardSetting from "./pages/dashboard/DashboardSetting";
import Default from "./pages/layouts/Default";
const router = createBrowserRouter([
{
// default 설정
Component: Default,
children: [
{
path: "/",
Component: Home,
},
{
path: "/about",
Component: About,
},
],
},
]);
export default function Router() {
return (
<>
<RouterProvider router={router} />
</>
);
}
React Router v7 라우트 프리픽스
라우트 프리픽스 : element, Component 속성 없이 오직 path 속성만 가진 부모 라우트 아래에 children을 중첩시키는 방식
ㄴ 화면을 띄워주는 공통 껍데기(레이아웃)는 없지만, 하위 자식 라우트들의 URL 시작 주소를 한 곳에서 묶어 관리하고 싶을 사용
{
path: "/group",
// Component: DashboardLayout,
children: [
{
path: "dashboard",
Component: Dashboard,
children: [
{
index: true,
Component: DashboardHome,
}, {
path: "setting",
Component: DashboardSetting,
children: [
{
path: "custom",
element: <h1>DashboardSetting</h1>
},
],
},
],
},
],
}
React Router v7 동적 세그먼트
동적 세그먼트 : URL 경로 중 고정되지 않고 사용자의 데이터(id 등)에 따라 동적으로 변하는 주소 영역을 뜻함
ㄴ 라우트 설정 시 주소창에 콜론 기호를 붙여 변수 형태로 선언
useParams 훅 : 동적 세그먼트로 지정된 URL 값을 컴포넌트 내부에서 읽어올 때 사용하는 리액트 라우터 전용 훅
// src/routes/index.tsx
{
path: "/post/:id",
Component: Post,
}
// src/routes/pages/post/Post.tsx
import { useParams } from "react-router";
export default function Post() {
const { id } = useParams();
return (
<>
<h1>Post: {id}</h1>
</>
);
}
^ 구조 분해 할당
// src/routes/index.tsx
{
path: "/post/:id/detail/:detail",
Component: PostDetail,
}
// src/routes/pages/post/PostDetail.tsx
import { useParams } from "react-router";
export default function PostDetail() {
const { id, detail } = useParams();
return (
<>
<h1>
PostDetail: {id} / {detail}
</h1>
</>
);
}
^ 구조 분해 할당
* 동적 세그먼트는 각기 다른 값이어야 함
ex) /post/:id/detail/:id (X)
React Router v7 옵셔널 세그먼트
옵셔널 세그먼트 : 라우트 설정 시 URL 경로의 특정 구역(세그먼트)을 선택 사항으로 지정하여 생략할 수 있도록 만드는 기법
ex) path: "/post/:id?" --> id를 생략해도 페이지 접속 가능
React Router v7 스플랫
스플랫 라우트 : 라우터에 명시적으로 정의되지 않은 모든 예외 경로를 잡아내기 위해 사용하는 라우트 지정 기법
== Catch-all 라우트 또는 Wildcard 라우트
ㄴ path 속성 값의 맨 끝에 * 기호를 붙여 선언
ex) path: "*"
// src/routes/index.tsx
{
path: "/post/*",
Component: Post,
}
// src/routes/pages/post/Post.tsx
import { useParams } from "react-router";
export default function Post() {
const { "*": splat } = useParams();
console.log(splat);
return (
<>
<h1>Post</h1>
</>
);
}
* 라우터의 맨 밑바닥에 배치하여 404 페이지 커스텀도 가능
React Router v7 NavLink, Link
Link 컴포넌트 : <a> 태그 역할을 대체하여 페이지 간 이동을 구현하는 컴포넌트
<Link to="/">Home</Link>
NavLink 컴포넌트 : Link의 기능을 확장하여, 현재 URL 경로의 일치 여부에 따라 다양한 스타일 상태를 동적으로 제공하는 특수 컴포넌트
ㄴ 현재 주소창의 URL과 to 속성에 지정한 주소가 일치할 경우, 해당 태그에 active 클래스를 자동으로 부여
속성이나 children에 콜백 함수 형식을 사용하여 isActive, isPending, isTransitioning 속성 값을 받아 컴포넌트 스타일 커스텀 가능
<NavLink to="/">Home</NavLink>
<NavLink
to="/about"
className={({ isActive, isPending, isTransitioning }) =>
[
isActive ? "isActive" : "",
isPending ? "isPending" : "",
isTransitioning ? "isTransitioning" : ""
].join(" ")
}
>
About
</NavLink>
React Router v7 <Form>
Form 컴포넌트 : <form> 태그를 대체하여 폼 전송을처리하는 컴포넌트
ㄴ 브라우저의 기본 동작인 화면 새로고침을 가로채서 차단하고, 비동기 통신을 수행함
action 속성을 활용하여 폼이 제출되었을 때 데이터를 전송할 대상 URL 경로를 지정
// src/routes/pages/auth/Login.tsx
import { Form, useActionData } from "react-router";
export default function Login() {
const data = useActionData();
return (
<>
{/* action에 지정된 주소로 페이지 전환 */}
<Form action="/" method="post">
<input type="email" name="email" autoComplete="off" />
<input type="password" name="pw" />
<button type="submit">로그인</button>
</Form>
<p>{data && data.message}</p>
</>
);
}
React Router v7 navigate
useNavigate 훅 : 컴포넌트 내부의 자바스크립트 코드 영역에서 프로그래밍 방식으로 주소를 강제 이동시킬 때 사용하는 훅
navigate("/")
^ 일반 이동 : 원하는 절대/상대 경로 주소를 문자열로 던져서 이동
ㄴ 주소창 대신 인자에 음수나 양수 숫자를 넣으면 브라우저 방문 기록을 기반으로 이동함
ex) navigate(-1) --> 이전 페이지로 뒤로 가
// src/routes/pages/auth/Login.tsx
import { Form, useActionData, useNavigate } from "react-router";
export default function Login() {
const data = useActionData();
const navigate = useNavigate();
return (
<>
{/* <form></form> */}
<Form method="post">
<input type="email" name="email" autoComplete="off" />
<input type="password" name="pw" />
<button type="submit">로그인</button>
</Form>
<p>{data && data.message}</p>
<button onClick={() => navigate("/")}>메인으로 이동</button>
</>
);
}
React Router v7 loader 속성으로 데이터 패칭
useEffect 기반 데이터 패칭 (전통적인 방식)
- 렌더링 흐름
- 컴포넌트 마운트 --> useEffect 실행 --> 비동기 데이터 패칭 --> 상태 업데이트 --> 리렌더링
- UX
- 주소가 바뀌면 화면 즉시 전환
- 서버에서 데이터를 받기 전까진 빈 배열이나 스피너가 먼저 노출되는 현상 발생
loader 기반 데이터 패칭 (최신 React Router v7 방식)
- 렌더리 흐름
- 주소 클릭 --> 라우터가 백엔드 데이터 패칭 (loader) 완료할 때까지 대기 --> 데이터 로드 완료 --> 화면 전환 및 컴포넌트 렌더링
- UX
- 주소를 클릭해도 서버에서 데이터를 모두 가져오기 전까지는 화면 전환 X
// src/routes/index.tsx
import { createBrowserRouter, RouterProvider } from "react-router";
import Home from "./pages/Home";
import Post from "./pages/post/Post";
import Default from "./layouts/Default";
import PostLoader from "./pages/post/PostLoader";
import { axiosInstance } from "../api/axios";
const router = createBrowserRouter([
{
Component: Default,
children: [
{
path: "/",
Component: Home,
},
{
path: "/post",
Component: Post,
},
{
path: "/post-loader",
Component: PostLoader,
loader: async () => {
const { data } = await axiosInstance.get("/posts");
return data;
},
},
],
},
]);
export default function Router() {
return (
<>
<RouterProvider router={router} />
</>
);
}
// src/routes/pages/post/Post.tsx
import { useEffect, useState } from "react";
import { axiosInstance } from "../../../api/axios";
export default function Post() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
const { data } = await axiosInstance.get("/posts");
setPosts(data);
};
fetchPosts();
}, []);
return (
<>
<h1>Post Component</h1>
<pre>{JSON.stringify(posts, null, 2)}</pre>
</>
);
}
// src/routes/pages/post/PostLoader.tsx
import { useLoaderData } from "react-router";
export default function PostLoader() {
const posts = useLoaderData();
return (
<>
<h1>PostLoader Component</h1>
<pre>{JSON.stringify(posts, null, 2)}</pre>
</>
);
}
React Router v7 loader + loading
loader와 로딩 처리 기법 : lodaer 함수가 서버에서 데이터를 완전히 패칭할 때까지 홤녀이 멈추는 현상을 방지하기 위해 로딩 컴포넌트를 지정하는 방식
데이터 패칭이 완료될 때까지는 해당 라우트에 설정된 hydrateFallback 속성에 지정된 컴포넌트 내용이 화면에 먼저 노출됨
// src/routes/index.tsx
{
path: "/post-loader",
Component: PostLoader,
loader: async () => {
const { data } = await axiosInstance.get("/posts");
return data;
},
HydrateFallback: PostLoading,
},
// src/routes/pages/post/PostLoading.tsx
export default function PostLoading() {
return (
<>
<p>Loading...</p>
</>
);
}
React Router v7 loading + error
errorElement 속성 : 데이터 패칭이나 컴포넌트 렌더링 도중 에러가 발생했을 때, 화면 전체가 멈추는 것을 방지하기 위해 예외 처리용 커스텀 에러 화면을 지정하는 속성
useRouteError 훅 : erroElement로 지정된 컴포넌트 내부에서, 실제로 발생한 구체적인 에러 객체(에러 메세지, 상태 코드 등)를 읽어올 때 사용하는 훅
// src/routes/index.tsx
{
path: "/post-loader",
Component: PostLoader,
loader: async () => {
const { data } = await axiosInstance.get("/posts");
return data;
},
HydrateFallback: PostLoading,
errorElement: <PostError />,
},
// src/routes/pages/post/PostError.tsx
import { isRouteErrorResponse, useRouteError } from "react-router";
export default function PostError() {
const error = useRouteError();
// isRouteErrorResponse : 라우트에서 response 관련된 에러인지 아닌지 판단하는 내장 함수
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status}</h1>
<h1>{error.data.message}</h1>
</div>
);
} else if (error instanceof Error) {
return (
<div>
<h1>알 수 없는 에러</h1>
<h1>{error.message}</h1>
</div>
);
} else {
return (
<>
<h1>에러 발생</h1>
</>
);
}
}'React > 타입스크립트로 배우는 리액트(React.js) : 기초부터 최신 기술까지' 카테고리의 다른 글
| 섹션 17. 데이터 통신 심화 (0) | 2026.06.22 |
|---|---|
| 섹션 15. 전역 상태 관리 - Zustand (0) | 2026.06.12 |
| 섹션 16. 데이터 통신 (1) | 2026.06.11 |
| 섹션 13. 전역 상태 관리 - Context API (0) | 2026.06.09 |
| 섹션 12. 사이드 이펙트와 컴포넌트 최적화 (0) | 2026.05.22 |