바닐라 JS로 이해하는 CSR, SSR, 라우팅, 하이드레이션 톺아보기
들어가며
오늘은 모던 웹 개발의 핵심 개념인 클라이언트 사이드 렌더링(CSR), 서버 사이드 렌더링(SSR), 클라이언트 사이드 라우팅, 그리고 하이드레이션(Hydration)에 대해 알아본다. 이 개념들은 React, Vue, Next.js, Nuxt.js 같은 현대적인 프레임워크의 근간을 이루고 있지만, 때로는 그 복잡성 때문에 정확히 이해하기 어려울 수 있다.
이 글에서는 프레임워크의 추상화 없이, 바닐라 자바스크립트로 이러한 개념들을 살펴보면서 그 작동 원리를 파헤쳐 본다. 이를 통해 프레임워크가 내부적으로 어떻게 동작하는지, 그리고 왜 이런 패턴과 기술이 중요한지 명확하게 이해할 수 있을 것이다.
웹 렌더링의 흐름: 역사적 관점
현대 웹 개발의 렌더링 방식을 이해하기 위해서는 간략한 역사적 맥락을 알아볼 필요가 있다.
초기 웹: 서버 중심 렌더링
웹의 초기에는 서버 사이드 렌더링이 기본이었다. 사용자가 URL을 입력하면 서버가 HTML을 생성하여 전체 페이지를 브라우저로 전송했다. 페이지 간 이동이나 데이터 갱신은 항상 새로운 페이지 로드를 의미했다. 링크를 클릭할 때마다 브라우저는 서버에 새로운 요청을 보내고, 서버는 완전히 새로운 HTML 페이지를 생성하여 응답했다. 이 방식은 간단하지만 사용자 경험 측면에서는 페이지 전환마다 발생하는 깜빡임과 로딩 시간이 단점이었다.
AJAX의 등장: 부분 업데이트의 시작
JavaScript와 AJAX(Asynchronous JavaScript and XML)의 발전으로 페이지 전체를 다시 로드하지 않고도 서버에서 데이터를 가져와 페이지의 일부분만 업데이트하는 것이 가능해졌다. 이는 더 동적이고 반응성 높은 웹 애플리케이션으로의 전환점이 되었다.
AJAX를 활용하면 웹 페이지가 서버와 비동기적으로 데이터를 교환할 수 있어, 전체 페이지를 리로드하지 않고도 특정 부분만 업데이트할 수 있다. Gmail이나 Google Maps와 같은 서비스가 AJAX를 적극 활용한 초기 사례였으며, 이를 통해 웹 애플리케이션이 데스크톱 애플리케이션처럼 부드럽고 반응성 높은 경험을 제공할 수 있게 되었다.
SPA의 시대: 클라이언트 사이드 렌더링
이후 AngularJS, React, Vue 같은 프레임워크의 등장으로 단일 페이지 애플리케이션(SPA) 개발이 일반화되었다. SPA는 초기에 필요한 HTML, CSS, JavaScript를 로드한 후, 페이지 전환이나 데이터 갱신이 필요할 때 서버로부터 JSON 데이터만 받아와 클라이언트에서 렌더링하는 방식이다.
SPA는 서버에 최초 요청 시 애플리케이션 실행에 필요한 모든 리소스를 다운로드한다. 이후 사용자 상호작용에 따라 필요한 데이터만 API를 통해 가져와 동적으로 DOM을 조작한다. 이 방식은 페이지 전환이 매우 빠르고 부드러워 마치 네이티브 앱과 같은 사용자 경험을 제공한다.
그러나 이 방식은 초기 로딩 시간이 길고, 검색 엔진 최적화(SEO)에 불리하다는 단점이 있었다. 검색 엔진 크롤러가 JavaScript로 렌더링된 콘텐츠를 제대로 인식하지 못하는 문제가 있었으며, 초기 로딩 시 사용자는 빈 페이지나 로딩 화면을 오래 보게 되는 경우가 많았다.
현재: 하이브리드 접근법
이러한 문제를 해결하기 위해, Next.js, Nuxt.js와 같은 프레임워크는 서버 사이드 렌더링과 클라이언트 사이드 렌더링의 장점을 결합한 하이브리드 접근법을 채택했다. 이 방식에서는 초기 페이지를 서버에서 렌더링하여 빠른 로딩과 SEO 혜택을 제공하고, 이후 클라이언트에서 하이드레이션(Hydration)을 통해 페이지를 인터랙티브하게 만든다.
이러한 하이브리드 방식은 '최초 뷰'를 빠르게 제공하면서도, 이후 상호작용은 클라이언트에서 처리하여 부드러운 사용자 경험을 제공한다. 이제 이 개념들을 좀 더 자세히 살펴보자.
클라이언트 사이드 라우팅 이해하기
클라이언트 사이드 라우팅은 사용자가 웹 애플리케이션 내에서 페이지 간 이동할 때, 브라우저가 서버에 새로운 페이지를 요청하는 대신 JavaScript를 사용하여 URL을 변경하고 필요한 콘텐츠를 동적으로 로드하는 기술이다.
작동 방식
전통적인 웹 사이트에서는 링크를 클릭하면 브라우저가 서버에 새 페이지를 요청하고, 전체 페이지가 다시 로드된다. 그러나 클라이언트 사이드 라우팅에서는 이 과정이 JavaScript를 통해 처리된다.
-
이벤트 감지: 사용자가 내부 링크를 클릭하면, JavaScript가 이 이벤트를 감지하고 기본 동작(페이지 새로고침)을 방지한다.
-
URL 변경: History API를 사용하여 브라우저의 URL을 변경한다. 이때 pushState() 메서드를 사용하면 실제 페이지 로드 없이도 브라우저 주소창의 URL과 브라우저 히스토리를 업데이트할 수 있다.
// URL 변경 시 사용되는 History API 예시
window.history.pushState({}, "", "/새로운/경로");
// 첫 번째 인자: 상태 객체(state) - 이 상태와 함께 저장될 데이터 객체
// 두 번째 인자: 제목(title) - 대부분의 브라우저에서 무시됨
// 세 번째 인자: URL - 새로운 히스토리 항목의 URL
-
콘텐츠 렌더링: 변경된 URL에 따라 적절한 컴포넌트나 페이지 콘텐츠를 렌더링한다. 이는 미리 로드된 JavaScript를 통해 DOM을 직접 조작하는 방식으로 이루어진다.
-
상태 관리: 브라우저의 뒤로/앞으로 버튼을 처리하기 위해 popstate 이벤트를 감지하고, 해당 상태에 맞는 페이지를 렌더링한다.
이점과 단점
이점:
- 페이지 전환이 빠르고 부드러움 (깜빡임 없음)
- 필요한 데이터만 서버에서 가져와 트래픽 감소
- 네이티브 앱과 유사한 사용자 경험 제공
단점:
- 초기 JavaScript 로드 시간이 길어질 수 있음
- 구현 복잡성 증가
SEO 관점에서의 CSR
클라이언트 사이드 렌더링은 전통적으로 SEO에 취약점을 가지고 있다. 검색 엔진 크롤러가 페이지를 방문할 때, JavaScript가 실행되기 전 초기 HTML은 대부분 비어있거나 최소한의 내용만 포함하고 있다. 이로 인해 다음과 같은 SEO 문제가 발생할 수 있다:
- 색인 누락: 주요 콘텐츠가 JavaScript로 렌더링되기 때문에 일부 검색 엔진이 페이지의 실제 콘텐츠를 제대로 색인화하지 못할 수 있다.
- 메타데이터 문제: 동적으로 생성되는 제목, 설명 등이 크롤링 시점에 존재하지 않을 수 있다.
- 소셜 미디어 공유: 페이스북, 트위터 등의 소셜 미디어 플랫폼은 JavaScript 렌더링을 기다리지 않고 메타 태그를 추출하므로, 공유 시 미리보기가 정확하지 않을 수 있다.
최근 Google과 같은 주요 검색 엔진은 JavaScript 렌더링 능력을 향상시켰지만, 여전히 모든 검색 엔진이 이를 완벽하게 지원하는 것은 아니며, JavaScript 렌더링은 HTML 파싱보다 우선순위가 낮아 색인화가 지연될 수 있다.
서버 사이드 렌더링(SSR) 구현하기
**서버 사이드 렌더링(SSR)**은 서버에서 페이지의 HTML을 생성하여 브라우저에 전송하는 방식이다. 이는 초기 로딩 속도를 개선하고 SEO를 향상시킨다.
작동 방식
SSR의 기본 흐름은 다음과 같다:
-
사용자 요청: 사용자가 특정 URL로 접속한다 (예: /search?query=마블).
-
데이터 가져오기: 서버는 해당 URL에 필요한 데이터를 가져온다 (예: 영화 데이터베이스에서 "마블" 관련 영화 검색).
-
HTML 생성: 서버는 이 데이터를 사용해 완전한 HTML을 생성한다. 이 HTML에는 이미 데이터가 반영된 콘텐츠가 포함되어 있다.
-
응답 전송: 생성된 HTML을 클라이언트에 전송한다. 중요한 점은, 이 HTML에 JavaScript 코드와 함께 초기 데이터도 포함시킬 수 있다는 것이다. 주로 window.__INITIAL_DATA__와 같은 전역 변수에 데이터를 저장하는 방식을 사용한다.
// 서버에서 생성하는 HTML에 초기 데이터 포함 예시
const html = `
<div id="app">
<!-- 렌더링된 콘텐츠 -->
</div>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
</script>
`;
이점과 단점
이점:
- 초기 페이지 로드가 빠름 (첫 콘텐츠 표시 시간 단축)
- 소셜 미디어 공유 시 미리보기 제공 가능
- 네트워크 연결이 느린 환경에서도 콘텐츠를 빠르게 표시
단점:
- 서버 부하 증가
- 페이지마다 서버 요청 필요 (전통적인 방식에서)
- 개발 복잡성 증가
- 완전한 인터랙티브 기능까지 시간 소요 (하이드레이션 필요)
SEO 관점에서의 SSR
서버 사이드 렌더링은 SEO에 큰 이점을 제공한다. 검색 엔진 크롤러가 페이지를 방문했을 때 이미 완전히 렌더링된 HTML을 받기 때문에 콘텐츠를 쉽게 색인화할 수 있다:
- 완전한 색인화: 모든 콘텐츠가 초기 HTML에 포함되어 있어 검색 엔진이 전체 콘텐츠를 즉시 색인화할 수 있다.
- 메타데이터 최적화: 각 페이지별로 적절한 제목, 설명, 오픈 그래프 태그 등을 서버에서 동적으로 생성하여 포함할 수 있다.
- 소셜 미디어 호환성: 페이스북, 트위터 등에서 공유될 때 정확한 미리보기와 메타데이터를 표시할 수 있다.
- 다양한 검색 엔진 지원: JavaScript 렌더링 능력이 제한적인 검색 엔진에서도 콘텐츠를 올바르게 해석할 수 있다.
SSR은 특히 콘텐츠 중심 웹사이트, 블로그, 뉴스 사이트, 이커머스 플랫폼과 같이 검색 트래픽이 중요한 서비스에 매우 효과적이다. 검색 결과 노출이 비즈니스의 핵심 요소인 경우, SSR은 필수적인 선택이 될 수 있다.
하이드레이션(Hydration) 이해하기
하이드레이션은 서버에서 렌더링된 정적 HTML에 JavaScript 기능을 "주입"하여 인터랙티브한 웹페이지로 만드는 과정이다. 쉽게 말해, 서버에서 받은 HTML 골격에 이벤트 리스너와 상태 관리 기능을 부여하는 것이다.
작동 방식
하이드레이션의 기본 흐름은 다음과 같다:
-
정적 HTML 수신: 브라우저가 서버에서 생성된 HTML을 받아 화면에 표시한다. 이 시점에서 페이지는 이미 콘텐츠를 보여주지만, 버튼 클릭 같은 인터랙션은 작동하지 않는다.
-
JavaScript 로드: 브라우저가 관련 JavaScript 파일을 로드하고 실행한다.
-
DOM 연결: JavaScript 코드가 실행되면서 기존 HTML 요소에 이벤트 리스너와 기능을 연결한다. 중요한 점은, 이 과정에서 DOM을 다시 생성하지 않고 기존 DOM에 기능만 추가한다는 것이다.
-
상태 복원: 서버에서 전달받은 초기 데이터(window.__INITIAL_DATA__)를 사용하여 애플리케이션의 상태를 초기화한다.
// 하이드레이션 과정에서 초기 데이터 활용 예시
function initApp() {
const initialData = window.__INITIAL_DATA__;
if (initialData) {
// 서버에서 받은 데이터로 상태 초기화
// 추가 데이터 요청 없이 기존 DOM에 이벤트 리스너만 연결
} else {
// 데이터가 없는 경우 클라이언트에서 데이터 가져오기
}
}
이점과 최적화
하이드레이션의 큰 이점은 성능 최적화이다. 서버에서 이미 렌더링된 HTML과 초기 데이터가 있는 경우, 클라이언트는 다음과 같은 단계를 건너뛸 수 있다:
- 초기 데이터 요청: API 호출이나 데이터 가져오기 과정을 생략할 수 있다.
- HTML 생성: 이미 서버에서 생성된 HTML을 재사용할 수 있다.
- DOM 조작 최소화: 전체 페이지를 다시 렌더링하는 대신, 이벤트 리스너만 추가하면 된다.
이러한 최적화는 특히 모바일 기기나 느린 네트워크 환경에서 사용자 경험을 크게 향상시킨다.
CSR과 SSR의 결합: 최적의 사용자 경험
이제 앞서 설명한 개념들이 어떻게 함께 작동하여 최적의 사용자 경험을 만드는지 실제 사용자 흐름을 통해 살펴보자:
시나리오 1: 직접 URL 접속 (SSR 활용)
사용자가 직접 URL(/search?query=마블)로 접속하는 경우:
-
서버 렌더링: 서버는 요청된 URL을 분석하고, "마블" 관련 영화 데이터를 가져온다. 이 데이터로 완전한 HTML을 생성하여 사용자에게 전송한다.
-
즉시 콘텐츠 표시: 사용자는 JavaScript가 완전히 로드되기 전에도 페이지 콘텐츠를 볼 수 있다. 이는 인지된 성능을 크게 향상시킨다.
-
하이드레이션: JavaScript가 로드되면, 서버에서 제공한 초기 데이터(window.__INITIAL_DATA__)를 감지하고 하이드레이션을 수행한다. 이 과정에서 추가 데이터 요청 없이 HTML 요소에 이벤트 리스너를 연결한다.
-
완전한 인터랙티브 페이지: 하이드레이션이 완료되면 페이지는 완전히 인터랙티브해진다. 사용자는 버튼을 클릭하거나 검색을 수행할 수 있다.
시나리오 2: 내부 페이지 이동 (CSR 활용)
사용자가 애플리케이션 내에서 페이지를 이동하는 경우:
-
링크 클릭 감지: 사용자가 내부 링크("홈으로" 등)를 클릭하면, JavaScript가 이 이벤트를 감지하고 기본 동작을 방지한다.
-
클라이언트 라우팅: History API를 사용하여 URL을 변경하고, 해당 경로에 맞는 컴포넌트를 렌더링한다. 이 과정은 서버 요청 없이 클라이언트에서 이루어진다.
-
부드러운 전환: 전체 페이지를 다시 로드하지 않기 때문에, 페이지 전환이 매우 부드럽고 빠르게 이루어진다.
시나리오 3: 클라이언트에서 새 데이터 요청 (CSR + API)
사용자가 새로운 검색을 수행하는 경우:
-
사용자 입력: 사용자가 검색창에 새로운 키워드를 입력하고 검색 버튼을 클릭한다.
-
클라이언트 라우팅: URL이 업데이트되고(/search?query=새검색어), 검색 페이지 컴포넌트가 렌더링된다.
-
API 요청: 이번에는 서버에서 받은 초기 데이터가 없으므로, 클라이언트에서 API를 통해 데이터를 요청한다.
-
동적 렌더링: 받아온 데이터로 검색 결과를 동적으로 렌더링한다. 이 과정은 전체 페이지 리로드 없이 수행된다.
이러한 하이브리드 접근법은 다음과 같은 이점을 제공한다:
- 첫 페이지 로드 최적화: SSR을 통한 빠른 초기 로딩과 SEO 혜택
- 페이지 전환 최적화: CSR을 통한 부드러운 페이지 전환과 사용자 경험
- 데이터 재활용: 하이드레이션을 통한 불필요한 데이터 요청 제거
- 점진적 향상: JavaScript가 로드되기 전에도 기본 콘텐츠 제공
하이드레이션 관련 주의사항
하이드레이션 과정에서 발생할 수 있는 몇 가지 중요한 이슈들이 있다:
1. 서버/클라이언트 환경 차이
서버와 클라이언트 환경의 차이로 인한 문제를 주의해야 한다. 서버에는 window, document 같은 브라우저 객체가 없기 때문에, 이를 직접 참조하면 오류가 발생한다. 따라서 코드가 실행되는 환경을 항상 확인해야 한다.
// 서버에서 오류가 발생하는 코드
const width = window.innerWidth; // 서버에는 window 객체가 없음
// 환경을 확인하는 안전한 접근법
const width = typeof window !== "undefined" ? window.innerWidth : 0;
2. 서버/클라이언트 렌더링 결과 불일치
서버에서 렌더링한 HTML과 클라이언트에서 하이드레이션 후 생성되는 HTML이 다를 경우 문제가 발생할 수 있다. 이는 주로 다음과 같은 원인으로 발생한다:
- 시간에 의존적인 데이터: 날짜, 시간, 랜덤 값 등
- 환경에 의존적인 데이터: 사용자 위치, 브라우저 정보 등
- 조건부 렌더링 차이: 서버와 클라이언트의 조건 평가가 다른 경우
React와 같은 프레임워크에서는 이러한 불일치를 감지하고 경고하기도 한다. 해결책은 서버와 클라이언트에서 동일한 렌더링 결과를 보장하는 것이다.
3. 하이드레이션 지연
하이드레이션은 모든 JavaScript가 로드되고 실행된 후에야 완료된다. 이 시간 동안 페이지는 표시되지만 인터랙티브하지 않은 상태가 된다. 이를 "Time to Interactive (TTI)"라고 하며, 사용자 경험에 영향을 미칠 수 있다.
대규모 애플리케이션에서는 코드 분할(Code Splitting)과 지연 로딩(Lazy Loading)을 통해 이 문제를 완화할 수 있다. 필요한 코드만 먼저 로드하여 핵심 기능의 하이드레이션을 우선적으로 수행하는 방식이다.
결론
이 글에서는 바닐라 자바스크립트를 통해 CSR, SSR, 클라이언트 사이드 라우팅, 하이드레이션의 핵심 개념과 작동 원리를 살펴보았다. 이러한 이해를 바탕으로 Next.js, Nuxt.js와 같은 프레임워크를 사용할 때 발생할 수 있는 문제들을 더 효과적으로 디버깅하고 해결할 수 있을 것이다.
실제 프로젝트에서는 이러한 작업을 직접 구현하기보다는 검증된 프레임워크를 사용하는 것이 효율적이지만, 내부 동작 원리를 이해하는 것은 더 나은 개발자가 되기 위한 중요한 단계이다.
핵심 요약
- 클라이언트 사이드 라우팅: 페이지 전환 시 전체 페이지를 다시 로드하지 않고 JavaScript로 콘텐츠만 변경하는 기술
- 서버 사이드 렌더링(SSR): 서버에서 HTML을 생성하여 브라우저에 전송, 초기 로딩 속도와 SEO 개선
- 하이드레이션: 서버에서 받은 정적 HTML에 JavaScript 기능을 주입하여 인터랙티브하게 만드는 과정
- 최적화: 초기 데이터가 있을 경우 중복 작업을 건너뛰어 효율성 향상
이러한 웹 렌더링 방식의 진화는 더 빠르고, 반응성 있으며, 검색 엔진 친화적인 웹 애플리케이션을 만들기 위한 끊임없는 노력의 결과이다. 각 방식의 장단점을 이해하고 적절히 활용함으로써, 최적의 사용자 경험을 제공하는 웹 애플리케이션을 개발할 수 있다.