Frontend/Next.js

SSR과 Server Component(RSC) 이해하기

JTB 2025. 11. 3. 18:29

Next.js와 React 18+ 환경에서 SSR(Server-Side Rendering)Server Component(RSC) 의 차이를 정확히 이해하는 것은 성능 최적화와 클라이언트 번들 최소화 측면에서 매우 중요하다. 많은 개발자가 혼동하는 영역이므로, 핵심 개념과 예제 코드를 중심으로 깔끔하게 정리한다.

 

1. SSR (Server-Side Rendering)

1. 정의

SSR은 클라이언트의 요청 시점마다 서버에서 HTML을 생성해 전달하는 방식이다. Next.js의 App Router 환경에서는 getServerSideProps 대신 fetchcache: 'no-store' 옵션을 사용하여 SSR을 구현한다. 

2. 작동 방식

  1. 클라이언트가 페이지(URL)를 요청한다.
  2. Next.js 서버는 해당 요청 시점에 React 컴포넌트를 실행하고, HTML을 서버에서 생성한다.
  3. 생성된 HTML이 클라이언트로 전송되어 초기 화면이 즉시 표시된다.
  4. 이어서 JS 번들이 로드되고, React가 서버에서 렌더된 HTML과 클라이언트 렌더 트리를 비교하여 Hydration(하이드레이션) 을 수행한다 — 즉, 이벤트 핸들러와 상태를 연결해 인터랙션을 활성화한다.

3. 특징

  • 페이지 단위로 서버 렌더링을 수행한다.
  • 서버에서 데이터를 호출하여 HTML에 반영할 수 있다.
  • HTML과 JS 번들이 모두 클라이언트로 전달된다.
  • SEO 최적화와 초기 로딩 속도 개선에 유리하다.

4. 예제 코드 (App Router 방식)

cache: 'no-store'를 사용하면 Next.js는 이 fetch를 요청마다 새로 실행하여, 기존 getServerSideProps와 동일한 SSR 효과를 낸다.

// app/page.tsx
export default async function Page() {
  // ✅ SSR: 요청 시마다 서버에서 새로 실행
  const res = await fetch('https://api.example.com/posts', {
    cache: 'no-store', // SSR 방식
  });

  const posts = await res.json();

  return (
    <main>
      <h1>서버 사이드 렌더링 (SSR)</h1>
      <ul>
        {posts.map((post: any) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </main>
  );
}

5. ISR(Incremental Static Regeneration) 버전

ISR은 “매 요청마다”가 아니라 “일정 시간마다” 서버에서 새로 렌더링한다.

export default async function Page() {
  // ✅ ISR: 일정 주기마다 서버에서 페이지를 재생성
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 60 }, // 60초마다 새로 생성
  });

  const posts = await res.json();

  return (
    <main>
      <h1>ISR (부분적 SSR)</h1>
      <ul>
        {posts.map((post: any) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </main>
  );
}

 

2. Server Component (RSC, ‘use server’)

1. 정의

Server Component는 React 18+에서 도입된 서버 전용 컴포넌트로, 클라이언트로 JS 번들을 거의 보내지 않고 서버에서만 렌더링되는 구조를 가진다. 인터랙션 없는 컴포넌트에 최적화되어 있다.

2. 작동 방식

  1. 서버에서 React Server Component를 실행한다.
  2. HTML과 직렬화된 Props만 클라이언트로 전달한다.
  3. 클라이언트는 이 HTML을 그대로 렌더링하며, 필요한 경우 'use client' 컴포넌트와 결합해 인터랙션을 추가한다. — 즉, 인터랙션이 필요하면 Server Component 안에 'use client' 컴포넌트를 섞어서 사용하면 된다.

3. 특징

  • 컴포넌트 단위로 서버 렌더링을 수행한다.
  • 서버에서 DB나 API를 직접 호출할 수 있다.
  • 클라이언트 JS 번들을 최소화해 성능을 극대화한다.
  • 클라이언트와 서버 간 경계가 명확하여 데이터 접근이 안전하다.

4. 예제 코드 (Next.js App Router)

Server Component는 클라이언트에서 조작이 필요 없는 컴포넌트를 서버에서만 렌더링하여 클라이언트 번들을 최소화한다.

// app/components/ServerMessage.tsx
'use server';

async function getServerData() {
  const res = await fetch('https://api.example.com/data', { cache: 'no-store' });
  return res.json();
}

export default async function ServerMessage() {
  const data = await getServerData();

  return (
    <div>
      <h2>Server Component 예제</h2>
      <p>서버에서 받아온 데이터: {data.message}</p>
    </div>
  );
}
// app/page.tsx
import ServerMessage from './components/ServerMessage';

export default function Page() {
  return (
    <main>
      <h1>Next.js Server Component</h1>
      <ServerMessage /> {/* 클라이언트 JS 최소화 */}
    </main>
  );
}

 

3. SSR vs Server Component 비교 정리

구분 SSR Server Component (RSC)
렌더링 단위 페이지 단위 컴포넌트 단위
렌더링 시점 요청마다 (cache: ‘no-store’) 빌드 또는 요청 시 서버 실행
데이터 호출 서버 가능 서버 가능
클라이언트 JS HTML + JS 번들 전달 HTML + Props만 전달 (JS 최소)
목적 SEO, 초기 로딩 속도 개선 성능 최적화, JS 번들 최소화
상호작용 Hydration으로 추가 가능 'use client' 컴포넌트와 조합
대표 코드 fetch(..., { cache: 'no-store' }) 'use server' 함수 또는 기본 Server Component

 

4. 핵심 요약

  • SSR은 요청마다 서버에서 HTML을 새로 생성하며, 클라이언트로 JS 번들을 함께 전달한다. → SEO와 초기 로딩 속도를 개선하는 데 적합하다.
  • Server Component는 컴포넌트 단위로 서버에서 렌더링하며, 클라이언트로 HTML과 Props만 전달해 번들 크기를 극적으로 줄인다. → 서버 데이터 접근이 많은 대규모 애플리케이션의 성능을 최적화한다.
  • App Router 환경에서 SSR을 구현하려면 fetch(..., { cache: 'no-store' }) 또는 next: { revalidate: 0 } 옵션을 사용한다.