PLG OS

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.

The six assertions
AssertionHow to checkFails onAutomated
Overlay absent from server HTMLfetch without JSmarkup contains overlay
No hydration mismatchconsole as error in testsany warning
Stored state read after mountunit test with mocked storageread during render
Portal container before overlayordering assertionoverlay mounts first
Soft navigation behaviourclient-side route changeguidance missing or duplicated
Hard reload behaviourfull page loadguidance missing or duplicated
Viewport variant after layoutresize then assertwrong 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.

Continue with PLG OS

Explore onboarding →