Guides
Sign in

Guide · Speed

Why your Vercel API routes are slow when your pages are fast

Cached pages come from a Vercel edge near each visitor. Everything else, like API routes, server actions and dynamic pages, runs in one function region: Washington DC, unless you chose another. If your database is on another continent, every query crosses that distance and back. Put your functions next to your database.

A real example: 0.2 s pages, 1.1 to 1.3 s API routes

The same small Next.js 15 site on Vercel’s free Hobby plan as in our guide to ISR reads. Its database was a hosted one in Mumbai. Its functions ran in Washington DC, Vercel’s default, which nobody had ever changed. Most of its visitors were in India. On 2 November 2026 we timed it from India with curl, to the first byte of the answer:

A cached page, from the Mumbai edgeabout 0.21 s
An API route that reads the database, 5 runs1.06–1.33 s
Database regionMumbai
Function regionWashington DC
Visitors from Indiaabout 60%
Page views from India88%

The database wasn’t slow, and neither was Vercel. The API route was slow because its function ran on the other side of the world from both the visitors and the database.

Why are my API routes slow when my pages are fast?

Because they’re served from different places. A static or cached page is answered by the Vercel edge nearest the visitor. Anything that isn’t cached, such as an API route, a server action or a page rendered on each request, has to run as a function, and your functions run in one region. Vercel’s docs say that for all new projects that region is Washington DC, iad1 (Vercel’s function region docs).

On the example site, one call to the API route from India went like this:

  1. The request reached Vercel’s edge in Mumbai. The answer wasn’t cached there.
  2. The edge passed the request to the function in Washington DC.
  3. The function asked the database in Mumbai, and waited for the answer to come back to Washington DC. Once for every query.
  4. The function sent its answer back to the Mumbai edge, and on to the visitor.

Many people create their database in the region nearest to them, and never change Vercel’s default. Then the function and the database end up on different continents, and nothing warns you.

Why does every database query make it worse?

Because each query is a round trip from the function to the database and back. Mumbai and Washington DC are about 12,800 km apart in a straight line. Even at the speed of light in optical fibre, one round trip over that distance takes about 128 ms, and real cables take longer routes. A route that makes 3 queries one after the other pays that at least 3 times. If the function opens a new database connection, setting it up costs a few more round trips before the first query starts.

A rough rule: extra wait ≈ round trip between function and database × (queries + connection set-up). The same goes for anything else your function calls on every request, like an auth service.

So the cause isn’t the database. It’s the distance between the function and the database, multiplied by the number of queries.

How do I check my own app?

  1. See where a request went. Run this on an API route that reads your database, with your own address:

    Command for your terminal

    curl -sI https://yourapp.com/api/something | grep -i x-vercel-id
    The x-vercel-id header lists the Vercel regions the request passed through, and the region your function ran in (Vercel’s response header docs). A cached answer shows only the edge, like bom1::…. When a function ran, its region is the last code before the long request ID: bom1::iad1::… means the edge was Mumbai and the function ran in Washington DC.
  2. Find your database’s region in your database provider’s dashboard. Most hosted databases show a city or a cloud region name such as ap-south-1. Vercel’s list of regions shows which of its codes sits in which cloud region; a few are below.
  3. Compare the time to first byte of a cached page and of that API route. This prints the seconds until the first byte arrived:

    Command for your terminal

    curl -o /dev/null -s -w '%{time_starttransfer}\n' https://yourapp.com/api/something
    Run it a few times, for both addresses. If the API route takes several times as long as the cached page, and the two regions are far apart, distance is the likely cause.
Mumbai, ap-south-1bom1
Singapore, ap-southeast-1sin1
Sydney, ap-southeast-2syd1
Frankfurt, eu-central-1fra1
London, eu-west-2lhr1
Washington DC, us-east-1iad1

How do I move my functions next to my database?

Put the function next to the database, not next to the visitor. Vercel’s own docs say the same: functions should run “in the same region as your database, or as close to it as possible” (Vercel’s regions page). There are two ways to set it (Vercel’s function region docs):

  • In the dashboard: your project’s Settings, then Functions, then Function Regions. Pick the region nearest your database, then redeploy.
  • In vercel.json: add a regions key with your own region code, then deploy:

    vercel.json

    {
      "regions": ["bom1"]
    }
    If vercel.json sets regions, it wins over the dashboard (Vercel’s Fluid compute docs), so check there first if a dashboard change seems to do nothing.

