Astro View Transitions: SPA 전환의 함정과 올바른 JS 생명주기
Astro에 <ViewTransitions />를 추가하는 건 한 줄짜리 작업입니다. 하지만 그 한 줄이 기존에 작성한 모든 클라이언트 사이드 JavaScript를 조용히 망가뜨릴 수 있습니다.
왜 DOMContentLoaded가 안 되는가?
<ViewTransitions />를 켜면 Astro는 페이지 전환 시 전체 HTML을 새로 내려받는 대신, fetch로 새 페이지의 콘텐츠만 가져와서 현재 DOM에 패치합니다. 진짜 SPA처럼요.
이 과정에서 DOMContentLoaded는 최초 페이지 로드 때 한 번만 발생합니다. 이후 View Transitions로 전환된 페이지에서는 절대 발생하지 않습니다. 그래서 DOMContentLoaded 안에 작성한 Intersection Observer, 이벤트 리스너 등이 두 번째 페이지부터 동작하지 않는 거죠.
해결책: astro:page-load
Astro는 이를 위해 astro:page-load 이벤트를 제공합니다. 이 이벤트는:
- 최초 페이지 로드 시 발생
- View Transitions로 새 페이지가 마운트될 때마다 발생
// ❌ 이렇게 하면 첫 페이지에서만 동작
document.addEventListener('DOMContentLoaded', () => {
initScrollObserver();
});
// ✅ 이렇게 해야 모든 페이지에서 동작
document.addEventListener('astro:page-load', () => {
initScrollObserver();
});
실전 패턴: 다크모드 토글
document.addEventListener('astro:page-load', () => {
const toggle = document.getElementById('theme-toggle');
toggle?.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
});
추가 훅: astro:before-swap
페이지 전환이 시작되기 직전에 실행할 코드는 astro:before-swap을 사용하세요. Observer 해제, 타이머 정리 등 클린업 작업에 적합합니다.
document.addEventListener('astro:before-swap', (e) => {
observer?.disconnect();
});
이 두 가지 훅만 제대로 이해하면 View Transitions와 바닐라 JS를 완벽하게 공존시킬 수 있습니다.