현대 웹 애플리케이션의 사용자 경험(UX)은 데스크톱 앱이나 모바일 앱과 유사할 정도로 부드럽고 빠릅니다. 이러한 변화의 중심에는 SPA(Single Page Application) 라는 아키텍처가 있습니다. React는 바로 이 SPA를 구축하기 위한 가장 대표적인 라이브러리 중 하나입니다.
이번 글에서는 전통적인 웹 방식인 MPA와 SPA를 비교하며 SPA의 핵심 원리를 알아보고, React가 어떻게 SPA 구조를 따르는지, 그리고 SPA의 한계를 극복하기 위한 렌더링 전략(SSR, SSG)에는 무엇이 있는지 살펴보겠습니다.
SPA는 Single Page Application의 약자로, 이름 그대로 단 하나의 HTML 페이지로 전체 애플리케이션을 운영하는 구조를 의미합니다.
index.html 파일을 서버로부터 받아오고, 그 이후의 모든 화면 전환은 JavaScript가 기존 페이지의 일부를 동적으로 다시 그리면서 이루어집니다.결과적으로 SPA는 페이지 전환 시 깜빡임이 없어 사용자 경험이 매우 뛰어나고, 서버와의 불필요한 통신을 줄여 애플리케이션의 반응 속도를 높입니다.
두 방식의 차이점을 표로 정리하면 다음과 같습니다.
| 구분 | MPA (Multi Page Application) | SPA (Single Page Application) |
|---|---|---|
| HTML 구조 | 각 페이지마다 별도의 HTML 파일 존재 | index.html 하나만 존재 |
| 페이지 전환 방식 | 요청 시 서버에서 새 HTML을 응답 → 전체 페이지 리로드 | 클라이언트(JS)가 필요한 부분만 교체 렌더링 |
| 속도 | 초기 로드는 빠를 수 있으나, 페이지 이동 시 느림 | 초기 로드는 다소 무거울 수 있으나, 페이지 이동은 매우 빠름 |
| 서버 역할 | 화면을 그리는 HTML을 계속 제공 (렌더링 주체) | 초기 로드 후에는 데이터(API)만 제공 (데이터 공급자) |
| 사용자 경험(UX) | 페이지 전환 시 깜빡임(Blinking) 발생 | 부드럽고 네이티브 앱과 유사한 사용자 경험 |
| SEO (검색 엔진 최적화) | 각 페이지가 완성된 HTML이라 검색 엔진 친화적 | CSR 기반일 경우, 별도 처리(SSR/SSG)가 필요할 수 있음 |
| 구현 복잡도 | 상대적으로 단순하고 직관적 | 클라이언트 측 라우팅, 상태 관리 등 고려할 요소가 많음 |
React는 SPA를 구축하기 위해 태어난 라이브러리라고 해도 과언이 아닙니다. 우리가 npx create-react-app이나 npm create vite@latest로 React 프로젝트를 시작하면 전형적인 SPA 구조가 만들어집니다.
<!-- public/index.html -->
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
index.html 파일은 앱의 전체적인 뼈대, 즉 껍데기 역할만 합니다.<div id="root"></div> 안으로 JavaScript에 의해 동적으로 채워집니다. 이 div가 React 앱의 유일한 진입점(Entry Point) 입니다.root div를 루트 컨테이너(Root Container)로 삼아, 그 안에서 컴포넌트 트리를 만들고 확장하며 전체 UI를 관리합니다.그렇다면 HTML 파일이 하나뿐인데 어떻게 여러 페이지(화면)를 보여줄 수 있을까요? 그 원리는 다음과 같습니다.
index.html과 React 관련 JavaScript 번들 파일(e.g., main.jsx가 변환된 파일)을 다운로드합니다.<div id="root"> 안에 <App />과 같은 최상위 컴포넌트를 렌더링하여 첫 화면을 그립니다.SPA가 여러 페이지처럼 보이게 만드는 기술의 핵심은 라우팅(Routing) 입니다. React에서는 주로 react-router-dom 라이브러리를 사용하여 클라이언트 측 라우팅을 구현합니다.
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
/이면 <Home /> 컴포넌트를 보여줍니다./about이면 <About /> 컴포넌트를 보여줍니다.중요한 점은, 이때도 브라우저는 서버에 다른 HTML을 요청하지 않는다는 것입니다. react-router-dom이 URL 변경을 감지하고, 그에 맞는 컴포넌트를 <div id="root"> 안에서 교체해줄 뿐입니다.
이처럼 뛰어난 UX를 제공하는 SPA에도 몇 가지 본질적인 한계가 존재합니다.
기본적인 SPA는 CSR(Client-Side Rendering) 방식으로 동작합니다. 즉, 브라우저가 초기에 받는 index.html은 거의 비어있고, JavaScript가 실행되어야 비로소 콘텐츠가 채워집니다. 검색 엔진 크롤러는 JavaScript를 완전히 실행하지 못할 수 있어, 페이지의 콘텐츠를 제대로 수집(인덱싱)하지 못하는 문제가 발생할 수 있습니다.
애플리케이션의 기능이 많아질수록 JavaScript 번들 파일의 크기가 커집니다. 사용자는 이 파일을 모두 다운로드하고 실행이 끝나야 첫 화면을 볼 수 있으므로, 초기 로딩 속도가 느려질 수 있습니다.
이러한 SPA(CSR)의 한계를 보완하기 위해 SSR(Server-Side Rendering) 과 SSG(Static Site Generation) 라는 렌더링 방식이 등장했습니다.
SSR (Server-Side Rendering)
SSG (Static Site Generation)
index.html을 사용합니다.따라서 현대 웹 개발에서는 프로젝트의 성격에 따라 CSR, SSR, SSG 중 가장 적합한 렌더링 방식을 선택하거나 혼합하여 사용하는 전략이 중요해졌습니다.