Перейти к содержимому
DVERSE

View Transitions в продакшене

Заметки о shared-element морфах на статическом Worker

Astro поддерживает View Transitions как встроенный API. Достаточно задать viewTransitionName на двух элементах в разных маршрутах — и браузер автоматически анимирует переход между ними без JavaScript-библиотек.

Но деплой на Cloudflare Workers со Static Assets выявил несколько подводных камней, которые стоит зафиксировать.

Конфигурация

Портфолио работает на Astro 6 с output: "static". Каждая страница пререндерится при сборке и раздаётся с edge-серверов Cloudflare как обычный HTML-файл. Единственный динамический маршрут — /api/brief, обрабатываемый Worker’ом.

View Transitions живут целиком в браузере — сервер о них ничего не знает. Это хорошая часть.

Как работает морф

Два элемента разделяют общий view-transition-name:

<!-- PostCard.astro (страница индекса) -->
<h3 style={{ viewTransitionName: `thought-${slug}-title` }}>
  {title}
</h3>
<!-- [slug].astro (страница поста) -->
<h1 style={{ viewTransitionName: `thought-${slug}-title` }}>
  {title}
</h1>

При навигации между ними браузер захватывает снимки обоих элементов и плавно морфит один в другой. Без Motion, без FLIP — чистый CSS под капотом.

Подводные камни

Три проблемы проявились при деплое:

  1. Дублирование имён переходов — если две карточки на индексе имеют одинаковое имя, браузер молча отбрасывает переход. Каждое имя должно быть уникальным на странице.
  2. MPA vs SPA режим — Astro по умолчанию использует MPA-переходы (полная загрузка страницы). Компонент <ViewTransitions /> переключает на перехват навигации в стиле SPA.
  3. Кеширование Cloudflare — Workers агрессивно кешируют. Устаревший HTML-ответ может ссылаться на старое имя перехода, которого больше нет на целевой странице.

Решение проблемы кеширования

Мы добавили Cache-Control: no-cache для HTML-ответов, сохранив долгосрочное кеширование для статических ресурсов (JS, CSS, изображения):

// Worker fetch handler
export default {
  async fetch(request: Request, env: Env) {
    const response = await env.ASSETS.fetch(request);
    const url = new URL(request.url);

    if (url.pathname.endsWith("/") || url.pathname.endsWith(".html")) {
      const headers = new Headers(response.headers);
      headers.set("Cache-Control", "no-cache");
      return new Response(response.body, { ...response, headers });
    }

    return response;
  },
};

Это не специфично для View Transitions — это хорошая практика для любого статического сайта, где HTML меняется при каждом деплое, а ресурсы хешируются.

Замечания по производительности

View Transitions добавляют ноль килобайт JavaScript, если использовать встроенную поддержку Astro. Браузерный API document.startViewTransition() делает всё сам.

Мы измерили через Lighthouse на типичной странице поста:

МетрикаДоПосле
Performance9898
FCP0.8s0.8s
LCP1.1s1.1s
CLS00

Регрессии нет. Сам переход добавляет ощутимую задержку ~250ms (длительность морфа), но пользователи воспринимают её как намеренную анимацию, а не как время загрузки.

Reduced motion

Переходы уважают prefers-reduced-motion. Когда пользователь включил уменьшение движения, Astro переключается на мгновенное растворение — всё ещё лучше, чем резкая смена страницы, но без плавных морфов.


Полная реализация живёт в шаблоне поста и компоненте PostCard. Паттерн обобщается на любую пару маршрутов с общим контентом — кейсы используют тот же подход.