"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; /** * Dev-only guard for routes whose server render is pathologically slow under * the Next.js 16 dev runtime (see payloadcms/payload#14487): the dashboard * takes roughly 100s per render in dev while the browser gives up around 60s, * so clicking any link to it looks like a hang. This component intercepts * plain left-clicks on anchors pointing at those routes, shows a confirm * dialog, and only navigates when the user insists. * * Gaps by design: direct URL entry, page refresh, modifier-clicks (Ctrl/Cmd + * click), and programmatic router.push calls (e.g. the command palette) are * not gated. Anchors inside a [data-dev-gate-bypass] ancestor are also * exempt, so a specific link can opt out. */ const GATED_PATHS = new Set(["/"]); function resolveTarget(href: string): string | null { try { const url = new URL(href, window.location.origin); if (url.origin !== window.location.origin) return null; if (!GATED_PATHS.has(url.pathname)) return null; return url.pathname + url.search + url.hash; } catch { return null; } } export function DevDashboardGate() { const router = useRouter(); const [open, setOpen] = useState(false); const [target, setTarget] = useState("/"); const proceedRef = useRef<() => void>(() => undefined); useEffect(() => { if (process.env.NODE_ENV !== "development") return undefined; function onClick(event: MouseEvent) { if (event.defaultPrevented) return; if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { return; } const anchor = (event.target as Element | null)?.closest?.("a"); if (!anchor) return; if (anchor.hasAttribute("download")) return; const targetAttr = anchor.getAttribute("target"); if (targetAttr && targetAttr !== "_self") return; if (anchor.closest("[data-dev-gate-bypass]")) return; const next = resolveTarget(anchor.getAttribute("href") ?? ""); if (!next) return; event.preventDefault(); event.stopPropagation(); proceedRef.current = () => { setOpen(false); router.push(next); }; setTarget(next); setOpen(true); } document.addEventListener("click", onClick, { capture: true }); return () => document.removeEventListener("click", onClick, { capture: true }); }, [router]); if (process.env.NODE_ENV !== "development") return null; return ( { if (!next) setOpen(false); }} > Open the dashboard in dev mode? In dev, {target} renders the dashboard server-side in roughly 100 seconds (a known Next.js 16 dev-runtime regression) and the browser usually gives up around 60s with an empty response. Production serves the same page in a few seconds. Stay here, or open anyway and wait. Stay here proceedRef.current()}>Open anyway ); }