Implementation worksheet · 6 min read
A Next.js Hydration Acceptance Test for In-App Components
Assert six things in an automated test: the server HTML does not contain the overlay (guidance is a client concern and rendering it server-side leaks it to crawlers and to users with JavaScript disabled), no hydration mismatch warning appears in the console, the component mounts only after hydration completes, targeting decisions that depend on browser-only state do not run during server render, the overlay's portal container exists before the overlay mounts, and a full page reload mid-guidance behaves identically to a soft navigation. The common thread is that in-app guidance is client-only state layered over server-rendered markup, and the seam between those is where it fails.
In-app components — tours, tooltips, checklists, banners — depend on browser-only facts: viewport size, stored state about what this user has seen, and elements whose positions only exist after layout. Next.js renders on the server first. Components that read those facts during render produce markup the client then disagrees with, and the symptoms are indirect: a flash of a tour the user already dismissed, a warning nobody reads, or an overlay that renders at the wrong position for one frame.
Put it into practice
1. Assert the overlay is absent from server HTML
Fetch the page without JavaScript and check the markup. Guidance in server output means crawlers index it and no-JS users receive dead markup. This is one assertion against the raw response and it catches a whole class of accidental server rendering.
2. Fail the test on hydration warnings
Hydration mismatches log as warnings, and warnings get ignored in a noisy console. Turn them into failures in the test environment so the mismatch is caught by the run rather than by a user seeing a flash of the wrong content.
3. Gate anything reading stored state behind mount
Whether this user has seen a tour lives in storage or an API, neither available during server render. Read it after mount, and render nothing until then. This is the specific pattern behind the flash of already-dismissed guidance, and it is the most common instance of the whole category.
4. Confirm the portal container exists first
Overlays usually render into a portal. If the container is created by a layout that hydrates after the overlay tries to mount, the overlay silently renders nowhere. Assert container-then-overlay ordering rather than trusting it.
5. Test soft navigation and hard reload separately
A client-side route change and a full reload take different paths through hydration, and guidance often works on one and not the other. Both belong in the test, because real users do both and only one of them is the path developers exercise.
6. Check viewport-dependent decisions after layout
Anything choosing a mobile or desktop variant must decide after layout, not from a guessed default. Deciding during render means the first paint can show the wrong variant and then swap, which is visible and reads as a glitch.
The six assertions
Copy this structure into your review document and record your observed result for each row.
| Assertion | How to check | Fails on | Automated |
|---|---|---|---|
| Overlay absent from server HTML | fetch without JS | markup contains overlay | |
| No hydration mismatch | console as error in tests | any warning | |
| Stored state read after mount | unit test with mocked storage | read during render | |
| Portal container before overlay | ordering assertion | overlay mounts first | |
| Soft navigation behaviour | client-side route change | guidance missing or duplicated | |
| Hard reload behaviour | full page load | guidance missing or duplicated | |
| Viewport variant after layout | resize then assert | wrong variant on first paint |
A failure worth checking
The flash of dismissed guidance. Server render has no access to the fact that this user dismissed the tour last week, so it renders the tour. The client hydrates, reads storage, and removes it. The user sees a tour they already dismissed appear and vanish on every page load. Each individual load is under a second and nobody files a bug; collectively it is the most-noticed defect in server-rendered in-app guidance, and it is exactly the mount-gate assertion above.
Common questions
Does the app router change any of this?
The mechanism differs but the seam does not. Browser-only state still cannot inform server output, and anything reading it still has to wait for the client. Verify the assertions against your own version rather than assuming a framework release removed the category.
Can we just render guidance client-side only and skip the tests?
Client-only rendering removes the mismatch but not the ordering problems — portal containers, layout-dependent positioning and the soft-versus-hard navigation difference all remain. Fewer assertions, not none.
Basis and scope
This is a proposed implementation method using illustrative examples, not a measured benchmark or a customer case study. Prepared with AI assistance. Validate product-specific behavior against current documentation and your own test environment.