본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로웹 개발 › 프론트엔드 › Vue

SPA(Single Page Application)

목차

SPA(Single Page Application)

SPA는 처음에 하나의 HTML 문서를 받아 두고, 이후 화면의 일부를 JavaScript로 바꾸며 동작하는 웹 애플리케이션 방식이다. 메뉴를 누를 때마다 새 HTML 문서를 내려받는 방식과 비교하면 흐름이 달라진다.

flowchart TD
    A[사용자가 메뉴 클릭] --> B{이동 방식}
    B -->|문서 전체 이동| C[서버에서 새 HTML 응답]
    C --> D[브라우저가 문서 다시 로드]
    B -->|SPA 내부 이동| E[라우터가 화면 상태 변경]
    E --> F[필요하면 API로 데이터 요청]
    F --> G[현재 문서의 화면 갱신]

SPA에서도 서버와 통신한다. 다만 내부 경로를 이동할 때 HTML 문서 전체를 매번 새로 로드할 필요가 없고, 필요한 데이터를 가져와 현재 화면을 바꾸는 경우가 많다. MDN의 SPA 설명도 단일 문서와 동적 콘텐츠 갱신을 핵심으로 설명한다.

화면 전환 한 번에 일어나는 일

주문 목록 /orders에서 /orders/42로 이동한다고 하자. 전체 문서 이동 방식은 서버에 새 문서를 요청하고 브라우저가 HTML을 파싱해 새 화면을 만든다. SPA 내부 이동은 라우터가 링크를 처리하고 기존 문서 안에 주문 상세 컴포넌트를 표시한다. 필요한 경우 /api/orders/42에 데이터를 요청한다.

이때 문서가 하나라는 말은 모든 데이터와 코드를 처음에 내려받는다는 뜻이 아니다. 상세 페이지의 JavaScript를 이동 시점에 내려받을 수도 있고, 데이터를 미리 가져올 수도 있다. 반대로 SPA라고 해서 서버 요청이 없는 것도 아니다. HTML 문서 요청과 API·코드 요청의 역할이 달라지는 것이다.

전체 문서 이동
  GET /orders/42 → HTML → 문서 파싱 → 화면

SPA 내부 이동
  링크 클릭 → 클라이언트 라우터 → 상세 화면 코드
                              → GET /api/orders/42 → 상태 갱신

라우터, 화면 코드, 데이터 요청 중 어느 단계가 느린지 분리해서 측정해야 한다. 초기 HTML이 빨리 도착해도 큰 JavaScript의 다운로드·실행 때문에 실제 조작 가능 시점은 늦을 수 있다.

URL은 화면 상태를 다시 만들 수 있어야 한다

사용자가 주문 상세 URL을 복사해 새 창에서 열었을 때도 같은 주문이 표시돼야 한다. 목록 화면에서만 보관한 메모리 변수에 전적으로 의존하면 직접 접속이 실패한다. 주문 ID는 경로에, 검색어·페이지·정렬처럼 공유할 상태는 쿼리 문자열에 두는 식으로 URL의 책임을 정한다.

History API는 문서를 다시 로드하지 않고 주소 기록을 추가할 수 있다. 다음은 라우터 라이브러리 내부 원리를 보여 주는 작은 예제이며, 링크 가로채기나 접근성 처리까지 갖춘 완성된 라우터는 아니다.

function renderLocation() {
  const path = window.location.pathname;
  document.querySelector('#current-path').textContent = path;
}

function navigate(path) {
  history.pushState({}, '', path);
  renderLocation();
}

window.addEventListener('popstate', renderLocation);
renderLocation();

HTML에 <p id="current-path"></p>가 있다고 가정한다. navigate('/orders/42')는 주소 기록을 넣은 뒤 화면을 갱신한다. pushState() 호출 자체는 popstate를 발생시키지 않으므로 직접 렌더 함수를 호출했다. 뒤로·앞으로 이동할 때는 popstate를 통해 현재 주소를 다시 해석한다. MDN History API

