Search

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

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라고 불렀습니다.

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

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 문제 SSR의 재발견 (2016~) ↓ 더 세밀한 제어 필요 Streaming SSR, RSC (2020s~)
Plain Text
복사
이 흐름에서 핵심 질문은 하나입니다.
코드의 복잡성을 어디에 둘 것인가? 서버? 클라이언트?
정답은 없습니다. 상황에 따라 달라지죠. SSR을 배우는 건 이 질문에 대해 합리적인 선택을 할 수 있는 능력을 기르는 것입니다.
이제 이 원리를 코드로 직접 확인해보겠습니다.