All ArticlesAnimation•November 1, 2026•9 min read

View Transitions at 50M Navigations: Cross-Document vs Same-Document vs JS Orchestration

A production benchmark of cross-document, same-document, and JS-orchestrated View Transitions across 50M navigations. Learn where INP, jank rate, and normalized cost per million transitions diverge — and how to choose an architecture that survives real traffic.

A laptop displaying an analytics dashboard with real-time data tracking and analysis tools.Photo by Atlantic Ambience on Pexels

At 50 million navigations, a 20 ms regression becomes 1,000,000 seconds of added main-thread work. That is the scale where View Transitions stop being visual polish and become an architecture decision. The platform now gives us three viable models: cross-document transitions for MPAs, same-document transitions for SPAs, and JS-orchestrated transitions for teams that need explicit choreography or broader browser coverage. Each has a different INP profile, jank rate, and cost per million transitions.

The Three Architectures in One Mental Model

Cross-document view transitions are browser-managed MPA navigations. The browser snapshots the outgoing document and the incoming document, then animates between them. There is no SPA router, no preserved client state, and no long JavaScript render path in the critical section. Same-document view transitions keep one document alive and animate between DOM states after a router render. JS-orchestrated transitions use the Web Animations API, FLIP, or a motion library to perform the choreography manually.

The architectural trade-off is simple: cross-document gives you low main-thread risk but full navigation cost; same-document gives you SPA payload efficiency but route rendering can block the main thread; JS orchestration gives you maximum control but the highest CPU and jank exposure.

Benchmark Methodology: 50M Navigations Without Lying to Yourself

These are reference-harness results, not universal SLA numbers. The harness replayed 50M weighted navigations across 1,200 routes, with a 60/40 mobile/desktop split. Mobile used a mid-tier Android profile with 4x CPU throttling; desktop used a current-generation laptop profile. The median route had 1,800 DOM nodes, and p95 had 5,400 nodes. I measured p75 INP on route-triggering interactions, jank rate as the share of navigations with at least one frame over 50 ms during the transition window, and main-thread CPU milliseconds per transition. Results varied by roughly ±20% across runs.

Architecturep75 INPJank rateMain-thread CPU/transitionNormalized cost/1M50M cost
Cross-document MPA104 ms1.4%38 ms$0.63$32
Same-document native127 ms2.9%74 ms$1.24$62
JS-orchestrated189 ms7.8%146 ms$2.44$122

The normalized cost formula is costPerMillion = (cpuMsPerTransition / 1000) * 1_000_000 * 0.0000167, pricing main-thread CPU at roughly $0.06 per vCPU-hour. This is not an invoice. It excludes CDN egress, observability, device energy, support load, and conversion impact. It is a comparability metric for architecture decisions.

Cross-Document View Transitions: The MPA Superpower

Cross-document transitions are the strongest default for content, commerce, and SEO-heavy architectures. The browser handles the snapshot lifecycle, and the transition does not require a client-side router to re-render a large application tree. The cost moves to document load, so prefetch, cache hit rate, and TTFB become the real performance levers.

html
<!-- Opt in on both documents, same origin -->
<meta name='view-transition' content='same-origin'>

<style>
  @view-transition { navigation: auto; }

  ::view-transition-old(root) {
    animation: 160ms ease-out both fade-out;
  }
  ::view-transition-new(root) {
    animation: 220ms cubic-bezier(.2, .8, .2, 1) both fade-in;
  }

  @keyframes fade-out { to { opacity: 0; } }
  @keyframes fade-in { from { opacity: 0; } }
</style>

<script>
  window.addEventListener('pagereveal', (event) => {
    const viewTransition = event.viewTransition;
    if (!viewTransition) return;

    const nextUrl = navigation.activation?.entry?.url ?? location.href;
    const nextPath = new URL(nextUrl).pathname;

    if (nextPath.startsWith('/checkout')) {
      document.documentElement.dataset.transition = 'checkout';
    }
  });
</script>

At 50M navigations, cross-document wins when the destination document is fast. A 1.4% jank rate still means 700,000 navigations with a long frame. The p75 INP advantage comes from removing route-render JavaScript from the interaction path, not from making the network faster. If TTFB is poor, the transition can feel decorative while users wait.

Same-Document View Transitions: Fast Until the Router Blocks

Same-document transitions are the natural SPA model. The DOM update happens inside the startViewTransition callback. The browser captures before and after states, then animates. The critical section is the callback: if your router renders 3,000 nodes, fetches data synchronously, or triggers layout thrash, that cost lands directly in the interaction path.

ts
type Route = {
  path: string;
  render: () => Promise<void>;
};

let activeTransition: ViewTransition | null = null;

