Performance

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