Hydration & pre-hydration forms
An SSR page is visible at once but interactive only after its island hydrates. Nifra keeps that gap safe for you - you rarely have to think about it.
Forms and links - nothing to do
A <form method="post"> to a route, and any <a href>, use progressive enhancement: native submit/navigation before hydration, a no-reload client takeover after.
// Native POST before hydration, client takeover after. Nothing to do.
<form method="post">
<button type="submit">Save</button>
</form>JS-only forms - guarded automatically
The one risky shape is a form wired purely in JavaScript - a preventDefault handler with no native fallback. Submitted before hydration, the browser would fall back to a native GET of the current page (/?email=…), a broken navigation.
// doc-check: skip - illustrative island: `FormEvent` + an app-provided `authClient`.
// A form wired purely in JS (preventDefault, no native fallback).
async function onSubmit(e: FormEvent) {
e.preventDefault()
await authClient.signIn.email({ email, password })
}
return <form onSubmit={onSubmit}>…</form>Nifra blocks that native submit until hydration commits, so the worst case is a no-op click - never a broken navigation. It never touches a method="post" form or a GET form with a real action. Opt a form out with data-native:
<form data-native>…</form> {/* Nifra won't guard it - native submit is intended */}Gate other JS on the signal
For a visible “not ready” state, or a non-form interaction (canvas, drag-drop, a third-party widget), gate on data-nifra-hydrated (set on <html> once hydration commits) or the one-shot nifra:hydrated event.
html:not([data-nifra-hydrated]) [data-needs-js] { opacity: 0.6; pointer-events: none; }// doc-check: skip - illustrative island: React hooks + your `onSubmit`.
const [ready, setReady] = useState(false)
useEffect(() => setReady(true), [])
return <button disabled={!ready} onClick={onSubmit}>Sign in</button>
// or, framework-free:
if (document.documentElement.hasAttribute("data-nifra-hydrated")) start()
else document.addEventListener("nifra:hydrated", start, { once: true })