Hydration Proof

Search documentation

Find a page or section

HP5004: The page differs after client-side navigation

HP5004 (navigation-mismatch) means a route renders different content when reached through the app's router than when its URL is loaded directly. Fix it.

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.

CodeHP5004
Namenavigation-mismatch
Default severityWarning
GroupInteraction during hydration
What it meansNavigating 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

  1. 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.
  2. 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.
app/settings/tabs.tsx
"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:

hydration-proof.config.ts
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.