개인정보처리방침© 2026 DEV BAK - 기술블로그. All rights reserved.
DEV BAK - 기술블로그
frontend

View Transitions API — 2025 Baseline 달성 이후, 라이브러리 없이 구현하는 프로덕션 전환 애니메이션

솔직히 처음 이 API를 봤을 때 "이게 된다고?" 싶었습니다. Framer Motion이나 GSAP 없이, CSS 몇 줄과 JavaScript 메서드 하나만으로 앱 같은 화면 전환을 구현한다는 게 너무 좋아 보였거든요. 그런데 실제로 써보니 정말 됩니다. 브라우저가 화면 전환 전후 스냅샷을 컴포지터 레이어에서 다루기 때문에, transform·opacity 기반 애니메이션 구간에서는 꽤 부드러운 결과가 나옵니다.

2025년 10월, Firefox 144 출시로 Chrome, Edge, Safari, Firefox 4대 브라우저가 모두 document.startViewTransition()을 지원하게 됐습니다. 폴리필이나 "Chrome만 됩니다" 주석 없이 프로덕션에 쓸 수 있는 기준점이 된 거죠. 이 글을 쓰는 이유가 바로 여기 있습니다. 기존 MPA에 전환 애니메이션을 처음 도입하려는 분, 또는 Framer Motion 같은 라이브러리 의존성을 걷어내고 싶은 분이라면 지금이 딱 맞는 타이밍입니다.

이 글에서는 API 작동 원리부터 프레임워크별 통합 패턴, 그리고 실무에서 빠지기 쉬운 함정까지 한 번에 다룹니다. SPA, MPA, Next.js, Astro, SvelteKit 어느 환경에서 작업하든 바로 적용해볼 수 있는 내용들로 채웠습니다.


핵심 개념

Baseline Newly Available: W3C와 브라우저 벤더들이 공동으로 정의한 상태입니다. Chrome, Edge, Safari, Firefox 최신 버전 모두에서 동작한다는 의미로, "이제 써도 되는 단계"의 공식 기준점입니다. View Transitions API(same-document)는 2025년 10월 이 상태에 도달했습니다.

브라우저가 전환을 처리하는 세 단계

작동 원리는 생각보다 단순합니다. 복잡한 애니메이션 라이브러리처럼 DOM을 직접 조작하는 게 아니라, 브라우저가 "변하기 전"과 "변한 후"를 각각 스냅샷으로 찍고 그 사이를 채워줍니다.

  1. document.startViewTransition(callback) 호출 → 브라우저가 현재 화면을 스냅샷
  2. callback 안에서 DOM 변경 수행
  3. 브라우저가 이전 스냅샷 → 새 DOM 사이를 기본 크로스페이드 또는 커스텀 CSS 애니메이션으로 전환
javascript
// 원리를 보여주는 최소 예시 (실제 프로젝트에서는 클래스 토글이나 상태 업데이트를 활용하게 됩니다)
document.startViewTransition(() => {
  container.innerHTML = newContent;
});

기본값은 크로스페이드이고, CSS로 원하는 대로 커스터마이즈할 수 있습니다.

SPA 방식과 MPA 방식

API는 크게 두 가지 모드로 나뉩니다.

Same-document (SPA 방식): JavaScript에서 document.startViewTransition()을 직접 호출합니다. 2025년 10월 기준 Chrome 111+, Edge 111+, Safari 18+, Firefox 144+에서 완전 지원됩니다. Baseline Newly Available 상태이므로 프로덕션 적용에 적합합니다.

Cross-document (MPA 방식): CSS @view-transition at-rule만으로 동일 오리진의 두 페이지 사이에 전환 애니메이션을 적용합니다. JavaScript가 전혀 필요 없습니다. Chrome 126+, Edge 126+, Safari 18.2+에서 지원하지만 Firefox는 아직 미지원 상태입니다.

css
/* 두 페이지 모두에 이 두 줄만 추가하면 됩니다 */
@view-transition {
  navigation: auto;
}

공유 요소 전환 (Shared Element Transition)

저도 처음에 이게 이렇게 쉽게 된다는 걸 믿지 못했는데, View Transitions API의 진짜 매력이 여기 있습니다. view-transition-name CSS 속성으로 특정 요소에 이름을 붙이면, 화면 전환 시 그 요소가 독립적으로 움직입니다. 상품 카드에서 상품 상세 페이지로 이동할 때 이미지가 자연스럽게 날아가는 그 효과입니다.

css
/* 목록 페이지의 카드 이미지 */
.product-card img {
  view-transition-name: product-hero;
}
 