The Hobby plan allows one function region, which is all this fix needs. Pro and Enterprise allow more. On 2 November 2026 Vercel’s pages disagreed on the Pro number (5 on the regions page, 3 on the Fluid compute page), so check before you rely on it.

You may also see Next.js’s export const preferredRegion suggested for single routes. Next.js now marks it as deprecated and says to remove it (Next.js’s preferredRegion page), so use the project setting instead.

What it did on the example site. Its owner deployed a preview of the same code with "regions": ["bom1"] (Hobby accepted it) and timed the same API route against production, still in Washington DC. Calls alternated between the two, each with its own query string so no cache could answer, from India on 2 November 2026, 10 warm runs each. x-vercel-id changed from bom1::iad1::… to bom1::bom1::….

API route, function in Washington DC (median)1.12 s
API route, function in Mumbai (median)0.30 s
A cached page from the same edge, same session0.26–0.28 s
First call to the new deployment, once1.78 s

About 3.7 times faster. With the function beside the database, an API call that reads the database was nearly as quick as a cached page. The one slow first call was the new deployment starting up. Two caveats: the preview was timed with vercel curl, which adds the header that gets past preview protection, and production with plain curl (cached pages timed both ways came out about the same); and everything was measured from one place.

A ready-made fix prompt for a slow server is part of Pro, coming soon. What we found stays free.

Won’t that be slower for visitors far from my database?

Not for anything that reads the database. Their request has to reach the database anyway. We didn’t measure this case; it follows from the path. With the function beside the database, it crosses the distance once each way. With the function near the visitor, or in a third region, every query crosses it. A visitor in London calling a Mumbai database waits less with the function in Mumbai than with the function in Washington DC.

A route that never touches the database could get a little slower for far visitors. Those are usually the ones you can make static or cache, which takes the function out of the way completely.

What if my users are all over the world?

  • Choose the database region from where your users are, from day one. Moving a database later is far more work than moving a function.
  • Cache public answers at the edge. A function response with Cache-Control: s-maxage=60, stale-while-revalidate=300 can be kept and served by Vercel’s edge, and refreshed in the background (Vercel’s CDN cache docs). Use it only for data that’s the same for everyone, never for anything about a signed-in person.
  • Move reads that don’t need fresh data into static pages. A page built ahead of time is served from the nearest edge, with no function and no query.
  • Use read copies in other regions if your provider has them. Supabase can put read-only copies of your database in other regions (Supabase’s docs). Neon’s read replicas stay in the same region as the database (Neon’s docs). Writes still go to the main database.

Doesn’t the Edge runtime fix this?

No. Edge functions and middleware run near the visitor by default (Vercel’s Edge runtime docs). That’s great for work that needs nothing else, like a redirect. But an edge function that queries a far-away database pays the distance on every query, just like a function in the wrong region. Vercel now recommends moving from the Edge runtime to Node.js.

Should I put my static files on S3 or CloudFront?

Usually not, if you’re on Vercel. Vercel already serves your static files from its edge network. Next.js’s docs say so directly: “Deploying to Vercel automatically configures a global CDN for your Next.js project. You do not need to manually set up an Asset Prefix.” (Next.js’s assetPrefix page). On the example site, files under /_next/static came back with cache-control: public, max-age=31536000, immutable and x-vercel-cache: HIT from the Mumbai edge.

  • S3 on its own lives in one region, so for most visitors it’s further away than Vercel’s edge, and slower.
  • S3 with CloudFront mostly repeats what Vercel already does for these files.
  • Moving them with Next.js’s assetPrefix lowers your Vercel CDN request count, but not your ISR reads: pages and their data still come from Vercel. It doesn’t move files in your public folder either.
  • It adds work and a second address. A visitor’s first visit needs one more DNS lookup and connection before the files start, which costs most on phones. Every deploy needs an upload step, old versions have to stay up for tabs that are still open, and fonts from another address need CORS headers.

It can make sense for large media files or downloads, or at very high traffic where the cost of Vercel’s CDN requests matters. For a typical small app, it adds moving parts without making anything faster. If your request counts are the worry, our ISR reads guide covers what drives them.

What this can’t tell you

We check your site from outside, like a visitor. We time how long your home page takes to arrive, once, from our server. We don’t call your API routes, and we can’t see your function region, your database’s region or how many queries a route makes. If your home page is rendered on each request and reads your database, a far-away function shows up in that time. The timings here come from one site, measured from one place on one day; yours depend on your regions, your queries and your visitors. Vercel’s plans and defaults can change, so check their pages for your plan.

Related

Sources