nextjs - 💡(How to fix) Fix ViewTransitions should work on popstate

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:38:56 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6000
  Available memory (MB): 16384
  Available CPU cores: 10
Binaries:
  Node: 26.2.0
  npm: 11.13.0
  Yarn: N/A
  pnpm: 9.15.4
Relevant Packages:
  next: 16.0.10 // There is a newer version (16.2.7) available, upgrade recommended! 
  eslint-config-next: N/A
  react: 19.0.0
  react-dom: 19.0.0
  typescript: 5.8.2
Next.js Config:
  output: N/A

---

'use client';
import { startTransition, useEffect } from 'react';
import { useRouter } from 'next/navigation';

export function BrowserNavigationTransition() {
  const router = useRouter();

  useEffect(() => {
    const abortController = new AbortController();

    window.addEventListener('popstate', (event: PopStateEvent) => {
      // 1. Prevent Next.js from processing the standard instant back navigation
      event.stopPropagation();

      // 2. Wrap the router event inside a transition
      startTransition(() => {
        // 3. Manually trigger a refresh to update the content based on the new URL
        router.refresh();
      });
    }, {
      signal: abortController.signal,
    });

    return () => abortController.abort();
  }, [router]);

  return null;
}
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/vercel/next-view-transition-example

To Reproduce

  1. Go to https://view-transition-example.vercel.app/blog
  2. Click "Read More" on any post
  3. Notice the avatars transition appropriately
  4. Click back in the browser
  5. Notice that the avatars don't transition back

Current vs. Expected behavior

Currently, the view transitions work correctly when navigating using <Link>, router.push(), or router.replace(), but not when navigating using the browser's native navigation tools.

I would expect the transitions to work regardless.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:38:56 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6000
  Available memory (MB): 16384
  Available CPU cores: 10
Binaries:
  Node: 26.2.0
  npm: 11.13.0
  Yarn: N/A
  pnpm: 9.15.4
Relevant Packages:
  next: 16.0.10 // There is a newer version (16.2.7) available, upgrade recommended! 
  eslint-config-next: N/A
  react: 19.0.0
  react-dom: 19.0.0
  typescript: 5.8.2
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Linking and Navigating

Which stage(s) are affected? (Select all that apply)

next dev (local), next start (local), Vercel (Deployed), Other (Deployed)

Additional context

I looked into app-router.tsx but it looks like it already uses startTransition for the popstate handler.

One thing I noticed that might be related is that during the in-app navigation, it renders the component, but when navigating using the browser's native navigation, it doesn't render but must be using a cached version. I don't know if this is related.

I have another application not using SSG and it does re-render the component, but still has the same behavior, so that could be a red herring.

I created the following small hacky component that can be placed in the root layout.tsx which fixes the issue, but it can introduce bugs and I'd prefer if this was fixed at the framework level.

'use client';
import { startTransition, useEffect } from 'react';
import { useRouter } from 'next/navigation';

export function BrowserNavigationTransition() {
  const router = useRouter();

  useEffect(() => {
    const abortController = new AbortController();

    window.addEventListener('popstate', (event: PopStateEvent) => {
      // 1. Prevent Next.js from processing the standard instant back navigation
      event.stopPropagation();

      // 2. Wrap the router event inside a transition
      startTransition(() => {
        // 3. Manually trigger a refresh to update the content based on the new URL
        router.refresh();
      });
    }, {
      signal: abortController.signal,
    });

    return () => abortController.abort();
  }, [router]);

  return null;
}

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING