Fix Slow TTFB on Vercel — Edge Functions vs Serverless
TTFB over 600ms is almost always a server problem, not a frontend problem. On Vercel, the main cause is cold starts on serverless Node.js functions. Moving to Edge Runtime eliminates them.
Diagnose first
Open DevTools → Network → click your document request → Timing tab. If TTFB (Waiting for server response) is high, the problem is server-side. If TTFB is fine but the page is slow, the problem is rendering or resources.
Cause 1 — Serverless cold starts
Vercel serverless functions spin down after inactivity. The first request after a cold period takes 500-2000ms extra for the container to start.
// Move to Edge Runtime — no cold starts, runs globally
// app/api/data/route.ts (App Router)
export const runtime = 'edge';
export async function GET(request: Request) {
return Response.json({ data: 'fast' });
}
Edge Runtime has limitations: no Node.js APIs, no filesystem access, smaller bundle size limit. If you need Node.js APIs, use edge middleware for the fast parts and defer heavy work to serverless.
Cause 2 — No caching
If your function fetches from a database or external API on every request, add caching at the function level:
// App Router — cache at the fetch level
export async function GET() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 } // cache for 60 seconds
});
return Response.json(await data.json());
}
// Or use unstable_cache for fine-grained control
import { unstable_cache } from 'next/cache';
const getCachedData = unstable_cache(
async () => fetchFromDB(),
['data-cache-key'],
{ revalidate: 3600 }
);
Cause 3 — Function not deployed at the right region
Serverless functions deploy to a single region by default. If your users are in Europe and your function is in US East, add 100-200ms of latency.
// vercel.json — set function region
{
"functions": {
"api/**/*.js": {
"regions": ["fra1", "lhr1"] // Frankfurt, London
}
}
}
Cache static pages at the CDN
For pages that do not change per user, set Cache-Control to let Vercel's CDN serve them without hitting the function:
// Next.js — set cache headers in route
export async function GET() {
return new Response(JSON.stringify({ data: 'ok' }), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
},
});
}
Run a live PageSpeed audit → SpeedFixer