Astro View Transitions 심화 가이드 — 부드러운 페이지 전환과 MPA UX

Astro View Transitions 심화 가이드 — 부드러운 페이지 전환과 MPA UX

이 글의 핵심

브라우저 View Transitions API의 역할과 Astro의 ClientRouter가 추가하는 클라이언트 라우팅·전환 제어를 구분하고, transition:name·transition:animate·transition:persist로 요소 매칭·애니메이션·상태 유지를 설계합니다. 커스텀 키프레임, 폴백 전략, prefers-reduced-motion, 스크립트 재실행·성능까지 멀티페이지 앱 실전에 맞춰 정리합니다.

이 글의 핵심

View Transitions는 한 화면에서 다른 화면으로 넘어갈 때 시각적 연속성을 유지하기 위한 웹 플랫폼 기능입니다. Astro는 이 API를 기반으로 하되, 브라우저 단독으로는 부족한 클라이언트 측 라우팅, 요소 유지(persist), 폴백 등을 ClientRouter(과거 문서의 ViewTransitions와 동일 계열)로 보완합니다. 본 글은 “설치 한 줄”을 넘어 transition:name, transition:animate, transition:persist, 커스텀 애니메이션, 폴백·접근성·성능, 멀티페이지 앱(MPA) 실무 패턴을 한 흐름으로 연결합니다.

선행 지식: Astro 레이아웃·페이지 구조, 기본 HTML/CSS 애니메이션, 브라우저 히스토리(pushState 등) 개념에 익숙하다고 가정합니다.


1. View Transitions API와 Astro의 역할 분담

1.1 브라우저 네이티브 View Transitions

View Transitions API는 문서 전환 시 이전 뷰와 새 뷰의 스냅샷을 바탕으로 애니메이션을 합성합니다. 크로미움 계열에서 우선 지원이 넓고, 크로스 도큐먼트(전통적인 MPA 네비게이션) 시나리오도 표준·브라우저 구현에 따라 확장되는 중입니다. 핵심은 “페이지를 새로 그리되, 사용자에게는 끊김 없는 한 장의 흐름으로 보이게 한다”는 점입니다.

1.2 Astro ClientRouter가 추가하는 층

문서만으로는 부족할 수 있는 부분—예: 동일 사이트 내 링크 가로채기, 히스토리와 연동한 부분 DOM 교체, transition:persist로 DOM 조각 유지, 폴백—을 Astro가 가벼운 클라이언트 라우터로 처리합니다. 이는 MPA의 서버 렌더링 모델을 유지하면서도 SPA에 가까운 체감을 얻게 해 줍니다. 다만 스크립트 실행 순서·재실행 규칙이 전통적인 풀 리로드와 달라지므로, “애니메이션만 얹었다”가 아니라 애플리케이션 수명 주기 설계가 함께 바뀐다고 보는 것이 안전합니다.

공식 블로그에서도 장기적으로는 브라우저 API만으로 충분해지는 방향을 논의하고 있으므로, 팀 내 기술 선택 시 “왜 ClientRouter가 필요한지”를 페이지별로 기록해 두면 이후 마이그레이션에 유리합니다.


2. 전역 활성화: ClientRouter 배치

Astro 5 기준으로 공통 <head> 또는 레이아웃에 다음과 같이 추가합니다.

---
// src/components/CommonHead.astro 등
import { ClientRouter } from "astro:transitions";
const { title, description } = Astro.props;
---
<meta charset="utf-8" />
<title>{title}</title>
<meta name="description" content={description} />
<ClientRouter />

이 한 가지로 동일 출처 내 내부 링크에 대한 기본 클라이언트 네비게이션과 기본 전환이 활성화됩니다. 세부 동작은 전환 지시자(transition directives) 로 덮어씁니다.


3. transition:name: 이전·새 페이지 요소 짝짓기

