티스토리 뷰
1. 개요
노마드코더의 Maker 마스터클래스 강의 중, 특정 product의 overview와 review를 보여주는 페이지를 만드는 영상을 본 후, layout이라는 꽤나 괜찮은 기능이 있어서 기록하려 한다.
// app/routes.ts
...prefix("/:productId", [
index("features/products/pages/product-redirect-page.tsx"),
route("/overview", "features/products/pages/product-overview-page.tsx"),
...prefix("/reviews", [
index("features/products/pages/product-reviews-page.tsx"),
route("/new", "features/products/pages/new-product-review-page.tsx"),
]),
]),
routes.ts를 보면 알 수 있듯, /:productId/overview에서는 상품의 개요를, /:product/reviews에서는 상품의 리뷰를 보여준다.
그리고 위 이미지는 상품의 overview 페이지와 reviews 페이지의 뷰인데, 노란색으로 감싼 부분을 reviews에서도 동일하게 보여주고 싶다. 이를 위해 그냥 중복된 코드를 작성하거나, 한 페이지에서 overview를 보여줄지, reviews를 보여줄지를 state 등을 이용해서 구현하는 등의 방법도 있지만, React Router의 layout을 통해 이를 간단히 구현할 수 있었다.
2. React Router의 layout이란
React Rotuer의 layout은 여러 페이지(혹은 경로)가 공통으로 공유하는 UI 구조(레이아웃)을 한 곳에서 관리할 수 있게 도와주는 기능이다. 이를 통해 특정 경로 아래의 모든 하위 페이지에 자동으로 레이아웃이 적용되게 할 수 있다.
3. layout 적용
3-1. 레이아웃 컴포넌트 작성
우선, 레이아웃 컴포넌트를 만들어야 한다. 이 상황에서 공유하고 싶은 레이아웃은 위 이미지의 헤더 부분이다. 그래서 저 부분의 코드를 overview 컴포넌트에서 복사한 후, 아래처럼 붙여넣기했다.
// app/features/products/layouts/product-overview-layout.tsx
import { NavLink, Outlet } from "react-router";
import { ChevronUpIcon, StarIcon } from "lucide-react";
import { Button, buttonVariants } from "~/common/components/ui/button";
import { cn } from "~/lib/utils";
export default function ProductOverviewLayout() {
return (
<div className="space-y-10">
<div className="flex justify-between">
<div className="flex gap-10">
<div className="size-40 rounded-xl shadow-xl bg-primary/50"></div>
<div>
<h1 className="text-5xl font-bold">Product Name</h1>
<p className="text-2xl font-light">Product Description</p>
<div className="mt-5 flex item-center gap-2">
<div className="flex text-yellow-400">
{Array.from({ length: 5 }).map((_, i) => (
<StarIcon className="size-4" fill="currentColor" key={i} />
))}
</div>
<span className="text-muted-foreground">100 reviews</span>
</div>
</div>
</div>
<div className="flex gap-5">
<Button
variant={"secondary"}
size="lg"
className="text-lg h-14 px-10"
>
Visit Website
</Button>
<Button size="lg" className="text-lg h-14 px-10">
<ChevronUpIcon className="size-4" />
Upvote (100)
</Button>
</div>
</div>
<div className="flex gap-2.5">
<NavLink
className={({ isActive }) =>
cn(
buttonVariants({ variant: "outline" }),
isActive && "bg-accent text-foreground "
)
}
to={`/products/1/overview`}
>
Overview
</NavLink>
<NavLink
className={({ isActive }) =>
cn(
buttonVariants({ variant: "outline" }),
isActive && "bg-accent text-foreground "
)
}
to={`/products/1/reviews`}
>
Reviews
</NavLink>
</div>
<div>
<Outlet /> {/* Outlet을 통해 하위 라우트가 렌더링된다 */}
</div>
</div>
);
}
코드의 끝부분을 보면 Outlet이라는 것이 있는데, 이 Outlet에 통해 하위 라우트의 컴포넌트들이 렌더링되게 된다. 추가로, 기존의 overview 컴포넌트에서 위 헤더 부분을 제거해야 헤더가 중복되지 않게 보인다.
3-2. routes.ts에서 layout 사용
// app/routes.ts
route("/promote", "features/products/pages/promote-page.tsx"), // <- 여기까지는 동일
...prefix("/:productId", [
index("features/products/pages/product-redirect-page.tsx"),
layout("features/products/layouts/product-overview-layout.tsx", [
route("/overview", "features/products/pages/product-overview-page.tsx"),
...prefix("/reviews", [
index("features/products/pages/product-reviews-page.tsx"),
route("/new", "features/products/pages/new-product-review-page.tsx"),
]),
]),
]),
]),
] satisfies RouteConfig;
기존의 route("/overview", ...) + ...prefix("/reviews", ...) 부분에 layout을 적용한다. layout의 각 파라미터는 다음과 같다.
- 첫 번째 파라미터: 레이아웃 컴포넌트의 파일 경로
- 두 번째 파라미터: 해당 레이아웃의 Outlet에 렌더링할 자식 routes 배열
3-3. 렌더링 결과
/products/1/overview에 진입하면 상단의 헤더 UI(레이아웃) + overview 컴포넌트가 동시에 보인다.
/products/1/reviews에 진입해도 상단의 헤더 UI(레이아웃) + overview 컴포넌트가 동시에 보인다.
4. 결론
React Router의 layout은 여러 라우트에서 공통으로 사용하는 UI(탭, 메뉴, 사이드바 등)를 한 곳에서 관리할 수 있게 해주는 강력한 도구로, 중복 UI를 제거할 수 있고, 유지보수가 보다 용이하며, 중첩 라우트와 잘 어울린다는 점이 정말 좋다고 할 수 있다.
참고 문서
'기록' 카테고리의 다른 글
Prettier와 @trivago/prettier-plugin-sort-imports로 import 자동 정렬하기 (0) | 2025.07.19 |
---|---|
@react-router/dev의 자동 타입 추론 (0) | 2025.06.22 |
C++에서 여러 줄 문자열 입력받기 (getline 사용) (1) | 2025.06.07 |
React Router의 <Link> 클릭 시 페이지 이동이 안 되던 현상을 임시로 해결 (0) | 2025.06.01 |
HTTP 헤더, CORS 에러 관련 간단하게 정리 (0) | 2025.05.11 |
- Total
- Today
- Yesterday
- 비트마스킹
- react router
- themoviedb
- 자바스크립트
- C++
- react
- 스택
- typescript
- Next.js
- 프로그래머스
- SQL
- 넥스트js
- Redux
- 구현
- CSS
- 리액트
- 햄버거버튼
- 코드스테이츠
- 완전탐색
- BFS
- 백준
- NextJS
- 다이나믹프로그래밍
- 브루트포스
- 동적계획법
- 타입스크립트
- aws
- 알고리즘
- 순열
- 카카오맵
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |