본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Frontend

웹 아키텍처의 다음 전환: MPA에서 PESPA까지

작성 2026. 10. 13.추가
목차12개 항목
출처와 참고

Kent C. Dodds의 글 「The Web's Next Transition」(EpicWeb.dev, 2024.03)을 요약·재구성한 노트다. 단계별 코드 변화는 TodoMVC로 만든 예제 저장소 kentcdodds/the-webs-next-transformation에서 볼 수 있다.

출발점

HTTP, HTML, CSS, JavaScript는 1990년대 중반에 표준이 됐다. 그 뒤로 웹은 문서 플랫폼에서 앱 플랫폼이 됐고, 앱을 만드는 아키텍처도 바뀌어 왔다. 바탕에는 처음부터 브라우저와 서버가 양방향으로 대화하게 해 준 두 요소, <a>(읽기)와 <form>(쓰기)이 있다.

네 가지 아키텍처는 "코드가 어디에 사는가"로 비교하면 차이가 선명하다. 비교 기준은 여섯 가지다.

영속화(persistence) · 라우팅 · 데이터 읽기 · 데이터 변경(mutation) · 렌더링 · UI 피드백

1. MPA (Multi-Page App)

거의 모든 코드가 서버에 있다. 브라우저는 탭의 로딩 표시 같은 UI 피드백만 맡는다.

  • 문서 요청: 서버가 URL을 라우팅하고, 데이터를 읽고, HTML을 그려 보낸다.
  • 변경: 폼을 제출하면 서버가 DB를 고치고 리다이렉트로 응답한다. 브라우저는 GET으로 새 화면을 받는다. 리다이렉트를 쓰는 이유는 POST가 히스토리에 남아 뒤로 가기 때 다시 전송되는 것을 막기 위해서다.
장점단점
요청/응답 하나로 끝나는 단순한 멘탈 모델매번 전체 새로고침이라 포커스 관리가 어렵다
자잘한 상호작용이 많은 UI에 부적합하다
페이지 전환 애니메이션, 낙관적 UI가 어렵다
로딩 피드백을 브랜드에 맞게 꾸밀 수 없다

2. PEMPA (Progressively Enhanced MPA)

점진적 향상(progressive enhancement)은 어디서나 동작하는 기본 버전을 먼저 만들고, 지원하는 브라우저에만 기능을 덧붙이는 방식이다(2003년 무렵 등장한 용어). XMLHttpRequest와 AJAX(2005년 무렵 유행한 용어)가 퍼지면서 jQuery, MooTools 시대가 열렸다.

  • 링크 클릭과 폼 제출을 JS로 가로채(preventDefault) API에서 데이터를 받아 화면 일부만 바꾼다.
  • 서버에는 MPA용 코드가 그대로 있고, AJAX용 엔드포인트와 클라이언트 코드가 추가로 생긴다.

문제점

  1. 브라우저 기본 동작을 대체하면서 생기는 버그: 경쟁 상태, 중복 제출, 에러 처리.
  2. 직접 짜야 하는 코드가 많다.
  3. 렌더링 코드 중복: 서버(예: PHP 템플릿)와 클라이언트(JS)에 같은 UI를 두 번, 그것도 다른 언어로 쓴다. JS로 고친 화면을 새로고침한 화면과 똑같게 유지하기가 매우 어렵다.
  4. 코드 구조: 상태와 렌더링을 맡는 중심이 없어 여기저기서 DOM을 직접 만졌다.
  5. 서버/클라이언트 간접성: 한쪽을 고치면 반대쪽도 고쳐야 하고, 흐름을 따라가기 어렵다.

3. SPA (Single Page App)

렌더링을 서버에서 완전히 걷어 낸다. 서버는 API만 제공하고, UI는 전부 클라이언트가 그린다. HTML은 CDN의 정적 파일로 나간다. 초기엔 거의 빈 HTML 껍데기였고, 이후 SSG로 아는 페이지는 빌드 때 미리 그리게 됐다. Backbone, Angular, Ember, React, Vue, Svelte 시대이고, 2010년대 중반부터 사실상 표준이다.

  • 얻은 것: 개발자 경험. 특히 렌더링 코드 중복이 사라졌다. CDN에서 빈 HTML을 빨리 주는 것만으로는 체감 성능이 크게 나아지지 않는다. 화면은 결국 JS와 데이터를 기다리기 때문이다.

문제점

  1. 번들 크기: JS가 점점 무거워졌다.
  2. 워터폴: 데이터 요청 코드가 번들 안에 있어서 문서 → 앱 번들 → 페이지 번들 → 컴포넌트 → 데이터 → 이미지 순으로 줄줄이 기다린다. 코드 스플리팅과 지연 로딩은 오히려 이걸 악화시킨다.
  3. 런타임 성능: 저사양 기기에서 클라이언트 JS가 부담이다.
  4. 상태 관리: 클라이언트가 서버 데이터를 들고 동기화해야 한다. 본질적으로 어려운 캐싱 문제이고, 그래서 상태 관리 라이브러리가 끝없이 나온다.

4. PESPA (Progressively Enhanced SPA)

SPA의 능력 + MPA의 멘탈 모델. JS 없이도 동작하는 앱을 기본으로 깔고, JS는 그 위에 경험을 향상시킨다. 핵심 이득은 "JS 없이도 된다"가 아니라(그건 부수 효과), 멘탈 모델이 훨씬 단순해진다는 점이다.