Astro는 기본적으로 DOM 위치·요소 종류 등을 바탕으로 대응 쌍을 추론합니다. 그러나 카드 그리드 재배치, 반응형으로 순서가 바뀌는 헤더, 동일 역할을 다른 태그로 표현하는 경우 추론이 어긋날 수 있습니다. 이때 transition:name 으로 논리적 동일성을 명시합니다.

<!-- pages/blog/[slug].astro (이전 페이지) -->
<article transition:name="post-hero">
  <h1>{title}</h1>
</article>
<!-- pages/blog/index.astro (다음 페이지) -->
<article transition:name="post-hero">
  <h1>블로그</h1>
</article>

주의: 한 페이지에서 동일한 transition:name 값은 한 번만 써야 합니다. 목록에서 반복 렌더링할 때는 slug 등으로 유일한 이름을 만들거나, 리스트 전체가 아니라 대표 썸네일 한 개만 이름을 부여하는 패턴이 흔합니다. 이름이 충돌하면 전환이 기대와 다르게 붙거나, 브라우저가 쌍을 매칭하지 못해 애니메이션이 약해지는 현상이 납니다.


4. transition:animate: 기본 모션과 페이지 단위 기본값

4.1 내장 지시자

Astro는 개별 요소에 다음과 같은 애니메이션을 지정할 수 있습니다.

  • fade: 기본에 가까운 크로스페이드 성격의 전환(문서 기본 동작과의 조합은 프로젝트·버전에 따라 세부가 다를 수 있음).
  • initial: Astro가 의도한 크로스페이드 대신 브라우저 기본 스타일 쪽에 가깝게 두고 싶을 때.
  • slide: 앞으로 갈 때와 뒤로 갈 때 방향이 반대로 잡히는 슬라이드 계열(히스토리 방향과 맞추기 좋음).
  • none: 해당 요소에 대해 브라우저 기본 애니메이션을 끔. 루트 <html>에 두면 페이지 전체 기본 페이드를 끄고, main 등에만 slide를 주는 식으로 “껍데기는 고정, 본문만 슬라이드” 패턴을 만들 수 있습니다.
<html lang="ko" transition:name="root" transition:animate="none">
  <head>...</head>
  <body>
    <header>...</header>
    <main transition:animate="slide">
      <slot />
    </main>
  </body>
</html>

4.2 내장 애니메이션 함수로 파라미터 조정

지속 시간·이징을 조정하려면 astro:transitions에서 함수를 가져와 객체로 전달합니다.

---
import { fade, slide } from "astro:transitions";
---
<header transition:animate={fade({ duration: "0.4s" })}>
  ...
</header>
<section transition:animate={slide({ duration: "0.35s" })}>
  ...
</section>

포인트: 루트에 강한 모션을 주면 레이아웃 시프트·스크롤 복원 체감이 함께 드러납니다. 헤더/내비는 none에 가깝게, 본문만 모션을 주는 구성이 서비스 UI에서 안정적인 경우가 많습니다.


5. 커스텀 애니메이션: 키프레임과 방향성 객체

완전히 새로운 모션을 쓰려면 CSS @keyframes 를 정의하고, Astro가 기대하는 TransitionDirectionalAnimations 형태로 old/new·forwards/backwards 를 지정합니다. 키프레임 이름 문자열과 duration·easing·필요 시 direction을 맞추는 것이 핵심입니다.

---
// 레이아웃 또는 해당 섹션 컴포넌트
import { ClientRouter } from "astro:transitions";

const bumpPair = {
  old: {
    name: "bump",
    duration: "0.45s",
    easing: "ease-in",
    direction: "reverse",
  },
  new: {
    name: "bump",
    duration: "0.45s",
    easing: "ease-out",
  },
};

const customTransition = {
  forwards: { old: bumpPair.old, new: bumpPair.new },
  backwards: { old: bumpPair.new, new: bumpPair.old },
};
---
<html lang="ko">
  <head><ClientRouter /></head>
  <body>
    <header transition:animate={customTransition}>...</header>
  </body>
</html>