/* 상세 페이지의 히어로 이미지 */
.product-detail .hero {
  view-transition-name: product-hero;
  /* 이미지 비율이 다를 때 늘어나는 현상 방지 */
  object-fit: cover;
}
 
/* 전환 애니메이션 커스터마이즈 */
::view-transition-old(product-hero),
::view-transition-new(product-hero) {
  animation-duration: 0.4s;
  animation-timing-function: ease-in-out;
}

두 페이지에서 같은 view-transition-name을 가진 요소가 발견되면, 브라우저가 알아서 그 사이를 보간해줍니다. 위치도, 크기도, 모양도요.

알아두면 좋은 것들 — Level 2 사양

W3C가 현재 활발히 개발 중인 Level 2 사양에는 실무에서 느꼈던 불편함을 해소해주는 기능들이 담겨 있습니다. 당장 쓸 수 있는 기능은 아니지만, 앞으로 어떻게 발전하는지 알아두면 도움이 됩니다.

view-transition-class: view-transition-name이 요소별 고유 ID라면, view-transition-class는 여러 요소에 공통 클래스를 부여해 동일한 애니메이션 스타일을 한 번에 적용합니다.

반복 요소의 자동 이름 생성: 카드 목록처럼 반복 요소마다 브라우저가 자동으로 내부 이름을 생성해주는 기능이 논의 중입니다. 현재 match-element, auto 등의 값으로 사양이 검토되고 있으며 아직 확정되지 않았습니다. 지금은 동적으로 생성된 리스트마다 유니크한 이름을 직접 부여해야 합니다.

:active-view-transition-type() 의사 클래스: 전환 타입에 따라 다른 CSS 애니메이션을 조건부로 적용할 수 있습니다. 아래 예시는 CSS nesting 문법(& 선택자)을 사용한 버전과 풀어 쓴 버전을 함께 담았습니다.

css
/* CSS nesting 문법 사용 */
:active-view-transition-type(slide-left) {
  &::view-transition-old(root) { animation-name: slide-out-left; }
  &::view-transition-new(root) { animation-name: slide-in-right; }
}
 
/* CSS nesting 없이 동일한 효과 */
:active-view-transition-type(slide-left)::view-transition-old(root) {
  animation-name: slide-out-left;
}
:active-view-transition-type(slide-left)::view-transition-new(root) {
  animation-name: slide-in-right;
}

실전 적용

MPA에서 JavaScript 없이 페이지 전환 애니메이션

가장 빠르게 효과를 볼 수 있는 방법입니다. Astro나 기존 멀티페이지 앱에서 JavaScript를 한 줄도 건드리지 않아도 됩니다.

css
/* 모든 페이지의 <head> 또는 전역 CSS에 추가 */
@view-transition {
  navigation: auto;
}
 