실제 라우터는 동적 경로, 중첩 화면, 이동 취소, 스크롤 복원, 같은 경로의 다른 매개변수 등을 처리한다. 이 동작을 직접 만들기 전에 검증된 라우터가 제공하는 범위와 요구 사항을 비교한다.

새로 고침하면 왜 404가 발생하는가

앱 내부에서 /orders/42로 이동했을 때는 라우터가 처리했지만, 새로 고침하면 서버에 그 경로의 문서를 요청한다. 서버가 /orders/42 파일을 찾다가 실패하면 앱을 시작할 HTML 자체를 받지 못한다.

HTML5 History 모드에서는 애플리케이션 경로에 대해 진입 HTML을 응답하도록 서버를 설정한다. 단, API나 존재하지 않는 JavaScript 파일까지 모두 HTML로 바꾸면 안 된다. 예를 들어 /assets/app.js 요청에 HTML을 주면 스크립트 오류로 나타나 원인을 찾기 어려워진다. 앱 진입 경로의 fallback과 정적 파일·API 경로를 구분한다. Vue Router의 History 모드 설정

해시 모드는 /#/orders/42처럼 # 뒤에 경로를 둔다. 이 부분은 일반적인 HTTP 문서 요청에 포함되지 않아 서버 경로 설정이 단순해진다. 대신 URL 모양과 검색·공유 요구에 맞는지 판단해야 한다. History 모드에서 모든 경로가 HTML을 받는다면 앱 안에서도 알 수 없는 경로의 404 화면을 따로 정의한다.

요청 순서와 표시 순서가 달라지는 문제

사용자가 주문 42를 열자마자 43으로 이동했다고 하자. 43의 응답이 먼저 도착하고 42의 응답이 뒤늦게 도착하면 현재 화면에 42가 표시될 수 있다. SPA는 문서가 유지되므로 이런 비동기 작업의 수명을 직접 관리해야 한다.

이전 요청을 AbortController로 취소하거나 요청 번호·현재 경로를 비교해 최신 요청만 반영한다. 페이지가 바뀌면 타이머, 전역 이벤트 리스너, 구독도 정리한다. 화면에서 컴포넌트를 제거하는 것과 외부에서 시작한 작업을 중단하는 것은 같은 일이 아니다.

또한 서버 오류와 빈 결과를 구분한다. 네트워크가 끊긴 화면에서 기존 목록을 유지할지, 재시도 버튼을 보여 줄지, 수정 중 입력을 보존할지도 정해야 한다. 페이지 이동이 부드럽다는 인상은 이 상태 처리가 갖춰졌을 때 생긴다.

초기 렌더링·검색·접근성의 선택

방식첫 HTML의 콘텐츠이후 화면 전환
클라이언트 렌더링 SPA앱을 시작하는 뼈대 중심일 수 있음클라이언트가 화면과 데이터 처리
SSR을 쓰는 앱서버가 초기 화면 HTML 생성hydration 후 SPA처럼 동작할 수 있음
정적 생성 사이트빌드 시 경로별 HTML 생성문서 이동 또는 클라이언트 라우팅 결합

SPA와 SSR은 반드시 서로 배타적인 선택이 아니다. 첫 문서의 생성 방식과 이후 탐색 방식을 나눠 생각하면 된다. 공개 콘텐츠는 검색 엔진과 공유 미리보기가 읽을 제목·본문이 필요하고, 로그인 후 업무 화면은 조작과 상태 유지가 더 중요한 경우가 많다.

클라이언트가 화면을 바꿀 때는 문서 제목, 주요 영역의 포커스, 로딩 상태 안내도 갱신한다. URL과 내용만 바뀌고 키보드 포커스가 사라진 요소에 남으면 보조 기술 사용자는 이동을 파악하기 어렵다. 새로 고침, 직접 URL 접속, 뒤로 가기, 느린 네트워크, 키보드 이동을 한 흐름으로 확인해야 SPA의 사용 경험을 평가할 수 있다.

같은 카테고리의 글