본문으로 바로가기

아직 어떤 상황에서 어떤 방식을 선택해야 하는지 완벽히 감이 오지는 않았습니다. 여러 번 직접 적용해 보면서 경험을 쌓아야 자연스럽게 익숙해질 것 같습니다. 이번 정리에서는 각 렌더링 방식과 컴포넌트의 특징을 간단히 정리해 보고, 언제 어떤 방식을 사용할지 고민해 보겠습니다.

 

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();