fix(web): wrap useSearchParams in Suspense so next build can prerender

/servers/new and /login/callback call useSearchParams() directly, which
bails the page out of static rendering and fails `next build` during
prerender. Split each into a thin Suspense wrapper + inner component.
Latent since `next dev` never prerenders — only surfaces in a prod build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marco Sadjadi 2026-05-21 00:36:56 +02:00
parent 38aa5875d3
commit 2b098c5d33
2 changed files with 40 additions and 4 deletions

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { Suspense, useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { apiFetch } from '@/lib/api'; import { apiFetch } from '@/lib/api';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -82,7 +82,7 @@ function specToEditable(spec: PreviewResponse['spec']): EditableSpec {
}; };
} }
export default function NewServerPage() { function NewServerPageInner() {
const router = useRouter(); const router = useRouter();
const [step, setStep] = useState<Step>('prompt'); const [step, setStep] = useState<Step>('prompt');
@ -708,6 +708,26 @@ export default function NewServerPage() {
); );
} }
// useSearchParams() forces client-side rendering — Next requires a Suspense
// boundary around it, or `next build` bails out of static generation.
export default function NewServerPage() {
return (
<Suspense
fallback={
<div className="mx-auto max-w-3xl px-6 py-8">
<h1 className="text-[22px] font-semibold tracking-tight">New MCP server</h1>
<div className="panel mt-10 p-8 text-center">
<Loader2 className="mx-auto animate-spin text-[--color-fg-muted]" size={20} />
<p className="mt-4 text-[13px] text-[--color-fg-muted]">Loading</p>
</div>
</div>
}
>
<NewServerPageInner />
</Suspense>
);
}
const SHARE_CATEGORIES = [ const SHARE_CATEGORIES = [
'productivity', 'productivity',
'developer-tools', 'developer-tools',

View File

@ -1,11 +1,11 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { Suspense, useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { Logo } from '@/components/logo'; import { Logo } from '@/components/logo';
import { apiFetch } from '@/lib/api'; import { apiFetch } from '@/lib/api';
export default function CallbackPage() { function CallbackInner() {
const router = useRouter(); const router = useRouter();
const params = useSearchParams(); const params = useSearchParams();
const token = params.get('token'); const token = params.get('token');
@ -53,3 +53,19 @@ export default function CallbackPage() {
</div> </div>
); );
} }
// useSearchParams() requires a Suspense boundary or `next build` cannot
// statically render this route.
export default function CallbackPage() {
return (
<Suspense
fallback={
<div className="flex min-h-screen items-center justify-center px-6">
<p className="text-[13px] text-[--color-fg-muted]">Verifying your magic link</p>
</div>
}
>
<CallbackInner />
</Suspense>
);
}