← Blog
렌더링 & 프레임워크

SPA란 무엇인가 — 페이지 전환을 브라우저가 맡는다는 것

한 줄 요약

SPA는 첫 로드 시 HTML/JS 번들만 받고, 이후 페이지 전환을 서버 요청 없이 브라우저 JS로 처리하는 방식. SvelteKit 기본은 "첫 응답은 SSR, 이후 전환은 SPA"인 하이브리드다.

왜 알아야 했나

  • SvelteKit을 쓰면서 "이거 항상 SPA 아닌가?"라는 의문이 생김. 라우팅 전환 시 <html>이 깜빡이지 않고, 네트워크 탭에 HTML 요청도 없었기 때문.
  • 실제로는 전환 동작만 SPA와 같았던 것이고, 첫 응답이 SSR이냐 빈 껍데기냐가 핵심 구분점이라는 걸 알게 됨.
  • streaming SSR 페이지에서 JS를 끄니 데이터가 화면에 안 나오는 현상을 만나면서 progressive enhancement의 동작 원리까지 파고들게 됨.

핵심 개념

1. SPA vs MPA

  • MPA (전통적): 링크 클릭마다 서버에 새 HTML 요청 → 전체 페이지 새로고침(깜빡임).
  • SPA: 처음에 JS 앱 전체를 받고, 이후 전환은 서버에 HTML 재요청 없이 JS가 화면 일부만 교체(필요한 데이터는 JSON 등으로). 깜빡임 없음.

2. SvelteKit은 하이브리드 (SSR + SPA)

첫 진입 / 새로고침 시 서버 응답이후 페이지 전환 (JS 켜짐)
SvelteKit 기본완성된 HTML (SSR)SPA처럼 JS 라우팅
순수 SPA (adapter-static fallback, ssr=false)빈 HTML 껍데기SPA처럼 JS 라우팅
  • 전환 동작만 보면 둘 다 같아서 구분 안 됨. 차이는 오직 "첫 응답에 콘텐츠가 들어있느냐".

3. 구분이 중요한 이유

  • SEO / 링크 미리보기: 크롤러·공유 미리보기(카톡/트위터)는 첫 응답 HTML만 봄. SSR은 콘텐츠가 보이고, 순수 SPA는 빈 화면.
  • 초기 렌더링 속도: SSR은 HTML이 바로 보이고 JS가 뒤에서 hydration. SPA는 JS 다 받고 실행해야 첫 화면이 뜸.

4. Progressive Enhancement (점진적 향상)

SvelteKit의 설계 철학. JS는 필수가 아니라 경험을 향상시키는 레이어.

  • SvelteKit이 만든 링크는 결국 평범한 <a href="...">.
  • JS 켜짐: 클라이언트 라우터가 클릭을 가로채(preventDefault) JS로 전환 → SPA 방식, 깜빡임 없음.
  • JS 꺼짐: 가로챌 JS가 없으니 브라우저가 <a> 기본 동작 실행 → 서버에 새 HTML 요청, 전체 새로고침 → MPA 방식, 깜빡임 있음.
  • JS 꺼도 SSR 덕분에 콘텐츠 표시 + 링크 이동은 그대로 동작한다 (단, 전환은 SPA가 아닌 브라우저 기본 방식).

5. Streaming SSR의 예외

  • load 함수가 promise를 await 안 하고 그대로 반환하면 streaming.
  • 서버는 HTML 뼈대를 먼저 보내고, promise가 resolve되는 대로 HTML 맨 아래에 <script>__sveltekit_dev.resolve(...)</script> 형태로 데이터를 흘려보냄.
  • 이 데이터를 화면에 꽂는 건 클라이언트 JS(__sveltekit_dev.resolve). JS 끄면 데이터는 HTML 하단에 텍스트로 도착해 있지만 화면엔 안 그려짐.
  • progressive enhancement가 적용되되, streaming한 부분만 JS(향상 레이어)에 의존한다.
await (non-streaming)streaming (promise 반환)
첫 HTML 응답데이터까지 완성된 HTML뼈대만, 데이터는 빈 채로
데이터 도착 방식HTML 안에 이미 박혀있음맨 아래 script로 나중에 흘려보냄
JS 끄면콘텐츠 보임빈 화면

예시 / 코드

순수 SPA 모드로 만들기 (adapter-static)

// svelte.config.js
import adapter from '@sveltejs/adapter-static';

