Server/Client Component
SSR/CSR과 같은 말이 아니라 다른 축이다.
한눈에
- Server/Client Component ≠ SSR/CSR — 같은 말이 아니라 아예 다른 축이다
- 축1 렌더링 방식 = 페이지 HTML을 '언제' 만드나(SSR/SSG/ISR/CSR), 페이지 단위 전략
- 축2 컴포넌트 종류 = 이 조각이 브라우저서도 살아야 하나(Server/Client), 컴포넌트 단위 선택
- Client Component도 서버에서 한 번 실행된다 — 초기 HTML 만든 뒤 JS도 보내 브라우저서 되살림 = hydration
- 'use client' = '브라우저서만 돌아라'가 아니라 '브라우저에도 보내서 살려둬라'
이 이름이 프론트 입문에서 제일 많이 사람을 넘어뜨린다. "Server Component는 SSR이고 Client Component는 CSR인가?" 아니다. Server/Client Component는 SSR/CSR과 다른 축이다. 이 한 줄을 못 넘으면 뒤가 전부 꼬인다. 여기서부터 시작하자.
헷갈림의 정체, 두 개의 다른 축
용어가 겹쳐 보일 뿐, 사실 서로 다른 두 질문에 답하는 개념이다.
- 축1 — 렌더링 방식: 페이지의 HTML을 언제 만드나? 요청마다 서버가(SSR), 빌드 때 미리(SSG), 주기적으로(ISR), 브라우저에서(CSR). 이건 페이지 단위의 전략이다. (앞의 렌더링 노트가 이 축이다.)
- 축2 — 컴포넌트 종류: 이 컴포넌트가 브라우저에서도 살아있어야 하나? Server Component냐 Client Component냐. 이건 컴포넌트 단위의 선택이다.
축이 다르니 서로 곱해진다. "SSR 페이지 안에 Client Component 하나가 들어간다" 같은 조합이 자연스럽게 성립한다. SSR이라서 다 Server Component인 게 아니고, Client Component라서 CSR인 것도 아니다.
결정적 오해, Client Component도 서버에서 실행된다
여기가 진짜 함정이다. 이름만 보면 Client Component는 "브라우저에서만 도는 것" 같다. 아니다. 'use client'가 붙은 컴포넌트도 SSR 과정에서 서버가 먼저 한 번 실행한다. 서버가 그걸 돌려 초기 HTML을 만들어 브라우저로 보낸다.
그런데 거기서 끝이 아니다. 서버는 그 조각의 JS도 함께 브라우저로 보낸다. 브라우저는 도착한 HTML 위에 그 JS를 붙여, 그 컴포넌트를 한 번 더 살려낸다. 마른 HTML에 물을 부어 되살리는 이 과정이 **hydration(수화)**이다. 서버에서 한 번, 브라우저에서 또 한 번 — Client Component는 두 무대에서 실행되는 셈이다.
그래서 'use client'의 진짜 의미는 "브라우저서만 돌아라"가 아니다. **"브라우저에도 보내서 (hydration으로) 살려둬라"**가 정확한 뜻이다.
표로 보면 무엇이 다른가
둘 다 서버에서 실행된다는 점은 같다. 갈리는 건 "브라우저로 JS를 딸려 보내느냐"다.
| 구분 | 서버에서 실행 | 브라우저로 JS 전송 | 결과 |
|---|---|---|---|
| Server Component | 예 | 아니오 | 죽은 HTML로만 남음 · 번들 절약 |
| Client Component | 예 | 예 | hydration으로 되살아남 · 상호작용 가능 |
핵심은 왼쪽 두 칸이다. 서버 실행은 둘 다 "예"로 같고, 오른쪽 "JS 전송"에서 갈린다. Server Component는 JS를 안 보내니 브라우저엔 움직이지 않는 HTML만 남고, Client Component는 JS까지 보내 브라우저에서 다시 살아나 클릭·타이핑에 반응한다.
채팅창으로 따라가기
말로는 잘 안 붙으니, 메시지 목록(Server Component)과 입력창(Client Component)이 한 채팅 페이지에 SSR로 뜨는 과정을 따라가 보자.
②에서 둘 다 서버가 실행한 게 포인트다(입력창도 서버서 한 번 돈다). 사용자는 ③에서 이미 화면을 보고, ④에서 입력창 몫 JS만 hydration되며 조작이 열린다. ⑤에서 메시지목록은 클릭할 게 없으니 JS를 아예 안 받아 HTML로 굳는다.
왜 JS를 안 보내는 게 이득인가
브라우저가 JS를 받으면 공짜가 아니다. 다운로드 → 파싱 → 실행을 다 거쳐야 하고, 이게 전부 시간이자 비용이다 — 특히 모바일에서 체감이 크다. 그런데 메시지 목록은 그냥 보여주기만 할 뿐 클릭할 게 없다. 여기에 JS를 보낼 이유가 없다. 그래서 서버가 HTML로 구워 보내면, 브라우저가 받아 처리할 JS가 줄고 그만큼 로딩이 빨라진다.
반대로 입력창은 타이핑을 감지하고 전송 버튼 클릭에 반응해야 한다. 이런 상호작용은 브라우저에서 살아있는 JS가 있어야만 가능하다. 그래서 입력창엔 JS가 필수다.
한 가지 자주 헷갈리는 것 — "나는 TypeScript로 짰는데?" TS를 써도 빌드하면 결국 JS로 변환돼 브라우저로 간다. 브라우저가 받는 건 언제나 JS다. 그러니 "이 조각에 JS를 딸려 보낼까 말까"가 곧 성능 선택이 된다.
정리하면, 렌더링 방식은 "HTML을 언제 굽나"를 정하고, Server/Client Component는 "그 조각에 JS를 딸려 보내나"를 정한다. 서로 다른 축이라던 첫 문장이 여기서 완결된다.
우리 프로젝트에선
이 0to1 블로그는 App Router라 기본이 Server Component다. 그래서 대부분의 페이지 조각이 JS를 안 받는 죽은 HTML로 가서 가볍다. 상호작용이 필요한 것 — 사이드바 트리를 접었다 펴는 WikiNav, 다크/라이트를 바꾸는 ThemeToggle 정도 — 만 골라 'use client'로 빼뒀다. 딱 그것들만 hydration되어 살아나고, 나머지 본문은 전부 구워진 HTML이다.
덕분에 "글 읽는 페이지"에 불필요한 JS가 실리지 않는다. 페이지를 짓고 밤마다 다시 굽는 장치는 AI 하네스에, App Router의 라우팅·렌더링 배경은 App Router 노트에 이어진다.
더 읽기
Server/Client Component와 hydration을 그림·예시로 더 풀어놓은 한국어 글들. 헷갈릴 때 하나씩.
- [Next.js] Server Component (아마란스 생각) — 서버·클라이언트 컴포넌트의 차이와
'use client'의 역할을 또박또박 - Next.js의 렌더링 과정(Hydrate) 알아보기 (howdy-mj) — 서버가 만든 HTML이 브라우저에서 어떻게 되살아나는지 hydration을 단계별로
- (번역) 리액트 앱(SSR)의 Hydration 이해하기 (velog @hyemin916) — "마른 HTML에 물 붓기" 비유로 hydration의 원리를 풀어낸 번역글