/* 기본 크로스페이드 대신 슬라이드 전환으로 변경 */
@keyframes slide-in-from-right {
  from { transform: translateX(30px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
 
@keyframes slide-out-to-left {
  from { transform: translateX(0);     opacity: 1; }
  to   { transform: translateX(-30px); opacity: 0; }
}
 
::view-transition-old(root) {
  animation: 300ms ease-in-out slide-out-to-left;
}
 
::view-transition-new(root) {
  animation: 300ms ease-in-out slide-in-from-right;
}
구성 요소 설명
@view-transition { navigation: auto; } 동일 오리진 페이지 이동 시 전환 자동 활성화
::view-transition-old(root) 떠나는 페이지(이전 스냅샷)의 애니메이션
::view-transition-new(root) 들어오는 페이지(새 DOM)의 애니메이션
root 전체 페이지 대상. 특정 요소 이름으로 교체 가능

Firefox 사용자에게는 애니메이션 없이 일반 페이지 이동이 발생합니다. 기능 자체는 정상 동작하므로 점진적 향상(Progressive Enhancement) 관점에서 문제없습니다.

프레임워크별 통합 패턴

가장 많이 쓰게 되는 패턴들을 모아뒀습니다. 프레임워크마다 통합 방식이 꽤 다릅니다.

Astro: 레이아웃 컴포넌트에 <ViewTransitions />를 한 번 선언하면 끝납니다. MPA 구조를 유지하면서 SPA 같은 전환을 얻는 가장 자연스러운 통합입니다.

astro
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>

Next.js (App Router): next.config.js에 플래그를 추가하면 <Link> 클릭 시 자동으로 startViewTransition이 트리거됩니다. Next.js 15+에서 지원됩니다. React 19.2의 <ViewTransition> 컴포넌트와 함께 쓰면 더 세밀한 제어가 가능합니다.

javascript
// next.config.js (Next.js 15+)
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    viewTransition: true,
  },
};
 
export default nextConfig;

<ViewTransition> 컴포넌트의 핵심은 목록과 상세 양쪽에서 같은 name을 써야 공유 요소 전환이 연결된다는 점입니다.

jsx
// 목록 페이지
import { ViewTransition } from 'react';
 
function ProductCard({ product }) {
  return (
    <ViewTransition name={`product-${product.id}`}>
      <img src={product.image} alt={product.name} />
    </ViewTransition>
  );
}
 
// 상세 페이지 — name이 같아야 두 요소가 하나의 전환으로 연결됩니다
function ProductDetail({ product }) {
  return (
    <ViewTransition name={`product-${product.id}`}>
      <img src={product.image} alt={product.name} className="hero" />
    </ViewTransition>
  );
}

SvelteKit: 공식 내장은 없지만, onNavigate 훅을 활용하는 패턴이 커뮤니티에서 잘 정착돼 있습니다. Promise를 반환하는 이유는 브라우저에게 전환 완료 시점을 명시적으로 알려주기 위해서입니다. 이 구조가 없으면 SvelteKit이 내비게이션을 언제 마무리해야 할지 알 수 없습니다.

javascript
// +layout.svelte 또는 +layout.js
import { onNavigate } from '$app/navigation';
 
onNavigate((navigation) => {
  // 미지원 브라우저에서는 그냥 통과
  if (!document.startViewTransition) return;
 
  // Promise를 반환해 브라우저가 전환 완료 시점을 알 수 있게 합니다
  return new Promise((resolve) => {
    document.startViewTransition(async () => {
      resolve();
      await navigation.complete;
    });
  });
});

접근성을 고려한 모션 처리

이게 빠지면 실무에서 꼭 한 번은 지적받습니다. prefers-reduced-motion을 브라우저가 자동으로 처리해주지 않아서 개발자가 직접 넣어야 합니다. 전정 장애나 모션 민감증이 있는 사용자에게 불편을 줄 수 있고, 접근성 감사에서도 자주 지적받는 항목입니다.

css
/* 전역 CSS에 처음부터 넣어두는 것을 권장합니다 */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

장단점 분석

장점

항목 내용
라이브러리 의존성 제거 Framer Motion, GSAP 없이 네이티브 CSS 애니메이션만으로 동작
컴포지터 레이어 처리 transform·opacity 전환 구간이 GPU에서 처리되어 부드러운 애니메이션
점진적 향상 미지원 브라우저는 전환 없이 정상 동작. document.startViewTransition 존재 여부로 분기 가능
MPA 적용 가능 @view-transition at-rule로 클라이언트 라우터 없이 페이지 전환 애니메이션 적용
레이아웃 시프트 없음 DOM 변경 전에 이미 스냅샷이 찍혀 있어 전환 중 레이아웃이 흔들리지 않음
Baseline 달성 2025년 10월부터 4대 브라우저 최신 버전 모두 지원 (same-document 기준)

이 중에서 실제로 가장 체감이 큰 건 MPA 적용 가능성입니다. 클라이언트 라우터를 붙이지 않아도 된다는 게, Astro나 기존 서버 렌더 앱 프로젝트에서는 상당히 큰 차이를 만들어냅니다.

컴포지터 레이어(Compositor Layer): 브라우저 렌더링 파이프라인에서 GPU가 직접 다루는 레이어입니다. transform, opacity 같은 속성이 이 레이어에서 처리되면 CPU 레이아웃 계산을 건드리지 않아 훨씬 부드럽게 동작합니다. View Transitions API는 스냅샷 전환 시 이 속성들을 기본으로 활용하기 때문에 성능 이점이 있습니다.

단점 및 주의사항

항목 내용 대응 방안
동시 전환 불가 문서당 하나의 전환만 실행 가능. 전환 중 새 전환 발생 시 이전 것이 건너뜀 전환 완료 후 다음 전환을 트리거하는 큐 구조 고려
view-transition-name 중복 동일 화면에 같은 이름이 두 개 이상이면 전환 전체 스킵 동적 리스트에는 ID 기반 고유 이름 부여 (Level 2에서 개선 예정)
콜백 블로킹 콜백이 resolve되기 전까지 사용자에게 이전 페이지가 멈춘 채로 보임 콜백 안에서 무거운 데이터 페치 금지. 데이터 먼저 로드 후 전환 시작 권장
접근성 이슈 전환 중 구/신 DOM 공존 시 스크린 리더 중복 발표, 포커스 혼동 가능 ARIA live region 관리, 전환 후 포커스 명시적 이동 처리
prefers-reduced-motion 미자동 처리 브라우저가 모션 감소 설정을 자동 반영하지 않음 CSS에 @media (prefers-reduced-motion: reduce) 직접 작성 필수
Firefox cross-document 미지원 MPA에서 @view-transition 사용 시 Firefox는 일반 전환 점진적 향상으로 대응. Interop 2026 논의 중

실무에서 가장 흔한 실수

  1. 동적 리스트에서 view-transition-name 중복: product-1, product-2처럼 ID 기반으로 이름을 생성하지 않으면, 같은 이름이 두 개 이상 등장하는 순간 전환 전체가 조용히 건너뜁니다. 처음에는 왜 안 되는지 한참 헤맬 수 있는 부분입니다.

  2. 콜백 안에서 fetch 호출: 데이터를 받아오는 동안 이전 페이지가 멈춘 채 사용자에게 그대로 노출됩니다. 데이터 로딩을 먼저 완료한 다음에 전환을 시작하는 순서로 바꾸는 게 좋습니다.

  3. prefers-reduced-motion 처리 누락: 처음부터 전역 CSS에 넣어두는 습관을 들여두면 나중에 따로 챙길 필요가 없습니다. 접근성 감사에서 자주 지적받는 항목이기도 합니다.


마치며

View Transitions API는 이제 "실험적 기능"이 아니라, 라이브러리 없이 프로덕션에 쓸 수 있는 검증된 브라우저 네이티브 도구입니다. 그렇다면 지금 어디서부터 시작할 수 있을까요?

  1. MPA 프로젝트가 있다면: 전역 CSS에 @view-transition { navigation: auto; } 두 줄을 추가해보시면 됩니다. 별도의 JavaScript 없이 모든 페이지 이동에 기본 크로스페이드가 적용되는 걸 바로 확인할 수 있습니다.

  2. SPA 또는 프레임워크 프로젝트라면: Next.js는 next.config.js에 experimental: { viewTransition: true } 한 줄, Astro는 레이아웃에 <ViewTransitions /> 컴포넌트를 추가하는 것부터 시작해보시면 좋습니다. SvelteKit이라면 위의 onNavigate 패턴을 +layout.svelte에 붙여넣어보시면 됩니다.

  3. 가장 인상적인 효과를 원한다면: 상품 목록 → 상세 화면처럼 반복되는 카드 UI가 있다면, 카드 이미지와 상세 페이지 히어로 이미지에 동일한 view-transition-name을 부여하는 것을 권장합니다. 처음 동작하는 순간의 놀라움은 직접 경험해보시면 압니다.


참고 자료

  • View Transition API - MDN Web Docs — API 기본 레퍼런스, 처음 접할 때 출발점
  • Using the View Transition API - MDN — 단계별 구현 방법 상세 설명
  • What's new in view transitions (2025 update) - Chrome for Developers — Chrome 팀의 2025년 변경사항 정리
  • Smooth transitions with the View Transition API - Chrome for Developers — 공식 구현 안내, 예시 풍부
  • Misconceptions about view transitions - Chrome for Developers — 흔한 오해 정리, 팀 내 설득 시 유용
  • CSS View Transitions Module Level 2 - W3C Draft — Level 2 사양 초안, 확정 전 문법 확인용
  • React Labs: View Transitions, Activity, and more - React 공식 블로그 — React <ViewTransition> 발표 원문
  • React <ViewTransition> 공식 문서 — 컴포넌트 API 레퍼런스
  • next.config.js: viewTransition - Next.js 공식 문서 — Next.js 15+ 연동 설정
  • View transitions - Astro 공식 문서 — persist, animate 지시어 포함 Astro 심화 패턴
  • Animating Page and View Transitions with Accessibility in Mind — 접근성 처리 심화, ARIA 및 포커스 관리 포함
  • Can I use: View Transitions API — 현재 브라우저 지원 현황 실시간 확인
#ViewTransitionsAPI#CSS#JavaScript#NextJS#Astro#SvelteKit#MPA#SPA#점진적향상#웹애니메이션
공유하기

목차

핵심 개념브라우저가 전환을 처리하는 세 단계SPA 방식과 MPA 방식공유 요소 전환 (Shared Element Transition)알아두면 좋은 것들 — Level 2 사양실전 적용MPA에서 JavaScript 없이 페이지 전환 애니메이션프레임워크별 통합 패턴접근성을 고려한 모션 처리장단점 분석장점단점 및 주의사항실무에서 가장 흔한 실수마치며참고 자료

추천 포스트

Webpack을 Rsbuild로 옮겼더니 프로덕션 빌드가 74% 빨라졌다 — 마이그레이션 현실과 Rspack 함정
frontend

Webpack을 Rsbuild로 옮겼더니 프로덕션 빌드가 74% 빨라졌다 — 마이그레이션 현실과 Rspack 함정

솔직히 저도 처음엔 "또 새로운 빌드 도구야?"라는 반응이었습니다. Vite가 나왔을 때도 그랬고, Turbopack 발표 때도 그랬죠. 그런데 Alan 핀테크 팀의 사례를 보고 나서는 그냥 지나칠 수가 없었습니다. 가장 복잡한 프로젝트에서 프로덕션 빌드 시간이 2분 10초에서 34초로...

2026년 06월 23일읽는 데 19분
CSS @layer로 마이크로 프론트엔드 스타일 충돌 잡기 | Cascade Layers 격리 전략
frontend

CSS @layer로 마이크로 프론트엔드 스타일 충돌 잡기 | Cascade Layers 격리 전략

마이크로 프론트엔드(MFE, Micro Frontend) 프로젝트를 처음 진행했을 때, 팀A가 배포한 버튼 스타일이 팀B의 화면에서 멋대로 바뀌어 있는 걸 보고 꽤 당황했던 기억이 있습니다. 그 시절엔 CSS Modules로 클래스명 해시를 만들거나, 선택자 앞에 팀 prefix를 붙이...

2026년 06월 23일읽는 데 22분
린팅이 62배 빨라지면 개발 습관이 달라진다 — ESLint에서 Oxlint로, Vite 프로젝트 마이그레이션 경험
frontend

린팅이 62배 빨라지면 개발 습관이 달라진다 — ESLint에서 Oxlint로, Vite 프로젝트 마이그레이션 경험

린터가 느리다고 느낀 적 있으신가요? 저도 그랬습니다. 중간 규모 React 프로젝트에서 를 돌릴 때마다 30초 가까이 기다리는 게 당연한 줄 알았는데, Oxlint로 바꾸고 나서 499ms가 나오는 걸 보고 처음엔 "설정이 잘못된 거 아닌가?" 의심했을 정도입니다. 공식 벤치마크 수치...

2026년 06월 23일읽는 데 19분
HTMX 4.0 서버 렌더링 패턴: 클라이언트 상태 없이 인터랙티브 웹을 만드는 아키텍처 선택
frontend

HTMX 4.0 서버 렌더링 패턴: 클라이언트 상태 없이 인터랙티브 웹을 만드는 아키텍처 선택

React를 처음 배울 때, 저는 솔직히 이 질문을 머릿속에 묻어뒀습니다. "버튼 클릭 하나에 이렇게 많은 코드가 필요한 게 맞는 건가?" 상태 관리, 빌드 도구, hydration 오류... 분명 웹은 HTML과 폼으로 동작하던 시절이 있었는데, 어느 순간 그 단순함을 잃어버렸다는 느...

2026년 06월 07일읽는 데 22분
Next.js RSC + Streaming으로 TTFB를 350ms에서 60ms로 단축하는 방법
frontend

Next.js RSC + Streaming으로 TTFB를 350ms에서 60ms로 단축하는 방법

운영 중인 서비스의 Core Web Vitals를 들여다보다가 TTFB가 400ms를 훌쩍 넘는 페이지를 발견한 적이 있습니다. DB 쿼리는 각각 빠른데 왜 이렇게 느리지 싶어서 살펴봤더니, 세 개의 독립적인 쿼리가 직렬로 대기하고 있었습니다. 로 병렬화하면 해결되겠구나 싶었는데, 막상...

2026년 06월 07일읽는 데 19분
Next.js Turbopack 빌드 캐시로 `next build`를 10배 빠르게 — 실험적 플래그의 함정과 CI 연동 전략
frontend

Next.js Turbopack 빌드 캐시로 `next build`를 10배 빠르게 — 실험적 플래그의 함정과 CI 연동 전략

CI에서 가 3분을 넘어가기 시작하면 슬슬 스트레스가 쌓입니다. PR 하나 올릴 때마다 배포 파이프라인 앞에서 커피 한 잔을 다 마셔야 하는 그 시간. 저도 e-커머스 프로젝트에서 webpack 빌드가 180초를 넘기면서 "이걸 어떻게 줄여보나" 하고 한참 고민했습니다. 로컬에서 스테이...

2026년 05월 30일읽는 데 16분