HP5004 (navigation-mismatch) means a route renders different content when you
reach it through the app's router than when you load its URL directly. Users
who arrive from a link see one page, users who reload see another. Render the
route from its own data (params, search params, server data), not from the
previous page or client state.
| Code | HP5004 |
|---|---|
| Name | navigation-mismatch |
| Default severity | Warning |
| Group | Interaction during hydration |
| What it means | Navigating to the route inside the app renders different content than loading the URL directly. |
What the HP5004 navigation mismatch finding means
The finding comes from the navigation checks
(checks.navigation: true or --navigation). hydration-proof opens a page,
navigates to each route with the app's router (router.push in Next.js, the
data router in React Router and Remix) and compares the result with a direct
load. Both loads use the same fixed browser clock and random seed, and numbers
are ignored in the comparison, so time and random values do not cause it:
Navigating to this route from another page renders different content than loading /settings directly (2 differences).The check runs twice per route when the router supports prefetching: once as a plain navigation and once after the router prefetched the route.
It is a warning. Routes that render parallel routes (@slot folders) or that
an intercepting route can replace are reported as info, because the difference
is usually intended there.
Likely causes
- A layout that keeps state between navigations, such as a selected tab or an open panel.
- Module-level variables or a client store filled in by the previous page.
- A component that depends on the previous route, the referrer or history state.
- Different data on the client: the route fetches again after navigation and gets another result.
How to fix it
- Read data from the route. Params, search params and server data are the same for a direct load and a navigation; the previous page's state is not.
- Check layouts that keep state between navigations and components that depend on the previous route. Reset that state when the route changes, or move it into the URL.
"use client";
import { useSearchParams } from "next/navigation";
import { useSettingsStore } from "./store";
// Before: the tab comes from a store the previous page filled in
export function useActiveTabBefore() {
return useSettingsStore((state) => state.activeTab);
}
// After: the tab comes from the URL, the same on every kind of load
export function useActiveTab() {
return useSearchParams().get("tab") ?? "profile";
}To start the navigation from a specific page, set checks.navigation.from, or
navigateFrom on the route.
When the difference is intended
A modal from an intercepting route or a slot that keeps its page can differ on purpose. Add an ignore rule for the route:
import { defineConfig } from "hydration-proof";
export default defineConfig({
checks: { navigation: true },
ignore: {
issues: [
{
code: "HP5004",
route: "/photos/*",
reason: "Opens as a modal after navigation",
},
],
},
});See ignoring findings for the other fields.