원티드 프리온보딩 챌린지 "Next.js 확실히 알고 레벨업 하기" 4일차 내용 정리
코드출처는 아래 링크입니다.
https://github.com/himprover/wanted-2025-2-challenge
RCC only
'use client';
interface Props {
num: number;
}
export function RCC({ num }: Props) {
const [json, setJson] = useState<JsonPlaceholder | null>(null);
const fetchHandler = async () => {
const data = await fetchJson(num);
setJson(data);
};
useEffect(() => {
fetchHandler();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (json === null) return <div>no data</div>;
return (
<div className='flex flex-col gap-2 border border-solid border-white p-2'>
<span>id: {json.id}</span>
<span>userId: {json.userId}</span>
<span>title: {json.title}</span>
<span>completed: {json.completed}</span>
</div>
);
}React와 같은 방식으로 사용되는 일반적인 RCC 코드이다.
API 요청을 하고 응답을 받기 전까지는 데이터가 null이므로 'no data' 가 표시된다.

RSC only
interface Props {
num: number;
sleepMs?: number;
}
export async function RSC({ num, sleepMs }: Props) {
const json = await fetchJson(num);
if (sleepMs) await sleep(sleepMs);
return (
<div className='flex flex-col border border-solid border-white p-2'>
<span>id: {json.id}</span>
<span>userId: {json.userId}</span>
<span>title: {json.title}</span>
<span>completed: {json.completed}</span>
</div>
);
}
http://localhost:3000/rsc?_rsc=hy9up
으로 요청하여 RSC payload가 응답으로 온다.
RSC + Suspense
Suspense를 사용하여 각각 2초, 4초 뒤에 요청하도록 한다.
import { RSC } from "@/components/RSC";
import { Suspense } from "react";
export default function Page() {
return (
<div>
<Suspense fallback={<span>loading 10</span>}>
<RSC num={10} />
</Suspense>
<Suspense fallback={<span>loading 20</span>}>
<RSC num={20} sleepMs={2000} />
</Suspense>
<Suspense fallback={<span>loading 30</span>}>
<RSC num={30} sleepMs={4000} />
</Suspense>
</div>
);
}

2초가 지났을 때 6번, 4초가 지났을 때 7번이 들어오는 것을 볼 수 있다.
RSC Payload를 보면 어떤 식으로 동작하는지 코드로 볼 수 있다.

주석 노드를 통해 반복할지, 완료했는지 표시한다.
Suspense를 사용하면 Promise 객체를 다루어 비동기적으로 뷰를 표시해주기 때문에 적극 활용하는 게 좋다. Suspense를 쓰지 않는다면 컴포넌트 내부에서 처리를 해야하는데, 이는 컴포넌트 자체가 아닌 데이터의 로딩에 따라 새로운 컴포넌트를 보여주는 식이라 선언적이지 못한 방식이기 때문.
(스켈레톤을 보여줄 때 써도 됨)
Suspense의 자매품 : Error Boundary (아래 코드 예시)
<ErrorBoundary fallback={<span>loading error</span>}>
<Suspense fallback={<span>loading</span>}>
<RSC num={10} />
</Suspense>
</ErrorBoundary>RCC + RSC 쇼핑몰 코드 예시
인터랙션 영역과 정보 영역을 분리하는 것이 중요하다.
정보 영역은 RSC로, 인터랙션 영역은 RCC로 만든다.
아래는 쇼핑몰 페이지에서 아이템 목록 부분을 만드는 부분이다.
아이템 목록에서, 아이템을 클릭했을 경우 해당하는 아이템의 상세 페이지로 이동하는 상호 작용이 들어가야 한다.
즉 RSC 안에 RCC가 들어가야 한다.
// ItemList.tsx
import { Item as ItemProps } from "./BrandItemRecommend";
import { Item } from "./Item";
import { OpenLink } from "./OpenLink";
export async function ItemList() {
const response = await fetch("https:~~~~~");
const data: ItemProps[] = await response.json();
// OpenLink라는 RCC에 children으로 Item 컴포넌트를 넘긴다.
return (
<div>
{data.length === 0 && <span>상품이 존재하지 않아요.</span>}
{data.map((item) => (
<OpenLink key={item.id} url={`https://~~~~/${item.id}`}>
<Item {...item} />
</OpenLink>
))}
</div>
);
}// OpenLink.tsx
interface Props extends PropsWithChildren {
url: string;
}
// OpenLink 컴포넌트 안에서 children을 검사하고, 유효하다면 onClick 이벤트를 가진 children을 반환한다.
export function OpenLink({ children, url }: Props) {
const router = useRouter();
const clickHandler = () => {
router.push(url);
};
if (isValidElement(children)) {
return cloneElement(children as ReactElement, { onClick: clickHandler });
}
return children;
}// Item.tsx
// 올바른 children일 경우 최상단 div에 onClick 이벤트가 달리게 된다.
export function Item(item: ItemProps) {
return (
<div>
<img src={item.imgUrl} alt="" />
<span>{item.brand}</span>
<span>{item.name}</span>
<span>{item.discountPercent}</span>
<span>{item.price}</span>
</div>
);
}
'IT > React & Next.js' 카테고리의 다른 글
| [Infra] Biome.js VSCode 세팅 (1) | 2025.08.31 |
|---|---|
| [React/TypeScript] React-Spilt-Table 오픈소스 라이브러리 (1) | 2025.04.21 |
| [Next.js] SSR에서의 Streaming HTML (0) | 2025.02.24 |
| [Next.js] Shared Component (0) | 2025.02.20 |
| [React] 커스텀 훅으로 로직 분리 (0) | 2025.02.14 |