Search

02_서버와_클라이언트의_역사

서버와 클라이언트의 역사

웹의 역사는 “보이지 않는 경계”의 이동 역사예요. 서버와 클라이언트 사이 어디서 HTML을 만들 것인가—이 질문이 30년간 반복되어 왔어요.
이 장에서는 그 경계가 어떻게 움직여왔는지 살펴볼게요.
SSR이 왜 이렇게 복잡하게 느껴지는지 이해하려면, 웹 개발이 어떤 흐름을 거쳐왔는지를 먼저 알아야 해요. 역사 전체를 다루려는 건 아니고, “왜 지금 이 상황이 됐는지” 맥락을 잡는 정도로만 살펴볼게요.

잊혀진 디폴트

SSR은 추가 기능이 아니에요. 이는 인터넷의 기본 상태로, 사용자 경험의 요구가 초기 서버 기능을 초과하면서 버려졌다가 정성스럽게 재설계됐어요.
“CSR이 디폴트인 줄 알았는데, 사실 SSR이 원래 디폴트였다.”
React와 SPA 시대에 프론트엔드를 시작한 개발자에게 SSR은 “추가로 해야 하는 것”처럼 느껴져요. 하지만 웹의 역사를 보면 정반대예요.
서버에서 HTML을 만들어 보내는 것이 웹의 원래 모습이었어요. 클라이언트에서 JavaScript로 화면을 그리는 것이 오히려 나중에 등장한 예외적 방식이죠.
이 관점을 가지면 SSR을 바라보는 눈이 달라져요.

원래 웹은 서버가 전부였다

1990년대 웹은 단순했어요. 브라우저가 서버에 페이지를 요청하면, 서버가 HTML을 만들어서 보내주고, 브라우저는 그걸 그대로 화면에 표시했어요. PHP, JSP, ASP 같은 기술들이 이 역할을 했죠.
[브라우저] --요청--> [서버: HTML 생성] --HTML--> [브라우저: 화면 표시]
Plain Text
복사
이 시절에는 “SSR”이라는 용어 자체가 없었어요. 서버에서 HTML을 만드는 게 웹 개발의 기본이었으니까요. 굳이 이름을 붙일 필요가 없었던 거예요.

클라이언트의 부상: AJAX 혁명

2005년, 제시 제임스 가렛(Jesse James Garrett)이 “Ajax: A New Approach to Web Applications”라는 글을 발표해요. AJAX(Asynchronous JavaScript and XML)라는 용어가 탄생한 순간이었죠.
AJAX 이전에는 작은 데이터 하나를 업데이트하려 해도 전체 페이지를 새로 받아야 했어요. AJAX가 이걸 바꿨어요. 페이지 전체를 새로고침하지 않고도 서버와 데이터를 주고받을 수 있게 된 거예요.
[AJAX 이전] 버튼 클릭 → 서버 요청 → 전체 페이지 새로고침 [AJAX 이후] 버튼 클릭 → 서버 요청 → 필요한 부분만 업데이트
Plain Text
복사
구글 맵스, Gmail 같은 서비스가 AJAX로 만든 부드러운 경험을 보여주면서, 웹 개발의 무게중심이 서서히 클라이언트 쪽으로 이동하기 시작해요.

jQuery 시대와 DOM 조작

2006년 존 레식(John Resig)이 jQuery를 발표해요. jQuery는 브라우저 호환성 문제를 해결하고, DOM 조작을 쉽게 만들어줬어요.
DOM(Document Object Model)은 브라우저가 HTML을 파싱해서 만드는 트리 구조예요. <div> 안에 <p>가 있으면, div 노드 아래에 p 노드가 있는 식이죠. JavaScript로 이 트리를 조작하면 화면이 바뀌어요.
// jQuery 이전: 브라우저마다 다른 코드가 필요했음 var xhr; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); // 모던 브라우저 } else { xhr = new ActiveXObject("Microsoft.XMLHTTP"); // IE 6 이하 } // jQuery: 모든 브라우저에서 동일한 코드 $.ajax({ url: '/api/data', success: function(data) { ... } });
JavaScript
복사
jQuery는 “클라이언트에서 DOM을 직접 조작한다”는 패러다임을 대중화했어요. 서버는 데이터만 주고, 화면을 그리는 건 클라이언트가 한다는 개념이 자리 잡기 시작한 거죠.

SPA의 등장

