Form Page Guard (Next.js App Router)
Next.js App Router adapter for Form Page unsaved-changes — provider, in-form guard, GuardedLink, and useGuardedRouter.
Plus Registry
Form Page Guard (Next.js App Router) ships on the authenticated @f-ui-plus registry. Configure FUI_PLUS_REGISTRY_TOKEN as described in Installation — Plus Registry.
Form Page Guard (Next.js App Router) blocks in-app navigation while a Form Page is dirty. Form Page itself is router-agnostic: it owns the native tab-close prompt and broadcasts guard state. This item is the Next.js App Router adapter. Requires Next.js ≥ 15.3 (onNavigate on Link).
This docs app does not run the Next.js App Router, so the live example is the wiring checklist. Copy the Usage snippet into a Next host.
When To Use
- The host is Next.js App Router and Form Page
warnOnUnsavedChangesshould stopLink/router.push/ back. - Pair with Form Page — do not install this adapter without it.
- Use Form Page Guard (TanStack Router) instead on TanStack Router hosts.
- Do not mix
next/link+useRouterwith a dirty form. OnlyGuardedLink/useGuardedRouterconsult the provider. - The native tab-close prompt stays on Form Page. This adapter does not replace
beforeunload.
Features
| Area | Behavior |
|---|---|
| Provider | FormNavigationGuardProvider at the app layout — owns the confirm dialog |
| Guard | FormPageUnsavedGuard inside FormPage registers dirty state (renders null) |
| Link | GuardedLink — same props as next/link; intercepts onNavigate while dirty |
| Router | useGuardedRouter — push / replace / back go through the same confirm |
| Dialog | Stay / Leave, plus optional Save draft & leave from Form Page confirm copy |
Installing
pnpm dlx shadcn@latest add https://ui.isaacfei.com/api/plus/r/form-page-guard-next.jsonnpx shadcn@latest add https://ui.isaacfei.com/api/plus/r/form-page-guard-next.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/api/plus/r/form-page-guard-next.jsonbunx shadcn@latest add https://ui.isaacfei.com/api/plus/r/form-page-guard-next.jsonWith the @f-ui-plus namespace configured in components.json, shadcn add @f-ui-plus/form-page-guard-next also works.
registryDependencies: @f-ui-plus/form-page. Peer: Next.js ≥ 15.3 (next/link, next/navigation).
Usage
import {
FormNavigationGuardProvider,
FormPageUnsavedGuard,
GuardedLink,
useGuardedRouter,
} from "@/components/f-ui/form-page/navigation-guard/next-app-router";
// app/layout.tsx
<FormNavigationGuardProvider>{children}</FormNavigationGuardProvider>;
// form page
<FormPage form={form} onFinish={handleSubmit} warnOnUnsavedChanges>
<FormPageUnsavedGuard />
{/* fields */}
</FormPage>;
function Actions() {
const router = useGuardedRouter();
return (
<>
<GuardedLink href="/orders">Back to orders</GuardedLink>
<button type="button" onClick={() => router.push("/orders")}>
Cancel
</button>
</>
);
}Examples
Three Pieces
Provider at the layout, guard inside Form Page, guarded navigation primitives everywhere else. Skipping any one of these lets a dirty form navigate away with no confirm.
Three pieces
- Wrap the app in
FormNavigationGuardProvider(layout). - Drop
FormPageUnsavedGuardinsideFormPage. - Navigate with
GuardedLinkoruseGuardedRouter— notnext/link/useRouteralone.
"use client";
import { SectionCard } from "@/components/f-ui/section-card/section-card";
/** Next.js App Router wiring — this docs app is not Next, so the adapter is not mounted. */
export function FormPageGuardNextDemo() {
return (
<SectionCard title="Three pieces">
<ol className="text-sm text-muted-foreground list-decimal space-y-2 pl-5">
<li>
Wrap the app in <code>FormNavigationGuardProvider</code> (layout).
</li>
<li>
Drop <code>FormPageUnsavedGuard</code> inside <code>FormPage</code>.
</li>
<li>
Navigate with <code>GuardedLink</code> or <code>useGuardedRouter</code> — not{" "}
<code>next/link</code> / <code>useRouter</code> alone.
</li>
</ol>
</SectionCard>
);
}API Reference
FormNavigationGuardProvider
| Prop | Type | Description |
|---|---|---|
children | ReactNode | App tree. |
confirm | UnsavedChangesConfirm | Fallback dialog copy when a form does not register its own. |
FormPageUnsavedGuard
No props. Must be a child of FormPage (reads guard context) and under FormNavigationGuardProvider. Renders null; the provider paints the dialog.
Throws if the provider is missing.
GuardedLink
Forwards next/link props. While dirty, onNavigate is prevented and the confirm runs before router.push(href).
useGuardedRouter
Returns { push, replace, back, forward, refresh }. push / replace / back call the guard registry; forward and refresh pass through.
useNextFormNavigationGuard
Low-level { when, confirm? } registration if you are not using FormPageUnsavedGuard. Prefer the drop-in guard inside Form Page.