<style is:global>
  @keyframes bump {
    0% {
      opacity: 0;
      transform: translateX(24px) scale(0.98);
    }
    100% {
      opacity: 1;
      transform: translateX(0) scale(1);
    }
  }
</style>

실무 팁:

  • 모션은 짧게: 200~400ms 대가 체감상 “빠르고 세련됨”에 잘 맞습니다.
  • 역방향 네비게이션까지 고려해 backwards를 대충 복사하지 말고, 사용자가 뒤로 갈 때 자연스러운 대칭인지 확인합니다.
  • 저사양 기기에서는 transformopacity 중심이 합성 레이어 친화적입니다(will-change 남용은 오히려 메모리만 늘 수 있어, 상시 지정은 지양).

6. transition:persisttransition:persist-props

6.1 DOM·미디어·아일랜드 유지

transition:persist 는 “새 페이지로 교체” 대신 기존 노드를 유지합니다. 비디오 재생, 오디오 위젯, 복잡한 캔버스, 지도처럼 상태를 잃으면 사용자 경험이 크게 나빠지는 요소에 특히 유효합니다.

<video controls muted autoplay transition:persist>
  <source src="/media/intro.mp4" type="video/mp4" />
</video>

프레임워크 아일랜드에 붙이면, 다음 페이지에 동일 컴포넌트가 있을 때 기존 인스턴스와 내부 상태가 이어질 수 있습니다.

<Counter client:load transition:persist initialCount={0} />

6.2 다른 트리에 있어도 이름으로 대응시키기

페이지마다 컴포넌트 계층이 달라도 transition:nametransition:persist를 함께 쓰면 대응 관계를 명시할 수 있습니다. 편의상 transition:persist="media-player" 처럼 이름을 값으로 넘기는 축약도 가능합니다.

6.3 transition:persist-props (Astro 4.5+)

기본 동작은 상태는 유지하지만 새 페이지의 props로 리렌더될 수 있습니다. 예를 들어 헤더에 현재 페이지 제목을 prop으로 넘기면, 네비게이션 시 제목이 갱신되어야 합니다. 반면 prop까지 고정해야 하는 특수 케이스(예: 실험적 UI, A/B 플래그 고정)에는 transition:persist-props 를 추가합니다. 남용하면 화면이 실제 라우트와 불일치할 수 있으므로, 디자인 시스템 수준에서 규칙을 두는 것이 좋습니다.


7. 폴백과 브라우저 호환성

ClientRouterView Transitions API가 없는 환경에서도 동작하도록 설계되어 있으며, fallback 으로 체감을 조절합니다.

  • animate(기본, 권장): 지원하지 않는 브라우저에서도 전환을 시뮬레이션하여 최대한 비슷한 경험을 제공합니다.
  • swap: 애니메이션 없이 즉시 교체합니다. “구형 브라우저에선 빠르게만” 같은 정책에 맞습니다.
  • none: 애니메이션 없는 전통적 네비게이션에 가깝게 둡니다(환경에 따라 체감 차이 확인 필요).
---
import { ClientRouter } from "astro:transitions";
---
<ClientRouter fallback="swap" />

크로스 브라우저 일관성을 위해 중요한 요소에는 transition:name / transition:animate를 명시하라는 가이드가 공식 문서에도 있습니다. “크롬에서만 예쁜 데모”로 끝나지 않으려면 Safari·Firefox 실기기에서의 깜빡임·스크롤·포커스 이동을 반드시 확인합니다.


8. 성능 최적화

8.1 애니메이션 비용

  • 레이아웃을 밀어내는 속성(width, height, top, left 등)은 리플로우를 유발하기 쉽습니다. 가능하면 transform/opacity 중심으로 설계합니다.
  • 동시에 많은 요소에 강한 모션을 주면 메인 스레드·합성 비용이 누적됩니다. 히어로 1개 + 본문 페이드처럼 계층을 나누어 우선순위를 부여합니다.
  • 이미지·폰트 로딩이 끝나기 전 전환이 시작되면 레이아웃 점프가 애니메이션과 겹칩니다. 치수 고정, placeholder, font-display 전략과 함께 봅니다.

