SPA 개념
문서 탐색과 SPA의 소프트 탐색을 비교하고 초기 렌더링·라우팅·History·데이터 갱신이 맡는 역할을 이해합니다.
지금까지 웹이 동작하는 원리와 HTML, CSS, 자바스크립트의 핵심 문법, 브라우저 API를 익혔고, 9장과 10장에서는 통신, 저장, 라우팅, 테스트, 성능까지 기초를 다졌습니다. 11장에서는 이 지식을 프런트엔드 애플리케이션의 구조로 연결합니다.
예전의 웹 페이지는 정적이거나, 이동할 때마다 페이지 전체를 새로 불러오는 방식이 일반적이었습니다. 하지만 사용자는 이제 웹에서도 앱처럼 빠르고 부드러운 반응을 기대하고, 이 요구에 맞춰 널리 쓰이게 된 방식이 싱글 페이지 애플리케이션(single page application, SPA)입니다. SPA는 지금의 문서를 그대로 두고 자바스크립트로 화면의 일부와 주소를 바꾸는 방식으로 화면을 이동합니다. 첫 HTML을 서버에서 만들거나 미리 만들어 둘 수도 있으므로, HTML 파일이 몇 개인지로 SPA인지를 가리지는 않습니다. 이 글에서는 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로 기능을 보강하면 지원 범위 안에서 기본 동작을 유지할 수 있다.
위 그림은 웹 애플리케이션을 만들 때 고르는 두 가지 축을 보여 줍니다. 첫 번째 축은 첫 화면의 HTML을 언제 만드는지입니다. 정적 사이트 생성(static site generation, SSG)은 빌드하거나 다시 생성할 때 미리 만든 HTML을 보내며, 바뀐 내용이 언제 반영되고 캐시를 언제 갱신할지 정해야 합니다. 서버 사이드 렌더링(server-side rendering, SSR)은 요청이 올 때마다 서버가 그 요청에 맞는 HTML을 만들며, 요청마다 다른 데이터와 응답 시간을 관리해야 합니다. 클라이언트 사이드 렌더링(client-side rendering, CSR)은 브라우저가 자바스크립트와 데이터를 받아 화면을 만들며, 첫 로딩 시간과 실패했을 때의 경험을 확인해야 합니다. SSG와 SSR을 Next.js로 구현하는 방법은 Next.js 교재의 정적 데이터 생성 (SSG)과 서버 사이드 렌더링 (SSR)에서 다룹니다.
두 번째 축은 다음 페이지로 어떻게 이동하는지입니다. 문서 탐색을 쓰는 멀티 페이지 애플리케이션(multiple page application, MPA)은 링크를 따라 새 HTML 문서를 요청하고 브라우저가 페이지를 바꿉니다. 소프트 탐색(soft navigation)을 쓰는 SPA는 클라이언트 라우터가 주소와 데이터, 화면을 바꾸며, 문서 제목과 포커스, 뒤로 가기까지 직접 관리해야 합니다.
두 축은 섞어 쓸 수 있습니다. 첫 요청에는 SSG나 SSR로 완성된 HTML을 보내고, 그 뒤의 링크 이동은 클라이언트 라우터가 맡는 방식이 흔합니다. 이때 서버가 만든 HTML에 자바스크립트의 동작을 연결하는 과정을 하이드레이션(hydration)이라고 합니다. 그림 아래의 설명처럼 점진적 향상(progressive enhancement)은 어느 축에든 더할 수 있는 설계 원칙으로, 기본 링크와 폼을 HTML로 먼저 제공하고 자바스크립트로 기능을 더하면 지원 범위 안에서 기본 동작을 지킬 수 있습니다.
MPA와 SPA 비교
전통적인 MPA
MPA에서는 일반 링크나 폼이 새 문서 탐색을 시작하면, 브라우저가 목적지의 HTML 문서를 받아 페이지를 바꿉니다. 이 HTML은 정적 파일일 수도 있고, 요청에 따라 서버가 만든 응답일 수도 있습니다. 뒤로 가기를 누르면 브라우저의 뒤로/앞으로 캐시(bfcache)가 이전 문서를 그대로 복원하기도 하므로, 모든 이동이 새로 내려받는 것은 아닙니다.
MPA는 첫 HTML 응답에 주요 콘텐츠가 들어 있어 검색 엔진의 크롤러가 내용을 읽기 쉽고, 필요한 리소스만 받으므로 첫 페이지가 빨리 보일 수 있습니다. 반면 페이지를 옮길 때마다 화면 전체가 바뀌며 깜빡이고, 네트워크 지연 때문에 느리게 느껴질 수 있습니다. 새 문서를 해석하고 상태를 다시 준비하는 비용도 생기는데, 공통 CSS와 자바스크립트를 실제로 다시 받는지는 HTTP 캐시 정책에 달려 있습니다(MDN의 HTTP 캐시). 요청마다 HTML을 만든다면 서버의 계산 비용도 들지만, 정적 파일과 CDN, 캐시 구성에 따라 달라지므로 MPA라서 서버 부담이 더 크다고 단정할 수는 없습니다.
SPA
SPA는 처음에 문서와 첫 화면에 필요한 HTML, CSS, 자바스크립트를 불러옵니다. 그 뒤로는 문서를 새로 불러오는 대신 자바스크립트로 화면을 바꾸고, 필요하면 데이터나 추가 코드를 서버에서 받아 화면의 일부만 갱신합니다. 주소로 화면을 나누는 라우터는 History API로 주소와 방문 기록을 바꾸고, 뒤로 가기나 앞으로 가기를 누르면 popstate 이벤트에서 지금 주소에 맞게 화면을 다시 그립니다. 주소만 바꾼다고 화면이 저절로 바뀌지는 않습니다.
SPA는 화면을 옮길 때 깜빡임이 없고 필요한 부분만 바뀌어 앱처럼 부드럽습니다. 이미 받아 둔 코드와 캐시를 다시 쓰므로 빠르게 전환할 수 있지만, 추가 코드나 데이터가 필요한 화면의 속도는 그 요청에 달려 있습니다. 문서 전체를 반복해 받지 않아도 되지만, 실제 전송량은 코드 분할과 데이터, 캐시 구성에 따라 달라집니다. 화면을 브라우저에서 그리는 구조라면 서버는 API를 제공하는 일에 집중할 수 있고, SSR이나 SSG를 함께 쓴다면 첫 HTML을 만드는 역할도 따로 나눠야 합니다.
단점도 있습니다. 첫 로딩 때 많은 자바스크립트와 CSS를 받아야 해서 첫 화면이 늦어질 수 있고, 이는 코드 분할로 줄일 수 있습니다. 자바스크립트로만 그린 콘텐츠는 검색 엔진이 완전히 읽기 어려울 수 있어 SSR이나 SSG로 보완합니다. 자바스크립트가 꺼져 있거나 오류가 나면 애플리케이션이 제대로 동작하지 않을 수 있고, 화면을 오래 유지하며 컴포넌트를 만들고 지우는 과정에서 메모리 관리에 주의하지 않으면 누수가 생길 수 있습니다.
보안도 따로 챙겨야 합니다. 클라이언트 라우터에서 메뉴를 숨기는 것은 서버의 접근 권한 검사를 대신하지 못하므로, 서버는 API마다 권한을 확인해야 합니다. XSS는 MPA에서도 생길 수 있으므로, 바깥에서 들어온 값을 DOM에 넣는 방식과 서버의 데이터 접근 검사를 함께 관리합니다.
더 깊이: MPA의 뒤로 가기가 빠른 이유
MPA에서 뒤로 가기를 누르면 이전 페이지를 서버에서 다시 받아 처음부터 그릴 것 같지만, 실제로는 거의 바로 이전 화면이 나타나는 경우가 많습니다. 브라우저의 뒤로/앞으로 캐시(back/forward cache, bfcache) 덕분입니다. bfcache는 페이지를 떠날 때 자바스크립트 힙까지 포함한 페이지 전체를 메모리에 스냅샷으로 보관했다가, 뒤로나 앞으로 이동하면 그 상태를 그대로 되살립니다. 그래서 서버에 다시 요청하지 않고, 자바스크립트의 변수까지 떠나기 직전의 상태로 돌아옵니다.
이 캐시는 모든 페이지에 적용되지 않습니다. 데스크톱의 Firefox는 unload 이벤트 리스너를 등록한 페이지를 bfcache에 넣지 않으며, 예전에는 Chrome도 그랬습니다. 그래서 페이지를 떠날 때의 처리는 unload 대신 pagehide 이벤트에 둡니다. 또 문서 응답에 Cache-Control: no-store를 보내면 그동안 브라우저가 그 페이지를 bfcache에 넣지 않았으므로, 로그인한 사용자의 페이지는 이 캐시의 도움을 받지 못할 수 있습니다.
되살아난 페이지에서는 load 이벤트가 다시 일어나지 않고, 대신 pageshow 이벤트의 persisted가 true가 됩니다. 페이지를 떠난 동안 바뀌었을 수 있는 장바구니 개수 같은 값은 이때 다시 확인합니다. Chrome 개발자 도구의 Application 패널에 있는 Back/forward cache 항목에서 테스트를 실행하면 그 페이지가 캐시되지 않는 이유를 볼 수 있으며, 자세한 내용은 web.dev의 bfcache 설명에 있습니다.
SPA를 이루는 기술
SPA의 핵심은 지금의 문서 안에서 주소와 화면, 상태를 서로 맞게 바꾸는 것입니다. 다음 API들은 필요에 따라 조합하는 수단이며 모두 꼭 필요한 것은 아닙니다. 주소의 # 뒤 부분(해시)으로 화면을 나누는 라우팅도 가능합니다.
Fetch API는 서버에서 데이터만 비동기로 받아 옵니다. History API는 같은 출처의 주소와 방문 기록을 바꾸는데, pushState()와 replaceState()는 새 문서를 가져오지도 popstate를 발생시키지도 않습니다. 그래서 라우터가 새 탐색 뒤에는 직접 화면을 그리고, 기록을 이동할 때는 popstate에서 화면을 복원해야 합니다(MDN의 pushState(), popstate 이벤트).
웹 스토리지는 사용자 설정이나 장바구니 초안처럼 브라우저에 남겨 둘 값을 저장할 때 씁니다. 저장된 값은 서버가 권한을 판단하는 기준이 될 수 없고, 자바스크립트로 읽을 수 있는 저장소에 로그인 토큰을 그냥 보관하는 방식은 피해야 합니다(OWASP의 HTML5 보안 지침). 웹 워커와 서비스 워커는 무거운 계산을 메인 스레드에서 떼어 내거나, 캐시와 오프라인 지원으로 SPA의 성능과 사용 경험을 높이는 데 씁니다.
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 정책을 적용한다 | 경로 변경과 별도 자원 완료를 연결한다 |
위 그림은 소프트 탐색에서 데이터가 흐르는 순서입니다. 첫 HTML 문서가 주소의 기준점이 되고, 브라우저가 화면을 그리거나 서버의 HTML에 동작을 연결합니다. 그 뒤 링크를 눌러 이동하려는 의도나, 뒤로 가기와 앞으로 가기의 popstate가 모두 같은 경로 판정(route match)으로 모입니다.
경로가 정해지면 이전 요청을 취소하고 새 데이터를 불러오며, 늦게 도착한 오래된 응답은 버립니다. 그다음 새 탐색일 때만 pushState()나 replaceState()로 기록을 남기고, 화면과 문서 제목, 포커스를 함께 반영합니다. popstate로 들어온 경우에는 기록을 다시 쓰지 않습니다. 그림 아래의 설명처럼 이것은 데이터를 먼저 확정하는 라우터의 개념 예시이며, 실제 요청과 기록, 화면 반영의 순서는 구현마다 다릅니다. 어떤 순서든 주소와 최신 응답, 제목, 포커스가 서로 맞아야 하고 오류도 처리해야 합니다.
직접 접근과 새로고침
/posts/7 같은 주소를 직접 입력하거나 새로고침하면 브라우저는 서버에 그 경로의 문서를 요청합니다. 그래서 서버가 그 경로의 HTML을 제공하거나 앱의 진입 HTML을 돌려주도록 설정되어 있어야 합니다. 클라이언트에서 pushState()가 잘 동작했다고 직접 접근까지 검증된 것은 아니며, 앱의 경로와 API, 정적 파일의 경로는 서버에서 서로 다르게 응답해야 합니다.
주소와 화면을 함께 바꾸기
다음은 프레임워크 없이 History API의 역할을 확인하는 가장 작은 예제입니다. 같은 출처의 HTTP 문서로 열고, 예제의 모든 경로가 이 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번 글 링크를 누르면 주소는 /posts/7, 제목과 화면은 7번 글이 됩니다. 뒤로 가기를 누르면 popstate 처리로 /posts와 글 목록이 복원됩니다. titleForPath()는 /posts에서 글 목록, /posts/7에서 7번 글, 그 밖의 경로에서 페이지를 찾을 수 없음을 돌려줍니다.
새 탐색 뒤의 render()를 빼면 주소만 바뀌고 이전 화면이 남습니다. 반대로 popstate 안에서 다시 pushState()를 호출하면, 뒤로 가는 동작마다 새 기록이 쌓여 정상적인 뒤로 가기를 방해합니다. 보조 키를 누른 클릭은 가로채지 않아 새 탭 열기 같은 브라우저의 기본 동작을 지킵니다.
SPA가 널리 쓰이는 이유
여러 단점이 있는데도 SPA가 널리 쓰이게 된 데는 몇 가지 이유가 있습니다. 먼저 데스크톱과 모바일 앱에 익숙해진 사용자가 웹에서도 빠르고 매끄러운 반응을 기대하게 되었습니다. 또 화면과 API의 역할을 나눠 프런트엔드와 백엔드를 따로 개발할 수 있게 되었습니다. 다만 서버의 역할이 항상 API로만 줄어드는 것은 아니며, 첫 화면을 어떻게 만들지와 함께 정합니다.
React, Vue, Angular 같은 프레임워크와 라이브러리가 SPA를 만드는 효율적인 도구와 패턴을 제공하면서, 복잡한 화면도 컴포넌트 단위로 만들고 상태를 관리하기 쉬워졌습니다. 데이터 처리 로직과 설계 지식을 다른 플랫폼에서 다시 쓸 수도 있는데, React Native는 DOM 대신 각 플랫폼의 기본 UI 구성 요소를 쓰므로 웹의 화면 코드가 그대로 동작하지는 않습니다(React Native의 핵심 컴포넌트 소개).
정리
- 첫 화면을 언제 만드는지(SSG, SSR, CSR)와 다음 페이지로 어떻게 이동하는지(MPA, SPA)는 따로 고르는 두 축이며 섞어 쓸 수 있습니다.
- MPA는 링크마다 새 문서를 받아 페이지를 바꾸고, SPA는 지금 문서를 유지한 채 클라이언트 라우터가 주소와 데이터, 화면을 바꿉니다.
- SPA는 전환이 부드럽지만, 첫 로딩, 검색 엔진, 자바스크립트 의존, 메모리 관리에 주의해야 하며 코드 분할과 SSR, SSG로 보완합니다.
- 라우터는 새 탐색 뒤에 직접 화면을 그리고
popstate에서는 기록을 다시 쓰지 않고 복원하며, 제목과 포커스도 함께 맞춥니다. - 주소를 직접 입력하거나 새로고침하면 서버가 먼저 응답하므로, 앱의 경로에 진입 HTML을 돌려주도록 서버를 설정해야 합니다.
- 클라이언트에서 메뉴를 숨기는 것은 권한 검사가 아니므로, 서버가 API마다 권한을 확인해야 합니다.
연습 문제
1. 뒤로 가기 뒤에 남은 화면
링크를 클릭하면 글이 잘 보이는데, 뒤로 가기를 누르면 주소는 /posts인데 화면에는 7번 글이 남아 있습니다. 서버의 HTML 응답과 popstate 처리 가운데 어디부터 확인해야 할까요? /posts/7을 새 탭에서 직접 열었을 때 404가 나오는 문제도 같은 방법으로 해결될까요?
해설 보기
popstate 처리부터 확인합니다. 지금 문서를 유지한 채 기록을 이동했는데 주소만 바뀌었으므로, popstate 리스너가 등록되어 있는지와 지금 경로를 읽어 화면을 그리는지 살펴봐야 합니다. 이때 새 기록을 추가하지 않고 /posts에 맞는 화면과 제목, 포커스를 갱신해야 합니다.
직접 열었을 때의 404는 서버가 /posts/7 문서를 제공하는지의 문제라서, 클라이언트에 리스너를 추가하는 것으로는 해결되지 않습니다. 서버나 호스팅 설정에서 앱의 경로에 진입 HTML을 돌려주도록 해야 합니다.
2. 두 축으로 분류하기
어떤 블로그는 첫 요청에 서버가 완성된 HTML을 만들어 보내고, 그 뒤 글 목록에서 글을 누르면 클라이언트 라우터가 데이터만 받아 화면을 바꿉니다. 이 블로그는 MPA일까요, SPA일까요? 그림의 두 축으로 설명해 보세요.
해설 보기
첫 화면은 SSR로 만들고, 페이지 이동은 SPA의 소프트 탐색을 쓰는 조합입니다. 첫 화면을 언제 만드는지와 다음 페이지로 어떻게 이동하는지는 따로 고르는 축이라서, 한 단어로 MPA나 SPA로만 부르기보다 두 축을 함께 적는 편이 정확합니다.
이 방식은 첫 HTML에 콘텐츠가 들어 있어 검색 엔진과 첫 화면 표시에 유리하고, 그 뒤의 이동은 부드럽습니다. 대신 서버의 HTML에 자바스크립트 동작을 연결하는 하이드레이션 과정과, 서버와 클라이언트가 같은 화면을 만드는지를 관리해야 합니다.
3. 숨긴 관리자 메뉴
클라이언트 라우터에서 로그인한 사용자가 관리자가 아니면 /admin 메뉴와 화면을 숨기도록 만들었습니다. 이것으로 관리자 기능이 보호되었다고 할 수 있을까요?
해설 보기
할 수 없습니다. 클라이언트의 코드는 사용자가 개발자 도구로 바꾸거나, 화면을 거치지 않고 관리자 API를 직접 호출할 수 있습니다. 화면을 숨기는 것은 사용 편의를 위한 것이지 권한 검사가 아닙니다.
관리자 기능의 API는 서버에서 요청마다 사용자의 권한을 확인하고, 권한이 없으면 거부해야 합니다. 클라이언트의 숨김은 그 위에 더하는 화면 처리일 뿐입니다.
다음 글 React 기초 (컴포넌트, JSX, 상태 관리)에서는 SPA를 만드는 대표적인 라이브러리인 React로 컴포넌트를 만들고, JSX와 상태로 화면을 그리는 방법을 살펴봅니다.