fix: wrap login page in Suspense (useSearchParams prerender crash)

- /login page used useSearchParams() without Suspense boundary
- Next.js 16 requires Suspense for useSearchParams during static prerender
- Extracted LoginForm component, wrapped in <Suspense>
- Removed --webpack flag (default Turbopack build now works)
- Build passes cleanly: ✓ Compiled successfully
This commit is contained in:
Z User committed 2026-10-29 00:42:15 +00:00
1 parent 576a85cd50
commit ece8626137
2 files changed
+32 -24

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000",
"dev:log": "next dev -p 3000 2>&1 | tee dev.log", "dev:log": "next dev -p 3000 2>&1 | tee dev.log",
"build": "prisma generate && next build --webpack && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/", "build": "prisma generate && next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
"start": "NODE_ENV=production bun .next/standalone/server.js 2>&1 | tee server.log", "start": "NODE_ENV=production bun .next/standalone/server.js 2>&1 | tee server.log",
"lint": "eslint .", "lint": "eslint .",
"db:push": "prisma db push --accept-data-loss", "db:push": "prisma db push --accept-data-loss",
+31 -23
View File
@@ -1,13 +1,13 @@
'use client' 'use client'
import { useState } from 'react' import { Suspense, useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { Loader2, Lock } from 'lucide-react' import { Loader2, Lock } from 'lucide-react'
export default function LoginPage() { function LoginForm() {
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const redirect = searchParams.get('redirect') || '/' const redirect = searchParams.get('redirect') || '/'
@@ -40,6 +40,32 @@ export default function LoginPage() {
} }
} }
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="password">Passwort</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Admin-Passwort eingeben"
autoFocus
autoComplete="current-password"
/>
</div>
{error && (
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
)}
<Button type="submit" className="w-full gap-2" disabled={loading || !password}>
{loading && <Loader2 className="size-4 animate-spin" />}
Anmelden
</Button>
</form>
)
}
export default function LoginPage() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background p-4"> <div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="w-full max-w-sm space-y-6"> <div className="w-full max-w-sm space-y-6">
@@ -53,27 +79,9 @@ export default function LoginPage() {
</p> </p>
</div> </div>
<form onSubmit={handleSubmit} className="space-y-4"> <Suspense fallback={<div className="text-center text-sm text-muted-foreground">Laden…</div>}>
<div className="space-y-2"> <LoginForm />
<Label htmlFor="password">Passwort</Label> </Suspense>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Admin-Passwort eingeben"
autoFocus
autoComplete="current-password"
/>
</div>
{error && (
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
)}
<Button type="submit" className="w-full gap-2" disabled={loading || !password}>
{loading && <Loader2 className="size-4 animate-spin" />}
Anmelden
</Button>
</form>
<p className="text-center text-xs text-muted-foreground"> <p className="text-center text-xs text-muted-foreground">
Geschützter Bereich — nur für autorisierte Benutzer. Geschützter Bereich — nur für autorisierte Benutzer.