8.2 네트워크와 캐시

클라이언트 라우터는 다음 HTML을 가져와 파싱합니다. 문서 크기, CSS/JS 청크 수, 서버 TTFB가 곧 전환 체감입니다. 불필요한 인라인 스타일 중복, 거대한 헤드 블록은 페이지마다 늘어날수록 스왑 비용을 키웁니다.

8.3 접근성: prefers-reduced-motion

Astro는 시스템의 모션 감소 설정을 존중하는 동작을 문서화하고 있습니다. 커스텀 CSS를 쓸 때도 다음과 같이 대체 경로를 두는 것이 안전합니다.

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.001ms !important;
  }
}

프로젝트 정책에 따라 완전 비활성 대신 최소한의 페이드만 남기는 팀도 있습니다.


9. 실전 멀티페이지 앱 전환 패턴

9.1 특정 링크만 풀 리로드: data-astro-reload

결제·인증·서드파티 스크립트 재주입이 필요한 페이지로 갈 때는 클라이언트 라우팅을 끕니다.

<a href="/checkout/confirm" data-astro-reload>결제 확인</a>

9.2 히스토리 제어: data-astro-history

push·replace·auto히스토리 스택 쌓임을 제어합니다. “확인 화면은 스택에 남기지 않는다” 같은 모바일 웹 결제 UX에 유용합니다.

9.3 프로그래매틱 네비게이션: navigate()

셀렉트 박스·검색 결과·키보드 단축키 등에서 astro:transitions/clientnavigate 로 이동시킬 수 있습니다. 사용자 입력을 URL에 그대로 넣지 말 것—공식 문서에서도 오픈 리다이렉트·XSS 위험을 경고합니다. 허용 경로 화이트리스트로 검증합니다.

import { navigate } from "astro:transitions/client";

const allowed = ["/dashboard", "/settings", "/profile"] as const;

export function go(path: string) {
  if ((allowed as readonly string[]).includes(path)) {
    navigate(path);
  }
}

9.4 폼 전송과 전환 (Astro 4+)

GET/POST 폼도 라우터와 통합됩니다. 기본 POSTmultipart/form-data 쪽으로 맞춰지므로, 전통적인 application/x-www-form-urlencoded가 필요하면 enctype을 명시합니다. 폼만 풀 리로드하려면 data-astro-reload 를 폼에 붙입니다.

9.5 스크립트와 수명 주기 이벤트

클라이언트 라우팅에서는 번들 모듈 스크립트가 한 번만 실행되는 특성이 있습니다. 메뉴·분석·폼 바인딩 등은 astro:page-load, astro:after-swap 등 문서에 정리된 이벤트에 맞춰 재초기화합니다. 전환 직후 포커스 이동·스크롤 복원까지 포함해 WCAG 관점에서 테스트합니다.


10. 트러블슈팅 체크리스트

증상점검
요소가 “이어지지” 않음transition:name 충돌, 조건부 렌더링으로 한쪽 페이지에만 존재
애니메이션만 이상함루트 transition:animate="none"과 자식 모션의 조합, 커스텀 키프레임 누락
뒤로 가기 시 어색함backwards 정의, slide 방향, 스크롤 위치
JS가 안 돌아감모듈 스크립트 1회 실행 규칙, 이벤트 기반 재초기화 필요 여부
특정 브라우저에서 깜빡임fallback 값, transition:animate 명시 여부

11. 정리

View Transitions API는 “예쁜 전환”을 넘어 사용자의 주의·맥락을 유지하는 도구입니다. Astro의 ClientRouter는 그 위에 MPA 친화적 클라이언트 라우팅transition:* 지시자를 얹어 실무 요구를 채웁니다. transition:name으로 짝을 명시하고, transition:animate로 계층별 모션을 나누며, transition:persist로 상태를 보존하고, fallback·모션 감소·성능·스크립트 수명까지 묶어야 프로덕션급 “부드러운 페이지 전환”이 완성됩니다.


참고