아직 어떤 상황에서 어떤 방식을 선택해야 하는지 완벽히 감이 오지는 않았습니다. 여러 번 직접 적용해 보면서 경험을 쌓아야 자연스럽게 익숙해질 것 같습니다. 이번 정리에서는 각 렌더링 방식과 컴포넌트의 특징을 간단히 정리해 보고, 언제 어떤 방식을 사용할지 고민해 보겠습니다.
Next.js는 React의 서버 컴포넌트(Server Components) 를 기본적으로 지원하며, 필요에 따라 클라이언트 컴포넌트(Client Components) 를 사용할 수 있습니다.
클라이언트 컴포넌트 (Client Components)
클라이언트 컴포넌트는 브라우저에서 실행되며, 상태(state), 이벤트(event), 훅(hooks) 과 같은 기능을 사용할 수 있습니다.
특징
- useState, useEffect 같은 훅을 사용할 수 있음
- 사용자와의 상호작용이 필요한 UI (예: 버튼 클릭, 입력 폼)에서 사용
- 렌더링 속도가 중요할 때 적합
클라이언트 컴포넌트로 만들려면 use client를 파일 상단에 추가합니다.
"use client"; // 클라이언트 컴포넌트 선언
import { useState, useEffect } from "react";
const ClientComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("클라이언트에서 실행됨!");
}, []);
return <button onClick={() => setCount(count + 1)}>클릭: {count}</button>;
};
export default ClientComponent;
서버 컴포넌트 (Server Components)
서버 컴포넌트는 Next.js에서 기본 동작하며, 서버에서 실행되기 때문에 보안이 필요한 데이터 처리 및 API 호출을 수행할 때 유용합니다.
특징
- 기본적으로 Next.js는 모든 컴포넌트를 서버에서 렌더링
- 비동기 함수 사용 가능 (async/await)
- React 상태 관리 불가능 (useState, useEffect 사용 불가)
- 데이터베이스 쿼리, API 호출 등을 서버에서 실행
const ServerComponent = async () => {
const res = await fetch("http://localhost:4000/products");
const data = await res.json();
return (
<div>
{data.map((product: any) => (
<p key={product.id}>{product.title}</p>
))}
</div>
);
};
export default ServerComponent;
- async 함수를 직접 사용 가능
- 클라이언트에서 실행되지 않음 (네트워크 탭에서 확인 불가)
Next.js의 렌더링 방식 (SSR, SSG, CSR, ISR)
Next.js는 페이지를 어떻게 렌더링할지에 따라 4가지 방식(SSR, SSG, CSR, ISR)을 제공합니다.
| 렌더링 방식 | 동작 방식 | 예시 |
| SSR (서버 사이드 렌더링) | 요청마다 서버에서 데이터를 가져와 렌더링 | 로그인된 사용자마다 다른 데이터를 보여줄 때 |
| SSG (정적 사이트 생성) | 빌드 시점에 미리 HTML을 생성 | 블로그, 제품 리스트 등 자주 변경되지 않는 페이지 |
| CSR (클라이언트 사이드 렌더링) | 브라우저에서 실행되며 API 요청으로 데이터를 가져옴 | 검색 결과 페이지, 대시보드 |
| ISR (점진적 정적 재생성) | 특정 시간마다 정적 페이지를 다시 생성 | 자주 업데이트되는 뉴스, 상품 리스트 |
1) SSR (서버 사이드 렌더링)
사용자가 요청할 때마다 새롭게 데이터를 가져와서 페이지를 렌더링하는 방식입니다.
- 장점: 최신 데이터를 가져올 수 있음
- 단점: 매 요청마다 서버에서 새로 렌더링하므로 성능 부담이 큼
const res = await fetch("http://localhost:4000/products", {
cache: "no-store", // SSR을 위해 캐싱 방지
});
const data = await res.json();
cache: "no-store" 옵션을 추가하면 매 요청마다 새로운 데이터를 가져옴
2) SSG (정적 사이트 생성)
빌드 시점에 HTML을 미리 생성하여, 이후 요청 시 빠르게 정적 파일을 제공하는 방식입니다.
- 장점: 속도가 빠르고 서버 부담이 없음
- 단점: 데이터 변경이 자주 일어나면 적절하지 않음
const res = await fetch("http://localhost:4000/products", {
cache: "force-cache", // 기본값 (SSG)
});
const data = await res.json();
cache: "force-cache" 옵션을 사용하면 빌드 시점에 데이터를 가져와 정적 페이지로 제공
3) CSR (클라이언트 사이드 렌더링)
서버에서 빈 HTML을 먼저 보내고, 브라우저에서 JavaScript로 데이터를 가져와 렌더링하는 방식입니다.
- 장점: 초기 로딩이 빠르고 사용자 경험이 좋음
- 단점: 검색 엔진(SEO)에 불리할 수 있음
"use client"; // 클라이언트 컴포넌트
import { useEffect, useState } from "react";
const ProductList = () => {
const [data, setData] = useState<Product[]>([]);
useEffect(() => {
fetch("http://localhost:4000/products")
.then((res) => res.json())
.then(setData);
}, []);
return (
<div>
{data.map((product) => (
<p key={product.id}>{product.title}</p>
))}
</div>
);
};
export default ProductList;
useEffect를 사용하여 클라이언트에서 API를 호출 후 데이터를 렌더링
4) ISR (점진적 정적 재생성)
정적 페이지를 일정 시간마다 다시 생성하여, SSG와 SSR의 장점을 결합한 방식입니다.
- 장점: 성능 최적화 + 최신 데이터 반영
- 단점: 일정 시간 동안은 캐시된 데이터를 볼 수도 있음
const res = await fetch("http://localhost:4000/products", {
next: { revalidate: 10 }, // 10초마다 데이터 갱신
});
const data = await res.json();