React Router란?
React에서 라우팅을 구현하기 위한 표준 라이브러리
웹 애플리케이션에서 여러 페이지를 쉽게 관리하고 내비게이션을 구현할 수 있게 해줌
🛠️ 설치
npm i react-router-dom
✏️ 구현 방식
1️⃣ createBrowserRouter + <RouterProvider>
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
]);
export default function Router() {
return <RouterProvider router={router} />;
}
- React Router v6.4 이상부터 지원하는 새로운 방식
- 라우팅 설정을 자바스크립트 객체 배열로 작성
- 코드로 관리하기 때문에
- 페이지 전환,
- 로딩 처리(loader)
- 에러 처리(errorElement)
- 데이터 가져오기 (loader/action) 같은 고급 기능 쓰기 편함
- 좀 더 구조적이고 대규모 앱에 어울림
✅ createBrowserRouter ➔ RouterProvider로 연결
2️⃣ <BrowserRouter> + <Routes>
import { BrowserRouter, Routes, Route } from "react-router-dom";
export default function Router() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
);
}
- 옛날부터 쓰던 전통적인 방식
- JSX 안에서 직접 <Route>로 설정
- 간단한 앱이나 페이지가 적을 때 편하고 직관적
- 다만 페이지 많아지면 복잡해지고 유지보수가 힘들 수 있음
✅ <BrowserRouter> ➔ <Routes> ➔ <Route>로 트리 구조
✨ <Outlet />
- 자식 라우트를 렌더링하는 자리
- 부모 컴포넌트 안에 <Outlet />을 넣으면, 현재 매칭된 자식 경로의 컴포넌트가 표시됨
import React from "react";
import { Outlet } from "react-router-dom";
const Default = () => {
return (
<div>
<Header />
<Outlet />
<Footer />
</div>
);
};
export default Default;
✨ children
부모 경로 안에 들어가는 자식 페이지 목록
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Default from "./layout/Default";
import AboutPage from "./pages/AboutPage";
import MainPage from "./pages/MainPage";
import ShopPage from "./pages/ShopPage";
import BlogPage from "./pages/BlogPage";
const router = createBrowserRouter([
{
path: "/",
element: <Default />, // 부모 컴포넌트
children: [
// 부모 안에 들어가는 자식 페이지들
{
path: "",
element: <MainPage />,
},
{
path: "about",
element: <AboutPage />,
},
{
path: "shop",
element: <ShopPage />,
},
{
path: "blog",
element: <BlogPage />,
},
],
},
]);
export default function Router() {
return <RouterProvider router={router} />;
}
중첩 라우팅(Nested Routing)
페이지 안에 페이지가 들어가는 구조
Router (루트 라우터)
└─ Default (공통 레이아웃, 예: Header, Footer)
└─ Outlet (여기에 자식들이 들어옴)
└─ MainPage (메인 화면 레이아웃)
└─ Outlet (여기에 다시 하위 자식들이 들어옴)
└─ MainSub1Page (메인 페이지의 서브 내용)
{
path: "",
element: <MainPage />,
children: [
{
path: "",
element: <MainSub1Page />,
},
],
}
- 부모 라우트의 Outlet 자리에 자식 라우트가 렌더링됨
import React from "react";
import { Outlet } from "react-router-dom";
const MainPage = () => {
return (
<div>
<h2>MainPage</h2>
<div>메인 페이지입니다.</div>
<nav>
<a href="">리스트1</a>
<a href="">리스트2</a>
<a href="">리스트3</a>
</nav>
<div>아래는 outlet 영역입니다.</div>
<Outlet />
</div>
);
};
export default MainPage;
<Link> 태그
React Router에서 <a> 대신 쓰는 내부 페이지 이동용 컴포넌트
- 새로고침 없이 SPA 방식으로 부드럽게 이동 가능
- 속성으로 to="경로" 사용
import React from "react";
import { Link } from "react-router-dom";
const Header = () => {
return (
<header>
<h1>
<Link to={"/"}>로고</Link>
</h1>
<nav>
<Link to={"/about"}>회사 소개</Link>
<Link to={"/shop"}>쇼핑</Link>
<Link to={"/blog"}>블로그</Link>
</nav>
</header>
);
};
export default Header;
<NavLink> 태그
현재 경로와 일치하면 자동으로 클래스(class)를 추가해주는 Link
import { NavLink } from "react-router-dom";
<NavLink to="/about">회사 소개</NavLink>
➡️ 기본적으로 현재 URL이 to="/about"와 일치하면 class="active"가 자동으로 붙음
➡️ active 상태일 때 커스텀 스타일링 가능
<Link> | <NavLink> |
그냥 이동만 함 | 이동 + 현재 위치면 스타일 바꿔줌 |
강조 불가 | 강조 가능 (isActive) |
🪄 NavLink의 active 스타일 적용 방법
1️⃣ 전역 CSS (index.css) 사용 시
<NavLink to={"/about"}>
회사 소개
</NavLink>
/* index.css */
.active {
color: red;
}
2️⃣ CSS 모듈 (Header.module.css) 사용 시
<NavLink
className={({ isActive }) => (isActive ? styles.active : "")}
to={"/about"}
>
회사 소개
</NavLink>
/* Header.module.css */
.active {
color: red;
}
👉🏻 NavLink는 기본 active 클래스를 붙여주지만 CSS 모듈은 파일마다 클래스 이름이 바뀌기 때문에 그걸 못 알아봄
👉🏻 클래스 이름으로 스타일 적용 불가
👉🏻 구조적으로 .hd > div 이런 식으로 접근하거나,
👉🏻 isActive로 styles.active를 직접 연결해줘야 함
항목 | 전역 CSs | CSS 모듈 |
파일 이름 | index.css | Header.module.css |
클래스 이름 | .active (그대로) | .active → 내부적으로 랜덤이름 |
NavLink 설정 | 그냥 NavLink | className={({ isActive }) => (isActive ? styles.active : "")} |
관리 방식 | 사이트 전체 공유 | 파일별로 스타일 독립 |
NotFound 설정
잘못된 경로로 접근했을 때, "404 Not Found" 같은 메시지를 보여주는 안전장치
- 레이아웃 없이 NotFound만 띄우기 위해서는 라우트 최상단에서 path="*"로 따로 빼서 설정
- path: "*" 는 모든 경로를 의미
- 위에서 정의한 /, /about, /shop, /blog 에 일치하지 않는 모든 URL은 자동으로 NotFound 페이지로 이동
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Default from "./layout/Default";
import MainPage from "./pages/MainPage";
const router = createBrowserRouter([
{
path: "/",
element: <Default />,
children: [
{
path: "/sub1",
element: <MainPage />,
},
],
},
{
path: "*",
element: <NotFound />,
},
]);
export default function Router() {
return <RouterProvider router={router} />;
}
📍 페이지 컴포넌트는 Page 접미사 붙이기
- 컴포넌트 파일 많은 프로젝트에서는 Page 컴포넌트와 UI 컴포넌트를 명확히 구분하f는 게 중요
- 폴더나 파일 이름만 봐도 바로 "아 얘는 페이지네" 파악 가능
- 파일 찾기도 편하고, 유지보수도 쉬워짐
src/
pages/
AboutPage.jsx
LoginPage.jsx
ProductsPage.jsx
components/
Header.jsx
Footer.jsx
Card.jsx
Button.jsx
페이지 컴포넌트 ➔ Page 붙이기 (AboutPage.jsx)
일반 UI 컴포넌트 ➔ 그냥 이름 (Button.jsx, Card.jsx)
📁 실무용 폴더 구조
페이지 단위로 폴더를 나누고, 관련 파일을 모아 관리하는 방식을 많이 쓴다 !
src/
├── pages/
│ ├── AboutPage/
│ │ ├── AboutPage.jsx // 페이지 컴포넌트
│ │ ├── AboutPage.module.css // 스타일 파일
│ │ ├── useAboutData.js // 전용 훅 (데이터 가져오기 등)
│ │ ├── AboutService.js // API 관련 함수
│ │ └── components/ // About 전용 하위 컴포넌트
│ │ ├── AboutCard.jsx
│ │ └── AboutCard.module.css
│ │
- 관련 파일을 한 곳에 모아서 관리 ➔ 파일 찾기 쉬움
- 기능별로 나누니까 확장성이 좋음 ➔ 기능 추가할 때 깔끔
- 리팩토링할 때 폴더 단위로 이동/삭제/추가가 쉬움
- 팀원끼리 컨벤션 통일 ➔ 협업하기 편함
Swipper
슬라이더(캐러셀)를 쉽게 만들 수 있는 라이브러리
🛠️설치
npm install swiper
https://swiperjs.com/get-started
Swiper - The Most Modern Mobile Touch Slider
Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.
swiperjs.com
🔥 React에서 사용법 (feat. autoplay)
- pages/MainPage.jsx
import React from "react";
import { Outlet } from "react-router-dom";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, Pagination, Navigation } from "swiper/modules";
const MainPage = () => {
return (
<div>
<h2>MainPage</h2>
<div>
<Swiper
slidesPerView={3} // 한번에 보이는 슬라이드 수
spaceBetween={30} // 슬라이드 사이 간격(px)
centeredSlides={true} // 활성화된 슬라이드 가운데로 정렬
autoplay={{
delay: 2500, // 지정된 ms마다 자동으로 다음 슬라이드 이동
disableOnInteraction: false, // 사용자가 슬라이드를 터치하거나 드래그했을 때 autoplay 동작 제어
}}
pagination={{ // 페이지네이션 추가
clickable: true,
}}
navigation={true} // 좌우 화살표 버튼 추가
modules={[Autoplay, Pagination, Navigation]}
className="silder" // 커스텀 스타일링을 위해 클래스명 부여
>
<SwiperSlide>
<div>
<img src="/img/Img_bg1.jpg" alt="" />
</div>
</SwiperSlide>
<SwiperSlide>Slide 2</SwiperSlide>
<SwiperSlide>Slide 3</SwiperSlide>
<SwiperSlide>Slide 4</SwiperSlide>
<SwiperSlide>Slide 5</SwiperSlide>
<SwiperSlide>Slide 6</SwiperSlide>
<SwiperSlide>Slide 7</SwiperSlide>
<SwiperSlide>Slide 8</SwiperSlide>
<SwiperSlide>Slide 9</SwiperSlide>
</Swiper>
</div>
<nav>
<a href="">리스트1</a>
<a href="">리스트3</a>
<a href="">리스트3</a>
</nav>
<div>아래는 outlet 영역입니다.</div>
<Outlet />
</div>
);
};
export default MainPage;
- Main.jsx
- Swiper 전역 스타일 import (한 번만 import해서, 전역에서 슬라이더 쓸 수 있도록 함)
// Import Swiper styles
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
https://swiperjs.com/demos#autoplay
Swiper Demos
Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.
swiperjs.com
📚 onSwiper + useRef로 Swiper 직접 조작하는 방법
onSwiper로 Swiper 인스턴스를 useRef에 저장하고, 직접 만든 버튼으로 슬라이드를 제어
useRef() | 빈 그릇 만들기 (null로 시작) |
onSwiper | Swiper가 완성될 때 인스턴스를 ref에 담아줌 |
버튼 조작 | slidePrev(), slideNext() 등 메서드 호출로 직접 이동 |
ex)
import { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
export default function CustomSwiper() {
const swiperRef = useRef(null); // Swiper 인스턴스 담을 ref 생성
return (
<div>
<Swiper
onSwiper={(swiper) => (swiperRef.current = swiper)} // Swiper가 준비되면 ref에 담기
slidesPerView={1}
spaceBetween={10}
>
<SwiperSlide>Slide 1</SwiperSlide>
<SwiperSlide>Slide 2</SwiperSlide>
<SwiperSlide>Slide 3</SwiperSlide>
</Swiper>
{/* 직접 만든 커스텀 버튼 */}
<button onClick={() => swiperRef.current.slidePrev()}>Prev</button>
<button onClick={() => swiperRef.current.slideNext()}>Next</button>
</div>
);
}
json-server
- 가짜 백엔드를 빠르게 만들어주는 도구
- db.json 파일 하나로 GET, POST, PUT, DELETE 요청을 테스트할 수 있음
- 서버가 준비되지 않았을 때, 프론트엔드 개발을 빠르게 시작할 수 있게 도와줌
- 복잡한 백엔드 코드 없이도 API처럼 사용 가능
🛠️ 설치
npm install -D json-server concurrently
명령어 | 의미 |
-D | 개발용(DevDependencies) 으로 설치 (배포할 때 제외) |
json-server | 가짜 API 서버를 만들어주는 툴 |
concurrently | 여러 개의 스크립트를 동시에 실행할 수 있게 해주는 툴 (Vite 개발 서버와 json-server를 동시에 실행할 때 필요) |
🪄 package.json 수정
"scripts": {
"watch:json-server": "json-server db.json --port 3000", //db.json 파일을 읽어서 3000번 포트에서 서버 실행
"watch:vite": "vite", // Vite 개발 서버 실행
"watch:react": "react-scripts start", // Create React App 기반 React 서버 실행
"dev": "concurrently npm:watch:*", // 위의 watch:* 명령어들을 동시에 실행 (vite + json-server 둘 다 켜기)
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
✍🏻 db.json 작성
프로젝트의 root(최상단) 경로에 작성
{
"products": [
{
"id": 1,
"title": "14K/18K 허니 벌집2 헥사곤 심플 반지",
"img": "image1.jpg",
"price": 185000,
"category": "new",
"discount": 5
},
{
"id": 2,
"title": "14K/18K 마닐드 하트 드롭 큐빅 원터치 귀걸이",
"img": "image2.jpg",
"price": 126250,
"category": "top",
"discount": 31
},
{
"id": 3,
"title": "14k 데일리 심플 볼귀걸이 시리즈",
"img": "image3.jpg",
"price": 30000,
"category": "top",
"discount": 45
},
// ... 생략
]
}
↪️ 실행
npm run dev
📁 React public 폴더
- 리액트가 빌드될 때 변환되지 않고 그대로 배포되는 파일을 저장하는 공간
- Webpack이나 Vite 같은 번들러가 수정하거나 최적화하지 않고, 파일을 그대로 복사해서 결과물에 포함시킴
"있는 그대로 서버로 올라가는 파일들"을 넣는 곳
📍 특징
- 변환(트랜스파일) : ❌ 없음
- 압축(최적화) : ❌ 없음
- 절대 경로로 접근 : ⭕ 가능
✅ 접근 방법
public 폴더에 hello.png가 있다고 치면
<img src="/hello.png" alt="Hello" />
- /hello.png처럼 절대 경로로 접근
- import 없이 바로 사용 가능
- 상대 경로(../) 필요 없음
☁️ 사용되는 경우
- favicon.ico: 브라우저 탭에 표시할 파비콘
- robots.txt, sitemap.xml: 검색 엔진 최적화(SEO) 파일
- 외부에서 직접 접근해야 하는 이미지, 동영상 같은 정적 파일
- 프로젝트 빌드 크기를 줄이기 위해 대용량 파일을 분리할 때
'LG 유플러스 유레카 SW > React' 카테고리의 다른 글
[#54] React 쇼핑몰 - 성능 향상 + Hero 페이지 제작 (2) | 2025.04.15 |
---|---|
[#53] React 쇼핑몰 - 헤더 반응형으로 만들어보기 (1) | 2025.04.14 |
[#51] 가위바위보 게임 (feat. React) (0) | 2025.04.10 |
[#50] React - useState (5) | 2025.04.09 |
[#49] React 설정 (1) | 2025.04.08 |