SPA 이해
MPA·SPA 탐색 구조와 CSR·SSR·SSG 렌더링 전략을 분리하고, 문서 진입·클라이언트 이동·라우팅 책임을 비교합니다.
MPA(Multi Page Application)와 SPA(Single Page Application)는 URL을 이동할 때 새 문서를 여는지, 실행 중인 문서 안에서 화면을 바꾸는지를 구분하는 탐색 구조입니다.
반면 CSR(Client-Side Rendering), SSR(Server-Side Rendering), SSG(Static Site Generation)는 첫 화면의 HTML을 어디에서 언제 만드는지를 설명합니다. 두 분류는 같은 축이 아니므로 SPA가 반드시 CSR이고 MPA가 반드시 SSR인 것은 아닙니다.
React는 UI를 렌더링하는 라이브러리입니다. 라우터, 렌더링 전략, 서버 설정, 캐시와 배포 방식은 React 하나만으로 결정되지 않습니다.탐색 구조와 렌더링 전략은 서로 다른 축입니다
React · navigation × rendering
MPA·SPA는 문서 수명 주기를, CSR·SSR·SSG는 초기 HTML 생성 시점을 설명합니다. 어느 한쪽을 골라도 다른 축의 여러 전략을 조합할 수 있습니다.
| 분류축 | 선택 항목 | 무엇을 결정하나 | 함께 쓸 수 있는 조합 |
|---|---|---|---|
| 탐색 구조 | MPA | URL 이동 때 서버·CDN에 문서를 요청하고 새 문서를 활성화합니다. | SSR·SSG가 흔하지만, 각 문서가 CSR로 UI를 완성하는 구성도 가능합니다. |
| 탐색 구조 | SPA | 최초·직접 진입은 문서를 요청하고, 실행 뒤 적합한 내부 이동은 현재 문서에서 라우터가 처리합니다. | CSR뿐 아니라 SSR·SSG HTML을 hydrate한 뒤 client navigation을 이어 갈 수 있습니다. |
| 렌더링 전략 | CSR | 브라우저가 JavaScript 실행 뒤 초기 UI를 만듭니다. | SPA 진입 문서 또는 MPA의 개별 문서 안에서 사용할 수 있습니다. |
| 렌더링 전략 | SSR | 요청 시점에 서버가 URL의 초기 HTML을 만듭니다. | 그대로 문서 탐색을 이어 가거나, hydrate 후 같은 문서 탐색으로 전환할 수 있습니다. |
| 렌더링 전략 | SSG | 빌드 시점에 경로의 HTML을 만들어 정적 호스트·CDN에서 제공합니다. | 각 정적 문서로 이동하거나, hydrate 후 client navigation을 사용할 수 있습니다. |
- 탐색축 · MPA
- URL 이동마다 새 문서를 요청하고 활성화합니다. 물리적인 HTML 파일이나 요청 시점 SSR을 뜻하지는 않습니다.
- 탐색축 · SPA
- 최초·직접 진입은 문서 요청입니다. 앱이 실행된 뒤 적합한 내부 이동만 현재 문서에서 history와 client router로 처리합니다.
- 렌더링축 · CSR
- 브라우저 JavaScript가 초기 UI를 만듭니다. SPA뿐 아니라 MPA의 개별 문서에도 사용할 수 있습니다.
- 렌더링축 · SSR
- 서버가 요청 시점에 초기 HTML을 만듭니다. hydrate 뒤 client navigation을 계속할 수도 있습니다.
- 렌더링축 · SSG
- 빌드 때 경로 HTML을 만듭니다. 정적 문서 탐색과 hydrate된 SPA 모두 구성할 수 있습니다.
SEO와 성능은 구조 이름이 아니라 URL·링크·상태 코드·초기 콘텐츠, JavaScript·hydration 비용, route chunk·data 요청과 캐시를 함께 측정해 판단합니다.
MPA: 이동할 때 새 문서를 여는 구조
MPA에서는 일반적인 링크 이동이나 주소 입력이 브라우저의 문서 탐색(document navigation)을 일으킵니다. 브라우저는 목적 URL을 서버나 CDN에 요청하고 응답받은 HTML을 새 문서로 파싱합니다. 이전 문서의 JavaScript 실행 환경은 일반적으로 끝나지만, 브라우저가 문서를 bfcache에 보관해 뒤로·앞으로 이동에서 복원할 수도 있습니다.
여기서 “여러 페이지”는 URL마다 정적인 HTML 파일이 반드시 하나씩 있다는 뜻이 아닙니다. 서버가 요청 때 HTML을 만들 수도 있고(SSR), 빌드 때 만든 파일을 제공할 수도 있으며(SSG), 응답 문서가 브라우저에서 나머지 UI를 그릴 수도 있습니다(CSR).
MPA의 성질도 구현과 배포에 따라 달라집니다.
- 새 문서 탐색은 브라우저가 제목, 초점, 스크롤과 문서 수명 주기를 기본 동작으로 처리한다는 장점이 있습니다.
- 공통 CSS·JavaScript·이미지는 HTTP 캐시에서 재사용될 수 있으므로 페이지마다 모든 리소스를 항상 다시 내려받는 것은 아닙니다.
- 서버 렌더링 비용은 캐시, CDN, 정적 생성 여부에 따라 달라집니다. “MPA이면 서버 부하가 크다”라고 단정할 수 없습니다.
- 문서 전환 비용은 있지만, 작은 문서와 캐시·프리페치·뒤로-앞으로 캐시(bfcache)를 잘 활용한 사이트가 SPA보다 반드시 느린 것은 아닙니다.
SPA: 실행 중인 문서 안에서 탐색하는 구조
SPA도 최초 진입, 주소창 입력, 새로고침, 외부 링크 이동에서는 URL의 HTML 문서를 요청합니다. 차이는 앱이 실행된 뒤의 클라이언트 내부 탐색에 있습니다.
라우터는 처리 가능한 같은 출처 링크의 기본 문서 이동을 대신하고 History API로 주소 기록을 갱신한 뒤, 현재 URL의 pathname을 라우트 규칙에 맞춥니다. path parameter와 search parameter는 화면의 대상·보기 조건에, hash는 문서 안 위치나 앱이 정한 상태에 사용할 수 있습니다. 이어서 필요한 컴포넌트와 데이터를 준비하고 React가 현재 문서의 UI를 갱신합니다. 뒤로 가기와 앞으로 가기에서도 라우터가 변경된 history 항목을 다시 해석합니다.
이 과정에서 네트워크 요청이 사라지는 것은 아닙니다. 라우트별 코드 분할을 사용하면 아직 받지 않은 JavaScript 청크를 가져올 수 있고, 화면 데이터도 새로 요청할 수 있습니다. 반대로 이미 받은 청크나 데이터는 캐시 정책에 따라 재사용할 수 있습니다.
SPA가 주는 대표적인 이점은 공통 레이아웃과 일부 메모리 상태를 유지하면서 전환할 수 있다는 점입니다. 그러나 초기 JavaScript, hydration, 데이터 waterfall, 지나치게 큰 번들로 인한 비용이 생길 수 있으므로 “SPA이면 항상 더 빠르다”는 보장은 없습니다.
CSR·SSR·SSG와 hydration
렌더링 전략은 탐색 구조와 별도로 선택하며, 프레임워크는 라우트마다 전략을 섞기도 합니다.
- CSR: 서버가 앱 진입 문서와 리소스를 보내고, 브라우저의 JavaScript가 초기 UI를 만듭니다. 이미 렌더된 React 마크업이 없다면 일반적으로
createRoot로 시작합니다. - SSR: 서버가 요청 시점에 URL의 초기 HTML을 만듭니다. 응답을 캐시하거나 스트리밍할 수도 있으므로 “요청마다 항상 같은 비용으로 전체 HTML을 동기 생성한다”는 뜻은 아닙니다.
- SSG: 빌드 시점에 경로의 HTML을 만들어 정적 호스팅이나 CDN에서 제공합니다. 콘텐츠 갱신 시점과 재생성 정책은 배포 시스템이 정합니다.
- hydration: SSR·SSG 등으로 미리 생성한 React HTML을 상호작용 가능한 앱으로 이어 붙이는 과정입니다.
hydrateRoot에 전달한 첫 클라이언트 결과는 서버가 만든 마크업과 일치해야 합니다. 정적인 문서나 순수 CSR에는 hydration이 반드시 필요한 것은 아닙니다.
검색 엔진 최적화도 MPA·SPA 이름만으로 결정되지 않습니다. URL별 고유 제목·메타데이터와 링크, 실제 콘텐츠가 담긴 초기 HTML, 올바른 HTTP 상태 코드, 크롤러가 필요한 리소스에 접근할 수 있는지까지 함께 확인해야 합니다. CSR 콘텐츠를 처리하는 검색 엔진도 있지만 모든 크롤러가 같은 수준으로 JavaScript를 실행하는 것은 아니므로, 검색 노출과 빠른 초기 콘텐츠가 중요하면 SSR·SSG 같은 사전 렌더링을 검토합니다.
첫 진입·내부 이동·직접 링크의 책임
SPA의 런타임을 이해하려면 “어떤 URL인가?”보다 먼저 “현재 앱이 이미 실행 중인가?”를 물어야 합니다.
React · navigation boundary
직접 진입·새로고침은 서버·CDN의 문서 경계에서 시작하고, 실행 중인 앱의 내부 이동은 client router가 history와 화면을 연결합니다.
- 1 · 최초 진입·직접 링크·새로고침
- 브라우저가 현재 URL을 문서로 요청합니다. 내부 client route보다 서버·CDN의 경로 처리가 먼저입니다.
- 2 · 호스트 경계
- 유효한 URL에는 route별 SSR·SSG HTML 또는 SPA 진입 문서를 반환합니다. 존재하지 않는 경로는 실제
404여야 합니다. - 3 · React 시작
- client-only shell은
createRoot로 렌더링합니다. 미리 렌더된 React HTML을 상호작용 가능하게 만들 때는 동일한 첫 결과로hydrateRoot를 수행합니다. - 4 · 실행 중인 앱의 내부 이동
- 적합한 링크를 라우터가 받아 history를 갱신하고 pathname을 라우트에 맞춥니다. params·search·hash는 화면 입력으로 쓰며, 필요한 chunk·CSS·data는 cache, prefetch 또는 network에서 옵니다.
- 5 · 화면과 경계 검증
- React가 UI를 갱신한 뒤 title·focus·scroll·pending/error 안내를 확인합니다. 클라이언트 route guard와 별개로 API·서버가 권한을 강제합니다.
같은 문서의 history 이동은 라우터가 화면을 복원하고, 문서 간 뒤로·앞으로 이동은 브라우저 bfcache가 전체 문서를 복원할 수도 있습니다. 어느 경우든 URL 공유와 재진입 결과를 함께 시험합니다.
직접 링크와 새로고침
사용자가 /products/42를 주소창에 입력하거나 그 주소에서 새로고침하면 브라우저는 서버에 /products/42 문서를 요청합니다. 배포 계층은 다음 중 하나를 수행해야 합니다.
- SSR·SSG 경로라면 해당 URL의 문서를 반환합니다.
- 하나의 진입 문서를 쓰는 client-routed SPA라면 유효한 앱 경로를 진입 HTML로 연결합니다. 이 fallback이 없으면 내부 이동은 되지만 직접 링크에서 404가 날 수 있습니다.
- 존재하지 않는 경로는 실제 404 상태를 반환해야 합니다. API·정적 자산·잘못된 URL까지 무조건 진입 HTML과
200으로 바꾸면 오류 처리와 검색 색인이 왜곡됩니다.
React, 라우터, 서버가 맡는 범위
- 브라우저: 문서 탐색, URL과 history, HTTP 캐시와 문서 수명 주기를 담당합니다.
- 서버·CDN: URL에 맞는 문서·fallback·404, 캐시 헤더, 보안 헤더를 결정합니다.
- 라우터: 실행 중인 앱에서 URL을 라우트에 맞추고, 링크 탐색·params·search·라우트 데이터·코드 분할을 연결합니다.
- React: 선택된 컴포넌트 트리를 렌더링하고 상태 변화에 맞춰 DOM을 갱신하며, 미리 렌더된 React HTML에는 hydration으로 연결됩니다.
- API·서버: 입력 검증과 인증·인가를 매 요청에서 강제합니다. 클라이언트 라우트 가드나 숨긴 버튼은 사용자 경험을 위한 보조 수단일 뿐 보안 경계가 아닙니다.
React 자체는 라우터도, SSR·SSG 배포 시스템도 아닙니다. 작은 기존 문서의 일부에 React를 추가할 수도 있고, MPA의 각 문서에 독립적인 React 루트를 둘 수도 있으며, 프레임워크로 서버 렌더링한 SPA를 만들 수도 있습니다.
성능·보안·접근성 확인표
구조 이름보다 실제 사용자 흐름을 측정하고 검증합니다.
- 성능: 초기 HTML의 유용한 콘텐츠, JavaScript와 라우트 청크 크기, hydration 비용, 데이터 요청 waterfall, 캐시 적중과 재방문 전환을 각각 확인합니다.
- 보안: MPA와 SPA 모두 XSS가 생길 수 있습니다. 신뢰하지 않은 HTML 삽입을 피하고 CSP 같은 방어를 적용하며, 비밀 값과 최종 권한 판정은 클라이언트 번들에 맡기지 않습니다.
- 접근성: 실제
href가 있는 링크와 의미 있는 문서 구조를 유지합니다. 클라이언트 탐색은 새 문서를 열지 않으므로 라우트 변경 뒤 문서 제목, 주 콘텐츠 초점, 로딩·완료 알림과 스크롤 복원을 앱과 라우터가 명시적으로 설계해야 합니다. - 오류 처리: 최초 진입과 내부 이동 모두에서 로딩, 네트워크 실패, 권한 부족, 찾을 수 없는 라우트가 올바른 상태와 복구 동작을 보여야 합니다.
다음 절에서는 React Router를 사용해 URL과 컴포넌트를 연결하는 방법을 배웁니다. 이때도 라우터가 React의 렌더링 책임이나 서버의 보안·배포 책임을 대신하지 않는다는 경계를 유지하세요.