export async function navigate(route: Route): Promise<void> {
  const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  if (!('startViewTransition' in document) || prefersReduced) {
    await route.render();
    return;
  }

  activeTransition?.skipTransition();

  const transition = document.startViewTransition(async () => {
    try {
      await route.render();
      await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
    } catch (error) {
      console.error('Route render failed during view transition', error);
      throw error;
    }
  });

  activeTransition = transition;

  try {
    await transition.finished;
  } catch (error) {
    console.warn('View transition did not finish cleanly', error);
  } finally {
    if (activeTransition === transition) activeTransition = null;
  }
}

Keep the callback under 50 ms. Yield before expensive work when possible. Limit named transitions to a handful of elements, use contain: layout paint on animated containers, and never await transition.finished before the navigation is considered complete. The measured 127 ms p75 INP and 2.9% jank rate are achievable only when route rendering is already disciplined.

JS-Orchestrated Transitions: Maximum Control, Maximum Risk

JS orchestration is appropriate when native snapshots cannot express the choreography, when you need a fallback for unsupported browsers, or when shared-element motion must survive a design system's strict timing rules. It is also the easiest way to create 7.8% jank. Every animation needs cancellation, cleanup, reduced-motion handling, and a hard budget.

ts
export async function flipSharedElement(
  element: HTMLElement,
  update: () => Promise<void>
): Promise<void> {
  const first = element.getBoundingClientRect();

  await update();
  await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));

  const last = element.getBoundingClientRect();
  const dx = first.left - last.left;
  const dy = first.top - last.top;
  const sx = last.width === 0 ? 1 : first.width / last.width;
  const sy = last.height === 0 ? 1 : first.height / last.height;

  const animation = element.animate(
    [
      {
        transform: `translate(${dx}px, ${dy}px) scale(${sx}, ${sy})`,
        transformOrigin: 'top left'
      },
      { transform: 'translate(0, 0) scale(1, 1)', transformOrigin: 'top left' }
    ],
    {
      duration: 240,
      easing: 'cubic-bezier(.2, .8, .2, 1)',
      fill: 'both'
    }
  );

  try {
    await animation.finished;
  } catch (error) {
    console.warn('FLIP animation interrupted', error);
  } finally {
    animation.cancel();
  }
}

Use transform and opacity only. Avoid animating layout properties. Batch DOM reads before writes. Cancel stale animations when a new navigation starts. At 50M navigations, the JS-orchestrated path cost $122 in normalized client CPU versus $32 for cross-document: roughly 3.8x. The business case must justify that delta with conversion or brand value, not just visual preference.

INP, Jank, and Cost: What the Numbers Actually Mean

INP is an interaction metric, but route-triggering clicks and SPA navigations influence it directly. Cross-document transitions may reset the document, so measure click-to-next-paint on the destination document as a custom navigation metric alongside INP. Same-document transitions are fully exposed to INP because the router callback runs in the same document. JS-orchestrated transitions add animation setup, layout measurement, and cleanup to that same critical path.

Jank rate is the more honest scale metric. At 50M navigations, 1 percentage point of jank is 500,000 bad transitions. The cross-document 1.4% rate is 700,000 long-frame navigations; the JS-orchestrated 7.8% rate is 3.9M. That is why a transition that looks smooth on a flagship laptop can still fail an enterprise performance budget on a mid-tier Android device.

Architecture Decision Matrix

RequirementRecommended modelNotes
Content, commerce, SEO-heavy MPACross-documentPrefetch routes, keep TTFB low, opt in on both documents
SPA app shell with preserved stateSame-document nativeKeep route render under 50 ms and cancel stale transitions
Complex shared-element choreographyNative first, JS fallbackBudget 2-3x CPU and enforce cleanup
Low-end devices or strict cost budgetCross-document or no transitionTest at 4x CPU throttle

Production Checklist and Pitfalls

  • Set a performance budget: p75 INP at or below 200 ms, transition CPU at or below 50 ms, jank rate below 3%.
  • Animate only transform and opacity; ban height, top, filter, and backdrop-filter transitions in review.
  • Limit view-transition-name to six or fewer elements per navigation.
  • Use contain: layout paint on animated containers and avoid transition: all.
  • Prefetch cross-document destinations; a beautiful transition cannot hide a 2-second TTFB.
  • Instrument Event Timing, Long Animation Frames, and navigation timing; tag each transition type and sample RUM at 10% or more.
  • Respect prefers-reduced-motion with an instant fallback, not a shortened animation.
  • Cancel stale transitions and handle render errors so a failed route never leaves a blank document.
  • Canary by device class and browser version; motion quality is device-dependent.

The common failures are predictable: awaiting transition.finished before completing navigation, duplicate view-transition-name values, snapshotting 10,000-node documents, and using JS orchestration without cancellation. Cross-document teams forget the opt-in on one side of the navigation. Same-document teams put data fetching inside the transition callback. JS-orchestrated teams measure animation duration but not long-task contribution.

Sources & Technical References

0 views•0 comments

Discussion (0)

Join the conversation

Loading comments...

Continue Reading

More production-grade engineering deep dives and architectural guides

Explore all articles