[FE] Next.js에서 SSR, RSC, Hydration, server-only 정리

정민희·2025년 9월 8일

FE

목록 보기
2/2

최근 React Hydration 오류를 공부하면서 SSR(Server-Side Rendering), RSC(React Server Components), Client Component, server-only 개념을 정리했다.


1. SSR (Server-Side Rendering)

SSR은 서버에서 React 컴포넌트를 실행해 HTML 문자열을 만든 후 브라우저에 내려주는 방식이다.

SSR의 흐름

1. 서버: React 실행 → HTML 문자열 생성
→ 이때 클라이언트 컴포넌트라고 할지라도 UI가 한번 생성됨

<!DOCTYPE html>
<html>
  <head>
    <title>My App (SSR)</title>
    <script src="/static/js/main.js"></script>
  </head>
  <body>
    <div id="root">
      <h1>Hello from SSR</h1>
      <button>Click</button>
      <script src="/static/js/main.js"></script>
    </div>
  </body>
</html>

2. 서버 → 브라우저: HTML 문자열을 내려줌
3. 브라우저: HTML 파싱(이때 script 태그를 만나면 해당 JS 파일을 요청해 가져옴) → DOM 생성 → 초기 화면 표시
4. 클라이언트: JS 번들 실행 → Virtual DOM 생성
5. Hydration: Virtual DOM과 SSR DOM을 비교(diff) 후 이벤트 핸들러, 상태를 연결

👉 장점: 빠른 초기 렌더링, SEO 친화적
👉 단점: 클라이언트에서도 다시 렌더링(Virtual DOM 생성)해야 하므로 JS 번들 크기와 Hydration 비용이 큼


2. RSC (React Server Components)

Next.js App Router에서 사용하는 새로운 방식으로, 서버와 클라이언트의 역할을 분리한다. 나는 SSR과 CSR의 장점을 모두 가져온 방식이라고 이해했다.

RSC의 특징

  • 서버 컴포넌트는 JS 번들이 클라이언트에 내려가지 않음
  • 서버 실행 결과가 두 가지로 전달됨:

1. HTML 문자열 : 초기 UI를 사용자에게 즉시 표시 가능

 <html>
  <head>
    <title>My App</title>
    <script src="/static/js/main.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

2. RSC payload(JSON-like) : React가 Virtual DOM을 복원하는 데이터(서버에서 JS를 실행한 결과를 내려줌)

[
  ["$","div",null,{"className":"title"},
    ["Hello ", ["$","span",null,null,"world!"]]
  ],
  ["$","p",null,null,"This is server rendered."]
]
  • 클라이언트에서는 "use client" 선언된 컴포넌트만 JS 번들이 내려가고 hydrate된다. 이때 클라이언트 컴포넌트 빌드 산출물은 HTML 문자열이 아니라 DOM을 React가 그리도록 지시하는 코드이다.
    function Button() {
      return React.createElement("button", { onClick: () => alert("clicked!") }, "Click");
    }

이때 순서가 서버 컴포넌트의 Virtual DOM을 만들고, 클라이언트 컴포넌트의 Virtual DOM을 만든 후 최종적으로 생성된 Virtual DOM과 SSR DOM을 가지고 hydration을 한다.

👉 장점: 불필요한 JS 번들 감소, 서버 코드 보안, 선택적 Hydration으로 성능 최적화

❓RSC를 쓰면 hydration 부담이 줄어드는 이유

SSR에서는 서버에서 JS 번들을 모두 내려준다. 그렇기 때문에 서버와 클라이언트가 똑같은 UI를 이중 렌더링 하게된다. 하지만 RSC는 클라이언트 컴포넌트에 대해서만 JS번들을 내려주기 때문에 hydration 역시 클라이언트 컴포넌트에 대해서만 발생한다.


3. Client Component ("use client")

  • 이벤트 핸들러, state, hook을 사용하는 컴포넌트
  • SSR 단계에서도 HTML은 만들어지지만, 이벤트 없는 "죽은 DOM" 상태로 내려감
  • 이때 HTML에 HTML코드 + script코드(JS 번들)를 같이 내려줌
  • 브라우저에서 JS 번들이 실행되면 Virtual DOM 생성 → SSR DOM과 hydrate → 살아있는 UI가 됨

👉 즉, Client Component는 SSR에서도 HTML을 만들지만, 인터랙션은 Hydration 후 가능


4. Hydration

Hydration은 SSR DOM과 Virtual DOM을 비교(diff)해 이벤트 핸들러와 상태를 붙이는 과정이다.

  • 같으면: DOM 그대로 두고 이벤트 핸들러만 연결
  • 다르면: React가 DOM 교체/수정 → 이때 mismatch 오류가 발생하기도 함

👉 Hydration은 Client Component에서만 발생하며, Server Component는 hydrate가 필요 없음


5. server-only

사실 이 옵션은 Next.js에서 App Router를 쓸 때에는 사용하지 않는다. 기본적으로 server component를 사용하기 때문이다. 그래도 server-only를 썼을때와 server component의 차이점이 궁금해서 조금 찾아봤다.

  • 특정 모듈이 클라이언트 번들에 포함되지 않도록 보장하는 도구
  • DB 쿼리, 파일 접근, 비밀 키 등 서버 전용 로직에 사용
  • UI를 직접 만들지 않고, 데이터/비즈니스 로직만 반환
  • 실제 UI는 Server Component나 Client Component가 이 데이터를 이용해 생성

👉 server-only = 보안/최적화 도구, RSC = 렌더링 모델 자체

즉, server-only는 로직 전용이고, UI를 반환하지 않는다는 점에서 server component와 차이가 있는 것 같다.


✅ 결론

  • SSR: 전체 UI HTML 생성 + 전체 코드 Hydration 필요
  • RSC: UI는 서버에서 만들고, 클라이언트 컴포넌트만 Hydration
  • server-only: UI X, 오직 서버 로직 전용

소감

Next.js를 사용하면서 사실 SSR이랑 RSC가 어떻게 동작하는지는 모르고 사용했던 것 같다. SSR, RSC가 어떻게 작동하는지 알고 나니 Next.js를 더 잘 활용할 수 있을 것 같다.

0개의 댓글