
Next.js 16.3 Turbopack Bugs: Hash Routing Still Broken
Skilldham
Engineering deep-dives for developers who want real understanding.
Last updated: August 2026
TL;DR
Next.js 16.3 Turbopack bugs are still breaking two common patterns. It has been eight days since the stable release. router.push() with a hash fragment concatenates onto the old hash. It does not replace it. You end up with broken URLs like /abc#Fragment1#Fragment2. Separately, dynamic(() => import('component')) renders the component twice under Turbopack. The cause is missing preload data. Both bugs are open right now. Both are tracked in the same GitHub discussion. The only current fix for the double-render bug is a static import - and that kills your code splitting.
You write router.push('/abc#Fragment2').
You are already sitting on /abc#Fragment1.
You check the address bar. It reads /abc#Fragment1#Fragment2.
You reload. Same thing. You try replace instead of push. Same thing.
You go looking for what changed. The trail leads to a GitHub discussion. Someone else hit this exact wall three days before you did.
These are two of the sharpest Next.js 16.3 Turbopack bugs so far. Here is what is happening. Here is why it is still broken. And here are the two workarounds that get you shipping again.
What's Actually Broken In Next.js 16.3
Next.js 16.3 went stable on August 3, 2026. Days later, two separate bug reports landed in one GitHub discussion thread, #95130. It is not the only rough edge in this release. The 16.3 build OOM bug with cacheComponents is a separate problem worth knowing about too.
Neither bug is new behavior. Both carried over from earlier versions. Nobody fixed them before 16.3 shipped.
The first is a routing bug. The second is a Turbopack hydration bug. They are not related to each other. But they share one thing. Both only show up once you run Turbopack in a real app, not a toy demo.
Bug 1: router.push() Hash Fragments Get Concatenated
This bug was first reported against 16.2 in issue #93126. It carried over into 16.3 unfixed. It is the same class of problem covered in Next.js async params not working - routing state that Turbopack and the App Router handle differently than developers expect.
The Broken Behavior
Start on a page with an existing hash. Push a new hash on top of it.
javascript
// Wrong: this is what most developers write, and it breaks in 16.3
'use client';
import { useRouter } from 'next/navigation';
function FragmentNav() {
const router = useRouter();
const goToSecondFragment = () => {
// Assume the current URL is already /abc#Fragment1
router.push('/abc#Fragment2');
};
return <button onClick={goToSecondFragment}>Next section</button>;
}Expected: the URL becomes /abc#Fragment2.
Actual: the URL becomes /abc#Fragment1#Fragment2. The old fragment never gets replaced. A second one just gets stuck on top.
Click again and it gets worse. Every push adds another fragment to the string.
What Goes Wrong
Next.js adds the new hash instead of swapping it in. This only happens during client-side navigation.
The router handles the path and query string correctly. The hash gets bolted on as a separate step. That step never checks for an old fragment first.
A full page load renders the hash correctly. The browser reads it straight from the URL. It never goes through the router's patch logic at all.

The Workaround
Until this gets fixed, clear the old hash yourself. Do it before you push the new one.
javascript
// Correct: strip the old hash before pushing, so there is nothing to concatenate onto
'use client';
import { useRouter } from 'next/navigation';
function FragmentNav() {
const router = useRouter();
const goToSecondFragment = () => {
const pathWithoutHash = window.location.pathname + window.location.search;
router.push(`${pathWithoutHash}#Fragment2`);
};
return <button onClick={goToSecondFragment}>Next section</button>;
}Building the path from window.location.pathname leaves no old hash behind. There is nothing left for Next.js to stick the new one onto.
This is not a real fix. It is you doing the router's job by hand. But it ships correct URLs today.
Bug 2: dynamic() Imports Render Twice Under Turbopack
This one hurts more if you lean on code splitting.
The Broken Behavior
javascript
// Wrong: renders twice under Turbopack in Next.js 16.3, Pages Router, ssr: true
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('../components/HeavyChart'), {
ssr: true,
});
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<HeavyChart />
</div>
);
}Load this page. HeavyChart shows up twice in the DOM. This is not a visual glitch. It is two full copies of the component, side by side or stacked.
What Goes Wrong
Turbopack is the default bundler in Next.js 16. Under Turbopack, the Pages Router never adds dynamicIds inside NEXT_DATA. That field is what React uses to preload next/dynamic modules before hydration starts.
Without that signal, React hydrates too soon. The dynamic module has not loaded yet. The client tree does not match the server HTML at that moment.
React reads the gap as a mismatch. It mounts a fresh copy instead of reusing the old one. The server-rendered copy stays behind, left in the DOM. You end up looking at both. It is the same mismatch-and-remount pattern behind most React hydration errors in Next.js - just triggered by a missing preload field, not a client and server value mismatch.
Switch the same app to next dev --webpack and the bug goes away. Webpack still emits dynamicIds correctly. That one data point points straight at Turbopack, not your code.
The Workaround
The only workaround right now is dropping the dynamic import. Use a static one instead.
javascript
// Workaround, not a fix: static import removes the double-render, but also removes code splitting
import HeavyChart from '../components/HeavyChart';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<HeavyChart />
</div>
);
}This works because there is no preload gap left for Turbopack to miss. HeavyChart now ships in the main bundle, not a separate chunk.
That is the real cost. If HeavyChart pulls in a large charting library, every visitor now pays for it. Not just the ones who reach the dashboard.
Treat this as a stopgap. Use it where the bundle cost is small. Do not spread it across a whole app.
Is This Still Open In Next.js 16.3?
Yes. Eight days after the stable release, neither bug has a shipped fix.
Both bugs live in the same discussion thread, #95130. Vercel is using it to collect 16.3 feedback. A maintainer replied to the hash routing report. They said the team had already started looking into it before this report came back around.
Neither bug has its own tracking issue with a milestone yet. That is normal for problems this fresh. It also means there is no sure way to know which patch release fixes it.
Check the discussion thread yourself before you assume either workaround still applies to your version.
How To Check If You're Affected
You hit the hash bug in one specific case. Some part of your app calls router.push() or router.replace() with a hash. The user might already be on a URL with a different hash. Anchor navigation, in-page tabs backed by the URL, and scroll-to-section patterns are the common ones. It is worth a quick check across your routes, the same way you would check the Suspense boundary rule for useSearchParams after any App Router upgrade.
You hit the dynamic import bug if you use next/dynamic with ssr: true. It has to be a Pages Router app. It has to run on Turbopack. App Router use was not confirmed as affected in the thread yet. Pages Router reports line up and repeat cleanly.
Neither bug throws a build error. Both only show up at runtime. That is why they slip past a quick manual test.
Key Takeaways
Both of these Next.js 16.3 Turbopack bugs are carryovers from earlier versions, not new regressions in 16.3 itself.
The hash routing bug adds a new fragment on top of an old one during client-side router.push() navigation.
The workaround is to strip window.location.pathname before pushing a new hash, not to rely on the router to replace it.
The double-render bug happens because Turbopack skips dynamicIds in NEXT_DATA for the Pages Router.
The only current workaround for the double-render bug is a static import, which removes code splitting for that component.
Both bugs are tracked in the same GitHub discussion, #95130, with no shipped fix as of this writing.
Check your Next.js version against the thread before you assume a workaround still applies.
Frequently Asked Questions
Is the Next.js 16.3 hash routing bug fixed yet?
No, not as of this writing. A Vercel maintainer replied to the report in discussion #95130. They said the team had already started looking into it before this report came in. No patch release has shipped a fix yet.
Does the dynamic import double-render bug affect the App Router too?
The confirmed reports in the discussion thread are from the Pages Router. App Router use was not confirmed as affected as of this writing. If you are on the App Router, test your dynamic() calls directly. Do not assume you are safe.
What is NEXT_DATA.dynamicIds and why does it matter?
It is the field Next.js uses to tell React which next/dynamic modules to preload before hydration starts. Under Turbopack, the Pages Router never fills in this field. React hydrates before the dynamic module loads and treats the gap as a reason to remount instead of reuse.
Can I disable Turbopack to avoid these bugs?
Yes. Running with next dev --webpack or the matching build flag avoids the double-render bug. Webpack still emits dynamicIds correctly. This is not something Vercel recommends long term in Next.js 16, since Turbopack is the default and webpack support is being phased down.
Does downgrading to Next.js 16.2 fix the hash routing bug?
No. The hash routing bug was first reported against 16.2 in issue #93126. It carried over unfixed into 16.3. Downgrading does not fix it.
Is this a Turbopack bug or a Next.js bug?
The double-render bug is a Turbopack bug. It does not happen under webpack with the same code. The hash routing bug sits in Next.js's own router logic. It has nothing to do with which bundler you use.
Will using webpack instead of Turbopack fix the double-render issue?
Yes. Based on the test in the GitHub thread, switching to webpack removes the double-render. dynamicIds gets set correctly again. It does not fix the hash routing bug, which is bundler-independent.
What's the performance cost of the static import workaround?
The component ships inside your main JavaScript bundle instead of its own chunk. Every visitor downloads it, not just the ones who reach that page. For a small component this barely matters. For a large library like a charting package, it can add real weight to your first load.
Fixing a broken URL or a doubled component is not the hard part here. The hard part is knowing these bugs exist before they hit production. Neither one throws a build error. Neither one is written up anywhere outside a single GitHub thread right now.
Watch discussion #95130 if either pattern lives in your app. Treat both fixes above as workarounds to drop once Vercel ships the real one. If you run Turbopack in production on 16.3, audit every dynamic() call with ssr: true this week. Do not wait to find the duplicate render by accident.