2010년대에 들어서면서 이 흐름이 극단으로 치달아요. 여러 변화가 동시에 일어났죠.
하드웨어: 사용자들의 컴퓨터와 스마트폰 성능이 급격히 좋아졌어요.
JavaScript 엔진: 2008년 Google이 Chrome과 함께 V8 엔진을 공개하면서 JavaScript 성능이 수 배 향상됐어요. “장난감 언어”가 “진짜 애플리케이션을 만들 수 있는 언어”가 된 거죠.
언어 자체의 발전: ES6(2015)에서 클래스, 모듈, 화살표 함수, Promise 등이 추가되면서 JavaScript로 대규모 애플리케이션을 구조화하기 쉬워졌어요.
이런 환경에서 “아예 클라이언트에서 모든 화면을 그리면 어떨까?”라는 시도가 나타났어요.
Backbone.js(2010), AngularJS(2010), 그리고 React(2013) 같은 라이브러리들이 등장하면서 Single Page Application(SPA)이 주류가 돼요. 서버는 빈 HTML 껍데기와 JavaScript 번들만 보내주고, 실제 화면은 브라우저에서 JavaScript가 그리는 방식이죠.
[브라우저] --요청--> [서버: 빈 HTML + JS] ---> [브라우저: JS 실행해서 화면 생성]
Plain Text
복사
이 방식은 사용자 경험 측면에서 장점이 있었어요. 페이지 이동 시 전체를 새로 로드할 필요 없이 필요한 부분만 바꾸면 되니까 훨씬 빠르고 부드러운 인터랙션이 가능했죠.
하지만 문제도 있었어요.
초기 로딩이 느렸어요. JavaScript 번들을 다 받아서 실행해야 화면이 보이니까요.
검색 엔진 최적화(SEO)가 어려웠어요. 검색 엔진 크롤러가 JavaScript를 실행하지 못하면 빈 페이지만 보게 되니까요.

Node.js: 언어 경계의 붕괴

2009년 라이언 달(Ryan Dahl)이 Node.js를 발표해요. 서버에서도 JavaScript를 실행할 수 있게 된 거죠.
이건 단순히 “서버에서도 JS를 쓸 수 있다”는 것 이상의 의미가 있었어요. 기존에는 서버(PHP, Java, Ruby)와 클라이언트(JavaScript)가 완전히 다른 언어였어요. 같은 로직을 양쪽에서 쓰려면 두 번 작성해야 했죠.
Node.js 이후 “같은 JavaScript 코드를 서버와 클라이언트 양쪽에서 실행할 수 있다”는 아이디어가 현실이 돼요. 이걸 Isomorphic JavaScript 또는 Universal JavaScript라고 불렀어요.

Isomorphic의 함정

하지만 “양쪽에서 같은 코드”라는 말에는 함정이 있었어요.
서버에서만 되는 것: DB 접근, 파일 시스템, 인증/인가 클라이언트에서만 되는 것: DOM 조작, window 객체, 브라우저 API 양쪽 다 되는 것: 순수 계산, 문자열 처리, 데이터 변환
Plain Text
복사
Isomorphic이란 “양쪽 다 되는 것만” 쓸 수 있다는 뜻이었어요. 서버 전용 로직(DB 쿼리, 인증 처리)을 컴포넌트 안에서 직접 쓸 수 없었죠. 이 제약은 2023년 React Server Components가 나올 때까지 풀리지 않았어요.
핵심: Isomorphic은 “같은 코드를 양쪽에서”가 아니라 “양쪽 다 되는 코드만” 공유한다는 의미예요. 서버 전용 로직은 여전히 분리해야 했어요.

BigPipe: 잊혀진 선구자

SPA가 주류가 되던 2010년, Facebook은 다른 방향을 탐구하고 있었어요. BigPipe라는 기술이었죠.
[BigPipe 핵심 아이디어] 1. 페이지를 Pagelet(조각)으로 분할 2. 빠른 부분(헤더, 네비게이션)부터 먼저 전송 3. 느린 부분(피드, 추천)은 나중에 <script>로 교체
Plain Text
복사
기존 방식은 모든 데이터가 준비될 때까지 기다렸다가 한 번에 보냈어요. BigPipe는 “준비된 것부터 보내자”는 발상의 전환이었죠.
이 개념은 당시에는 널리 퍼지지 않았지만, 13년 뒤 React의 Streaming SSR(Fizz)로 재탄생해요. 6장에서 다룰 Suspense와 스트리밍의 원형이 여기서 시작된 거예요.

다시 서버로, 근데 이번엔 둘 다

SPA의 문제를 해결하기 위해 “처음 한 번은 서버에서 HTML을 만들어서 보내고, 그 이후에는 클라이언트에서 처리하면 어떨까?”라는 아이디어가 나와요. 이게 지금 우리가 말하는 SSR이에요.
[첫 요청] 서버에서 HTML 생성 → 빠른 초기 로딩, SEO 가능 [이후] 클라이언트에서 처리 → 부드러운 인터랙션
Plain Text
복사
Next.js(2016), Nuxt.js(2016), Remix(2021) 같은 프레임워크들이 이 방식을 쉽게 구현할 수 있도록 등장했어요. 이 프레임워크들은 Node.js 덕분에 가능해진 “같은 React/Vue 컴포넌트를 서버에서도, 클라이언트에서도 실행한다”는 아이디어를 현실로 만들었어요.

메타프레임워크의 등장

SSR을 직접 구현하려면 해결해야 할 게 많았어요:
서버에서 React 컴포넌트를 렌더링하는 설정
라우팅 시스템 구축
데이터 페칭 패턴 설계
번들링과 코드 스플리팅
개발 서버와 HMR 설정
매 프로젝트마다 이걸 처음부터 하는 건 비효율적이었죠.
그래서 메타프레임워크가 등장해요. React나 Vue 같은 UI 라이브러리 위에 라우팅, SSR, 빌드 시스템을 얹은 “프레임워크 위의 프레임워크”예요.
React → Next.js, Remix Vue → Nuxt Svelte → SvelteKit
Plain Text
복사
메타프레임워크는 “의사결정 피로”를 줄여줘요. “라우터는 뭘 쓰지?”, “번들러는?”, “SSR 설정은?” 같은 고민 없이 바로 비즈니스 로직에 집중할 수 있게 해주죠.

보이지 않는 경계

여기서 복잡함이 시작돼요. 예전에는 “서버 코드”와 “클라이언트 코드”가 명확히 분리되어 있었어요. PHP는 서버에서만 돌아가고, JavaScript는 브라우저에서만 돌아갔죠.
그런데 지금은 같은 React 컴포넌트가 서버에서도 실행되고, 클라이언트에서도 실행돼요. 같은 코드인데 실행되는 환경이 다른 거예요. 이 경계가 모호해지면서 생기는 혼란이 바로 우리가 겪는 문제들의 원인이에요.
window를 쓰면 서버에서 에러가 나고
서버에서 만든 HTML과 클라이언트에서 다시 만든 결과가 다르면 hydration mismatch가 나고
어떤 코드는 서버에서만 실행되어야 하고, 어떤 코드는 클라이언트에서만 실행되어야 하는데, 그 경계 사이에서 무슨 일이 일어나는지는 눈에 보이지 않아요
결국 핵심은 이거예요.
종착점은 항상 HTML이에요. 서버에서 만들든, 클라이언트에서 만들든, 최종적으로 브라우저가 보여주는 건 HTML이에요. 차이는 “어디서 만드느냐”뿐이죠.

흐름 정리

서버에서 HTML 서빙 (1990s) ↓ 동적 페이지 필요 CGI, PHP, JSP (1990s-2000s) ↓ 더 나은 사용자 경험 필요 AJAX 혁명, jQuery (2005-2010) ↓ V8 엔진(2008), ES6(2015), 하드웨어 발전 SPA와 프론트엔드 프레임워크 (2010s) ↓ 초기 로딩, SEO 문제 ← BigPipe (2010, Facebook) - 스트리밍의 원형 SSR의 재발견 (2016~) ↓ 더 세밀한 제어 필요 Streaming SSR, RSC (2020s~) ← BigPipe 아이디어의 부활
Plain Text
복사
이 흐름에서 핵심 질문은 하나예요.
코드의 복잡성을 어디에 둘 것인가? 서버? 클라이언트?
정답은 없어요. 상황에 따라 달라지죠. SSR을 배우는 건 이 질문에 대해 합리적인 선택을 할 수 있는 능력을 기르는 거예요.

시대별 개발자가 알아야 할 것

웹의 복잡도가 어떻게 증가해왔는지 시각화하면 이래요.
1995년 개발자가 알아야 할 것: ├── HTML ├── CSS └── 약간의 JavaScript 총 3가지 2005년 개발자가 알아야 할 것: ├── HTML, CSS, JavaScript ├── AJAX, XMLHttpRequest ├── jQuery, DOM 조작 └── 브라우저 호환성 총 7-8가지 2015년 개발자가 알아야 할 것: ├── HTML, CSS, JavaScript (ES6+) ├── React/Vue/Angular ├── Webpack, Babel ├── npm, 패키지 관리 ├── REST API 설계 └── 상태 관리 (Redux 등) 총 15가지 이상 2025년 개발자가 알아야 할 것: ├── 위의 모든 것 + ├── SSR vs CSR vs SSG vs ISR ├── Hydration, Streaming ├── Server Components ├── Edge Functions ├── 번들 최적화 └── Core Web Vitals 총 25가지 이상
Plain Text
복사
복잡도는 계속 증가해요. 하지만 좋은 소식은 프레임워크가 이 복잡도의 상당 부분을 흡수한다는 거예요. 5장에서 다룰 내용이에요.
이제 이 원리를 코드로 직접 확인해볼게요.