export default {
  kit: {
    adapter: adapter({
      fallback: 'index.html'  // SPA의 핵심: 어떤 경로든 index.html 하나 반환
    })
  }
};
// src/routes/+layout.js
export const ssr = false;       // 서버 렌더링 끔
export const prerender = false;
  • fallback: 'index.html': 서버는 모든 경로 요청에 빈 index.html을 돌려주고, 그 안의 JS가 현재 URL을 보고 화면을 그림. 서버는 라우팅에 관여 안 함.

Streaming (JS 꺼지면 데이터 안 보임)

// +page.js 또는 +page.server.js
export function load() {
  return {
    posts: fetchPosts()   // await 안 함 → streaming
  };
}

Streaming 끄기 (JS 없어도 데이터 보이게)

export async function load() {
  return {
    posts: await fetchPosts()  // await → HTML에 박혀서 나감
  };
}
  • 트레이드오프: 느린 데이터를 기다리느라 첫 응답이 그만큼 늦어짐.

헷갈렸던 점

  • "라우팅할 때 HTML이 안 깜빡이고 네트워크에 HTML 요청도 없으니 SvelteKit은 항상 SPA 아닌가?" → 전환 동작만 SPA와 같을 뿐, 첫 응답은 SSR이라 하이브리드다.
  • "JS를 꺼도 라우팅이 되네?" → 그건 SPA 라우팅이 아니라 브라우저의 <a> 기본 동작(새 HTML 요청, 전체 새로고침). SSR이 각 경로를 제대로 렌더링해주기 때문에 가능.
  • "streaming 페이지에서 JS 끄니 데이터가 화면 맨 아래 텍스트로만 보이고 화면엔 안 나옴" → 버그 아님. streaming 데이터를 화면에 꽂는 resolve 함수가 JS라서, JS 끄면 안 그려지는 정상 동작.

실제 테스트 결과 ✅

  • JS 끈 상태에서 라우팅 → HTML을 새로 불러옴 (network 탭에 새 document 요청), 화면 깜빡임 발생. → 브라우저 기본 동작(MPA 방식)임을 확인.
  • JS 다시 켠 상태 → 첫 진입(또는 새로고침)에서만 HTML을 불러오고, 이후 전환은 HTML 요청 없이 JS로 처리. → SPA 방식임을 확인.
  • streaming SSR 페이지 + JS 끄고 새로고침__sveltekit_dev.resolve(...) 데이터가 HTML 맨 아래에 붙어서 오긴 하는데 화면엔 아무것도 안 나옴. → streaming 데이터는 JS 의존임을 확인.

구분 체크리스트

확인 항목SPA 전환 (JS 켜짐)브라우저 전환 (JS 꺼짐)
HTML document 요청없음 (데이터만)있음 (새 HTML)
화면 깜빡임없음있음
<html> 태그안 깜빡임깜빡임(문서 교체)

빠른 SSR vs 순수SPA 구분법: 크롬 개발자도구 → ⚙️ → Disable JavaScript 체크 후 새로고침. SSR이면 콘텐츠 보임, 순수 SPA면 빈 화면.

실무 적용

  • 순수 SPA가 적합: 로그인 후 쓰는 관리자 대시보드/내부 툴(SEO 불필요), 백엔드 API가 따로 있고 프론트만 정적 호스팅하고 싶을 때.
  • SSR(하이브리드) 권장: SEO·공유 미리보기가 중요한 블로그/마케팅/커머스.
  • streaming 사용 시 주의: 느린 데이터로 첫 화면을 막고 싶지 않을 때 유용하지만, 크롤러·JS 비활성 환경에서 그 데이터가 꼭 보여야 한다면 await로 전환할 것.
  • 관련 작업: [[SvelteKit Adapter]]

더 공부할 것

  • Hydration이 정확히 어떻게 일어나는지 (서버 HTML + 클라이언트 JS 결합)
  • ssr / prerender / csr 페이지 옵션 각각의 동작 차이
  • adapter-static fallback SPA에서 SEO 보완 방법 (prerender 가능한 페이지만 미리 렌더링)
  • streaming SSR에서 로딩 UI({#await}) 처리 패턴
  • progressive enhancement form actions (use:enhance)

🔗 참고

  • SvelteKit 공식 문서: Page options (ssr/csr/prerender)
  • SvelteKit 공식 문서: Loading data (streaming with promises)
  • SvelteKit 공식 문서: Adapters (adapter-static, adapter-node)