어떻게 동작하나

  • 브라우저 동작을 흉내 낸다. 클라이언트가 링크·폼을 가로채더라도 브라우저가 했을 일을 그대로 재현한다. 서버 입장에선 요청이 브라우저에서 왔든 JS에서 왔든 같은 코드가 돈다.
  • 변경 후엔 데이터를 다시 검증(revalidate)한다. 전체 새로고침이 했을 일을 대신하는 것이다.
  • 렌더링 코드는 하나다. 서버와 클라이언트 양쪽에서 렌더링되는 UI 라이브러리를 쓴다. 대기 상태나 낙관적 UI처럼 클라이언트 전용 향상 코드는 같은 곳에 함께 둔다(colocation).

데이터 변경 흐름

onClick + fetch가 아니라 폼 제출로 처리한다.

flowchart TB
  n1["#lt;form#gt; 제출"] --> n2["해당 라우트의 action<br/>(기본값: 현재 URL)"]
  n2 -->|"성공 응답"| n3["그 자리에서 갱신 (예: 좋아요)"]
  n2 -->|"리다이렉트"| n4["라우터가 새 라우트의 코드·데이터·에셋을 병렬로 로드"]
  n3 -->|"어느 쪽이든"| n5["현재 페이지 데이터를 재검증"]
  n4 -->|"어느 쪽이든"| n5

라우터는 URL만 보고도 필요한 코드·데이터·에셋을 미리 알기 때문에 병렬로 받고, 이동 전에 프리페치할 수 있다.

이전 아키텍처의 문제를 어떻게 푸나

이전 단계문제PESPA의 답
MPA전체 새로고침, 제한된 UI 피드백새로고침 없이 같은 개발 경험, UI 피드백은 완전히 직접 제어
PEMPA직접 짠 코드, 렌더링 중복, 간접성프레임워크가 브라우저를 흉내 내고, 렌더링은 하나, 프론트·백엔드 코드를 한곳에
SPA번들 크기, 워터폴, 런타임 성능, 상태 관리코드를 서버로, 라우트 단위 스플리팅, 병렬 로딩·프리페치, 요청/응답 모델이라 상태 관리 고민이 대부분 사라짐

PESPA의 특징 다섯 가지

  • 동작하는 기본 버전이 있고, JS는 가능하게 하는 게 아니라 향상시킨다
  • 코드·데이터·에셋의 지연 로딩과 똑똑한 프리페치
  • 코드를 서버 쪽으로 민다
  • UI 코드를 손으로 중복하지 않는다
  • 브라우저 동작을 투명하게 흉내 낸다

예상되는 반론

  • "SSG가 좋은데 서버 코드는 싫다": 실제 앱은 대부분 어차피 서버가 필요하다.
  • "서버 비용이 든다": 앱은 보통 API를 부르므로 SSG로도 비싼 서버 작업을 피하지 못한다. CDN의 HTTP 캐싱으로 렌더링 부담을 줄일 수 있다.
  • "SSR은 낯설다": 잘 다뤄 주는 도구를 쓰면 되고, 마이그레이션 중엔 클라이언트 전용으로 돌릴 수도 있다.

구현체: Remix와 그 밖의 프레임워크

  • Remix는 PESPA를 기본으로 제공한 첫 프레임워크다. 라우트마다 loader(읽기)와 action(변경)을 두고, 폼 제출 뒤 데이터를 자동으로 재검증하며, 대기·낙관적 상태에 점진적 향상을 지원한다. 이 기능은 2024년 React Router v7의 프레임워크 모드로 합쳐졌다.
  • 중첩 라우팅(nested routing)은 코드 구조를 정리하고 라우트 단위로 더 잘게 코드를 나누게 해 준다. Next.js App Router도 중첩 레이아웃과, <form action>에 Server Action을 넘기면 JS 없이도 제출되는 폼으로 같은 원칙을 따른다(서버 컴포넌트와 클라이언트 컴포넌트 조합 규칙).
  • SvelteKit(form actions), SolidStart도 같은 원칙을 따른다.
  • PESPA에 필요한 기능 자체는 전부터 있었다. 새로운 건 점진적 향상과 공유 렌더링을 합치는 데 초점을 맞췄다는 점이다.

한눈에 보기

렌더링라우팅·데이터JS 없이 동작핵심 약점
MPA서버서버O전체 새로고침
PEMPA서버 + 클라이언트 (중복)양쪽O코드 중복·복잡도
SPA클라이언트클라이언트 + APIX번들·워터폴·상태 관리
PESPA공유 (서버·클라이언트 같은 코드)라우터가 양쪽 조율OSSR 학습 비용, 서버 필요

더 읽을거리: Frontend Mastery 「The new wave of JavaScript web frameworks」, Jenna Smith 「Progressively enhance for a more resilient web」

관련 노트: SPA와 CSR·SSR, AJAX와 HTTP 요청, Next.js를 쓰는 이유, 프론트엔드의 등장 배경, 서버 컴포넌트와 클라이언트 컴포넌트 조합 규칙

원본 기록2

댓글

GitHub 계정으로 댓글을 남길 수 있어요.