Suspense has to be above the component that calls useSearchParams, not inside it. Split it:
// app/login/page.tsx (server component, no "use client")
import { Suspense } from "react";
import LoginForm from "./login-form";
export default function Page() {
return (
<Suspense fallback={null}>
<LoginForm />
</Suspense>
);
}login-form.tsx gets the "use client" and the useSearchParams call. Dev doesn't catch this because it renders on request; only the production build tries to prerender the page statically.
Alternative: page.tsx receives searchParams as a prop (it's a promise in Next 15, so await it) and passes next down. No client hook needed at all.