SPA 개념
문서 탐색과 SPA의 소프트 탐색을 비교하고 초기 렌더링·라우팅·History·데이터 갱신이 맡는 역할을 이해합니다.
우리는 지금까지 웹의 기본 동작 원리와 HTML/CSS/JavaScript 핵심 문법, 그리고 브라우저 API 활용법을 학습했습니다.
특히 9장과 10장을 거치며 통신, 저장, 라우팅, 성능, 테스트까지 기반을 다졌습니다.
이제 그 지식을 프론트엔드 아키텍처로 연결할 단계입니다.
과거 웹 페이지는 정적이거나, 이동할 때마다 전체를 새로고침하는 방식이 일반적이었습니다.
하지만 사용자는 이제 웹에서도 앱처럼 빠르고 부드러운 반응을 기대합니다.
이 요구를 충족하기 위해 등장한 패러다임이 Single Page Application (SPA)입니다.
11장 프론트엔드는 SPA 개념을 시작으로 프론트엔드 프레임워크의 등장 배경과 핵심 원리를 연결해 설명합니다.
이번 절에서는 SPA가 왜 필요했고 어떤 장단점이 있는지부터 정리합니다.
웹 페이지를 마치 데스크톱 애플리케이션처럼 동작하게 만드는 것이 Single Page Application (SPA)의 핵심 목표입니다.
단일 페이지 애플리케이션은 현재 문서를 유지하면서 JavaScript로 화면 일부와 URL을 갱신하는 탐색 방식을 사용합니다. 초기 HTML을 SSR·SSG로 만들 수도 있으므로 HTML 파일의 개수만으로 SPA 여부를 정하지 않습니다.
TWO AXES
초기 HTML과 페이지 이동은 따로 선택한다
SSG·SSR·CSR은 첫 화면을 만드는 시점이다. MPA·SPA는 다음 페이지로 이동하는 방식이며, 두 축을 조합할 수 있다.
1 · 첫 화면은 언제 만드는가?
- SSG · 미리 생성
- 빌드·재생성 때 만든 HTML을 전달한다. 변경 반영 시점과 캐시 갱신을 정한다.
- SSR · 요청 때 생성
- 서버가 요청에 맞는 HTML을 만든다. 요청별 데이터와 응답 시간을 관리한다.
- CSR · 브라우저에서 생성
- 브라우저가 JavaScript와 데이터를 받아 UI를 만든다. 초기 로딩과 실패 시 경험을 확인한다.
2 · 다음 페이지는 어떻게 이동하는가?
- 문서 탐색 · MPA
- 링크를 따라 새 HTML 문서를 요청하고 브라우저가 페이지를 바꾼다.
- 소프트 탐색 · SPA
- 클라이언트 라우터가 URL·데이터·UI를 갱신한다. 제목·포커스·뒤로가기도 관리한다.
조합 예시 · SSG 또는 SSR + SPA 탐색
첫 요청에는 완성된 HTML을 전달하고, 이후 링크 이동은 클라이언트 라우터가 처리할 수 있다. hydration은 미리 만든 UI에 JavaScript 동작을 연결하는 과정이다.
점진적 향상은 두 축에 더할 수 있는 설계 원칙이다. 기본 링크·폼을 HTML로 제공한 뒤 JavaScript로 기능을 보강하면 지원 범위 안에서 기본 동작을 유지할 수 있다.
전통적인 웹 애플리케이션과의 비교
SPA의 개념을 이해하기 위해서는 먼저 전통적인 웹 애플리케이션의 동작 방식을 떠올려 볼 필요가 있습니다.
전통적인 웹 애플리케이션 (MPA: Multiple Page Application)- 동작 방식: 일반 링크·폼이 새 문서 탐색을 시작하면 브라우저는 목적지의 HTML 문서를 받아 페이지를 전환합니다. 이 HTML은 정적 파일일 수도, 요청에 따라 서버가 생성한 응답일 수도 있습니다. 뒤로가기에서는 브라우저의 뒤로/앞으로 캐시로 문서가 복원될 수도 있으므로 모든 이동을 새 다운로드와 같은 뜻으로 보지는 않습니다.
-
장점
- SEO(검색 엔진 최적화) 유리: 첫 HTML 응답에 주요 콘텐츠가 들어 있으면 크롤러가 이를 읽기 쉽습니다. URL마다 실제 HTML 파일이 있어야 하는 것은 아니며, SSR·SSG와 SPA 탐색을 조합할 수도 있습니다.
- 초기 로딩 빠름: 필요한 최소한의 리소스만 로드하므로 첫 페이지 로딩이 빠를 수 있습니다.
-
단점
- 사용자 경험 저해: 페이지 전환 시 전체 화면이 깜빡이고, 네트워크 지연으로 인해 느리게 느껴질 수 있습니다.
- 문서 전환 비용: 새 문서를 해석하고 상태를 다시 준비하는 비용이 생길 수 있습니다. 공통 CSS·JavaScript의 실제 전송 여부는 HTTP 캐시 정책에 달려 있으므로 매번 전부 다운로드한다고 가정하지 않습니다(HTTP 캐시 안내).
- 서버 부담: 요청마다 동적으로 HTML을 생성한다면 서버 계산 비용이 들 수 있습니다. 정적 파일·CDN·캐시 구성과 API 호출량에 따라 달라지므로 MPA라는 이유만으로 서버 부담이 더 크다고 단정하지 않습니다.
-
동작 방식
처음 문서와 초기 화면에 필요한 HTML, CSS, JavaScript 리소스를 로드합니다.
이후 전체 문서를 새로 로드하는 대신 JavaScript로 화면을 전환하고, 필요하면 데이터나 추가 코드를 서버에서 받아옵니다.
받아온 데이터를 바탕으로 페이지의 특정 부분만 동적으로 업데이트합니다(DOM 조작).
경로 기반 라우터는
History API로 URL과 방문 기록을 갱신하고, 뒤로가기/앞으로가기의popstate사건에서는 현재 경로에 맞게 화면을 다시 그립니다. URL 갱신만으로 화면 전환이 자동으로 이루어지는 것은 아닙니다. -
장점
- 매끄러운 사용자 경험: 페이지 전환 시 화면 깜빡임이 없고, 필요한 부분만 업데이트되므로 데스크톱 앱과 같은 부드러움을 제공합니다.
- 빠른 반응성: 준비된 코드·캐시를 재사용할 수 있어 빠르게 전환할 수 있습니다. 추가 코드나 데이터가 필요한 화면의 속도는 그 요청 비용에 따라 달라집니다.
- 네트워크 사용량 감소: 전체 문서를 반복해서 받지 않는 이점이 있지만, 실제 전송량은 코드 분할·데이터·캐시 구성에 따라 달라집니다.
- 모바일 앱 개발 용이: 웹과 모바일 앱 간의 코드 및 로직 공유가 용이하여, 하이브리드 앱 개발에 유리합니다.
- 서버 역할 분리: 클라이언트 렌더링 중심으로 구성하면 서버를 API 제공에 집중시킬 수 있습니다. SSR·SSG를 함께 쓰면 초기 HTML을 만드는 역할도 구분해야 합니다.
-
단점
- 초기 로딩 시간: 첫 로딩 시 애플리케이션에 필요한 모든(또는 많은) JavaScript, CSS 파일을 한꺼번에 로드하므로 초기 로딩 시간이 길어질 수 있습니다. (코드 스플리팅 등으로 완화 가능)
- SEO 불리: 검색 엔진 크롤러가 JavaScript로 동적으로 렌더링되는 콘텐츠를 완전히 파악하기 어려울 수 있습니다. (SSR, SSG 등으로 완화 가능)
- JavaScript 의존성: 브라우저에서 JavaScript가 비활성화되어 있거나 오류가 발생하면 애플리케이션이 제대로 동작하지 않을 수 있습니다.
- 메모리 누수 가능성: DOM을 동적으로 조작하고 컴포넌트를 생성/제거하는 과정에서 메모리 관리에 주의하지 않으면 메모리 누수가 발생할 수 있습니다.
- 보안 경계 관리: 화면에서 메뉴를 숨기는 클라이언트 라우팅 규칙은 서버의 접근 권한 검사를 대신하지 못합니다. XSS는 MPA에서도 발생할 수 있으므로 외부 입력을 DOM에 넣는 방식과 서버의 데이터 접근 검사를 함께 관리합니다.
SPA의 핵심 기술 요소 (복습)
SPA의 핵심은 현재 문서에서 경로·화면·상태를 일관되게 갱신하는 것입니다. 다음 API는 요구 사항에 따라 조합하는 수단이며 모두 필수는 아닙니다. URL의 해시를 사용하는 라우팅도 가능합니다.
Fetch API(또는XMLHttpRequest): 서버로부터 데이터를 비동기적으로 받아오기 위한 핵심 기술입니다. (데이터만 주고받음)History API: 같은 출처의 URL과 방문 기록을 갱신합니다.pushState()·replaceState()호출 자체는 새 문서를 가져오거나popstate를 발생시키지 않습니다. 라우터가 새 탐색 뒤 직접 렌더링하고, 방문 기록을 이동할 때popstate로 화면을 복원해야 합니다(pushState, popstate).Web Storage (로컬/세션 스토리지): 사용자 설정이나 장바구니 초안 등 브라우저에 유지할 데이터를 저장할 때 선택합니다. 저장된 데이터는 서버의 권한 판단 근거가 아니며, JavaScript에서 읽을 수 있는 저장소에 로그인 토큰을 무조건 보관하는 방식은 피합니다(OWASP 저장소 지침).Web Workers&Service Workers: 복잡한 연산을 메인 스레드에서 분리하거나, 오프라인 지원, 캐싱 등을 통해 SPA의 성능과 사용자 경험을 극대화하는 데 사용됩니다.
경로 기반 SPA의 /posts/7 주소를 직접 열거나 새로고침하면 브라우저는 서버에 그 경로의 문서를 요청합니다. 서버가 해당 경로의 HTML을 제공하거나 앱의 진입 HTML로 연결하도록 구성되어 있어야 합니다. 클라이언트에서 pushState()가 잘 동작했다는 사실만으로 직접 진입까지 검증된 것은 아닙니다. 앱 경로와 API·정적 자산 경로의 응답 규칙도 구분해야 합니다.
SPA 소프트 내비게이션 데이터 흐름 — 상태 추적
click과 popstate에서 URL·이전 요청·로딩·커밋·history 기록 여부가 변한다. 새 탐색과 복귀에서 pushState를 중복하지 않는 차이를 현재 상태로 추적할 가치가 있다.
DATA-FLOW · B99 WEB FUNDAMENTALS
SPA 소프트 내비게이션 데이터 흐름
소프트 탐색은 새 문서를 불러오는 대신 URL·데이터·UI를 갱신한다. 새 링크 이동과 뒤로가기는 같은 라우트 계산을 쓰지만, 뒤로가기에서는 history를 다시 기록하지 않는다.
데이터를 먼저 확정하는 라우터의 개념 예시다. 실제 요청·history·화면 반영 순서는 구현에 따라 다르며, URL·최신 응답·제목·포커스의 일관성과 오류 처리가 필요하다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | 소프트 탐색은 새 문서를 불러오는 대신 URL·데이터·UI를 갱신한다. 새 링크 이동과 뒤로 가기는 같은 라우트 계산을 쓰지만, 뒤로 가기에서는 history를 다시 기록하지 않는다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 현재 경로 | 문서: 유지 중 / location: 현재 경로 | 실행 중인 SPA가 현재 location의 화면을 보여 준다 | 이미 실행 중인 문서의 내부 탐색을 전제로 한다 |
| 새 경로를 준비 | 요청: 새 경로의 현재 요청 / 이전 완료: 현재 화면 쓰기 불가 | 내부 이동은 history와 경로 입력을 바꾸며 이전 요청 소유권을 닫는다 | 오래된 완료보다 현재 경로를 우선한다 |
| 현재 경로 화면 | 문서: 동일 문서 / UI: 현재 location 결과 | 현재 요청만 UI에 반영하고 title/focus/scroll 정책을 적용한다 | 경로 변경과 별도 자원 완료를 연결한다 |
SPA 소프트 내비게이션 데이터 흐름 — 상태 추적
click과 popstate에서 URL·이전 요청·로딩·커밋·history 기록 여부가 변한다. 새 탐색과 복귀에서 pushState를 중복하지 않는 차이를 현재 상태로 추적할 가치가 있다.
DATA-FLOW · B99 WEB FUNDAMENTALS
SPA 소프트 내비게이션 데이터 흐름
소프트 탐색은 새 문서를 불러오는 대신 URL·데이터·UI를 갱신한다. 새 링크 이동과 뒤로가기는 같은 라우트 계산을 쓰지만, 뒤로가기에서는 history를 다시 기록하지 않는다.
데이터를 먼저 확정하는 라우터의 개념 예시다. 실제 요청·history·화면 반영 순서는 구현에 따라 다르며, URL·최신 응답·제목·포커스의 일관성과 오류 처리가 필요하다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | 소프트 탐색은 새 문서를 불러오는 대신 URL·데이터·UI를 갱신한다. 새 링크 이동과 뒤로 가기는 같은 라우트 계산을 쓰지만, 뒤로 가기에서는 history를 다시 기록하지 않는다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 현재 경로 | 문서: 유지 중 / location: 현재 경로 | 실행 중인 SPA가 현재 location의 화면을 보여 준다 | 이미 실행 중인 문서의 내부 탐색을 전제로 한다 |
| 새 경로를 준비 | 요청: 새 경로의 현재 요청 / 이전 완료: 현재 화면 쓰기 불가 | 내부 이동은 history와 경로 입력을 바꾸며 이전 요청 소유권을 닫는다 | 오래된 완료보다 현재 경로를 우선한다 |
| 현재 경로 화면 | 문서: 동일 문서 / UI: 현재 location 결과 | 현재 요청만 UI에 반영하고 title/focus/scroll 정책을 적용한다 | 경로 변경과 별도 자원 완료를 연결한다 |
이러한 기술들이 조합되어 SPA는 사용자에게 네이티브 앱과 같은 경험을 제공할 수 있게 됩니다.
URL과 화면을 따로 바꾸면 생기는 문제
다음은 프레임워크 없이 History API의 역할을 확인하는 최소 예제입니다. 브라우저에서 같은 출처의 HTTP(S) 문서로 열고, 예제의 모든 경로가 이 HTML로 응답하도록 준비한 환경을 가정합니다. 데이터는 코드 안의 두 문구를 사용하므로 API 요청·로딩·실패 처리는 포함하지 않습니다. 이는 개념 예제이며 이 교재 저장소의 라우터 설정을 뜻하지 않습니다.
<nav>
<a href="/posts" data-route>목록</a>
<a href="/posts/7" data-route>7번 글</a>
</nav>
<h1 id="view" tabindex="-1"></h1>
<script>
const view = document.querySelector('#view');
function titleForPath(path) {
if (path === '/posts') return '글 목록';
if (path === '/posts/7') return '7번 글';
return '페이지를 찾을 수 없음';
}
function render() {
view.textContent = titleForPath(location.pathname);
document.title = view.textContent;
view.focus();
}
document.querySelector('nav').addEventListener('click', (event) => {
const link = event.target.closest('a[data-route]');
if (!link || event.button !== 0 || event.ctrlKey || event.metaKey ||
event.shiftKey || event.altKey) return;
event.preventDefault();
history.pushState(null, '', link.getAttribute('href'));
render(); // pushState는 popstate를 발생시키지 않음
});
window.addEventListener('popstate', render); // history를 다시 쓰지 않음
render(); // 직접 진입한 URL도 같은 규칙으로 그림
</script>/posts에서 7번 글 링크를 누르면 URL은 /posts/7, 제목과 화면은 7번 글이 됩니다. 뒤로가기를 누르면 popstate 처리로 /posts와 글 목록이 복원됩니다. 새 탐색 뒤의 render()를 빼면 URL만 바뀌고 이전 화면이 남습니다. 반대로 popstate 안에서 다시 pushState()하면 되돌아가는 탐색에 새로운 기록을 추가하여 정상적인 뒤로가기를 방해합니다. 새로고침과 직접 진입은 별도로 서버 응답까지 확인합니다.
직접 진단할 문제: 링크 클릭 뒤에는 정상적으로 글이 보이지만, 뒤로가기를 누르면 주소는 /posts인데 화면에 7번 글이 남습니다. 이 증상에서는 서버의 새 HTML 응답과 popstate 처리 중 어디부터 확인해야 할까요? /posts/7을 새 탭에 직접 열었을 때 404가 나오는 문제도 같은 수정으로 해결될까요?
풀이 확인
현재 문서를 유지한 방문 기록 이동에서 URL만 복원되었으므로 먼저 popstate 리스너와 현재 경로를 읽는 렌더링을 확인합니다. 새 기록을 추가하지 않고 /posts에 맞는 UI·제목·포커스를 갱신해야 합니다. 직접 진입의 404는 서버가 /posts/7 문서를 제공하는지의 문제이므로 클라이언트 리스너만 추가해서 해결되지 않습니다. 예제의 경로 판정 함수는 /posts에서 글 목록, /posts/7에서 7번 글, 그 밖의 경로에서 페이지를 찾을 수 없음을 반환합니다.
왜 SPA가 현대 웹 개발의 주류가 되었는가?
SPA가 위에서 언급된 단점들에도 불구하고 현대 웹 개발의 대세가 된 이유는 다음과 같습니다.
- 향상된 사용자 경험에 대한 요구: 데스크톱 애플리케이션이나 모바일 앱에 익숙해진 사용자들은 웹에서도 빠르고 매끄러운 반응성을 기대합니다. SPA는 이러한 기대를 충족시킵니다.
- 프론트엔드와 백엔드의 분리: 클라이언트 UI와 API의 역할을 나누어 독립적으로 개발할 수 있습니다. 서버의 역할이 항상 API로만 제한되는 것은 아니며 초기 렌더링 방식과 함께 결정합니다.
- 강력한 프론트엔드 프레임워크의 등장: React, Vue, Angular와 같은 프론트엔드 프레임워크/라이브러리는 SPA 개발을 위한 효율적인 도구와 패턴을 제공하여, 복잡한 UI를 쉽게 구축하고 관리할 수 있도록 해줍니다.
- 개발 생산성 향상: 컴포넌트 기반 개발, 가상 DOM, 상태 관리 등 프레임워크의 기능은 개발 생산성을 크게 높입니다.
- 크로스 플랫폼 개발의 용이성: 데이터 처리 로직과 설계 지식을 다른 플랫폼에서 재사용할 수 있습니다. React Native는 DOM 대신 네이티브 UI 구성 요소를 사용하므로 웹 화면 코드가 그대로 동작한다고 가정하지 않습니다(React Native 소개).
SPA 개념 정리
핵심은 MPA와 SPA를 기능 선호가 아니라 렌더링/배포/SEO 요구 조건으로 구분하는 것입니다.
이번 장에서는 현대 웹 개발의 중심 패러다임인 Single Page Application (SPA)의 개념을 심도 있게 학습했습니다.
여러분은 전통적인 MPA 방식과 SPA 방식의 근본적인 차이, 그리고 각각의 장단점을 명확히 이해했습니다.
SPA는 초기 로딩 이후 필요한 부분만 갱신해 데스크톱 앱에 가까운 매끄러운 경험을 제공합니다.
이를 위해 Fetch API, History API 같은 웹 API가 핵심적으로 사용된다는 점도 확인했습니다.
또한 SPA가 주류가 된 배경으로 높아진 사용자 경험 기대치, 프론트엔드/백엔드 역할 분리, 그리고 프레임워크 생태계의 성장까지 함께 정리했습니다.
SPA의 소프트 내비게이션은 첫 로드 이후 라우팅, 데이터 요청, 부분 갱신으로 이어집니다.
SPA는 현대 웹 개발의 핵심적인 개념입니다. 이제 다음 절부터 React, Vue와 같은 프론트엔드 프레임워크가 이 흐름을 어떻게 구현하는지 구체적으로 살펴볼 준비가 되었습니다.