Fix this error · Servers and hosting
Fix: Vercel 504 GATEWAY_TIMEOUT FUNCTION_INVOCATION_TIMEOUT
Why Vercel stops a function with a 504, current duration limits per plan, maxDuration, upstream timeouts and background jobs, with code.
Your Vercel Function ran longer than its maximum duration, so Vercel stopped it and answered the request with a 504. The fix that works most often is to put a timeout on the slow database or API call inside the function, then move work that really takes minutes out of the request into a queue or background job. Raise maxDuration only when the work has a known upper limit that fits your plan.
| Fact | Detail |
|---|---|
| Error | 504: GATEWAY_TIMEOUT Code: FUNCTION_INVOCATION_TIMEOUT |
| In the logs | Vercel Runtime Timeout Error: Task timed out after 15 seconds (the number is your function's limit) |
| Where you see it | The browser or API client, the Vercel Logs page, vercel logs |
| Usual cause | A slow upstream API or database call, or a handler that never returns a response |
| Usual fix | Time out the upstream call yourself, return a response on every path, and move long work to a background job |
| BugTape catches it | In the browser, yes, when the request follows a click. On the server, only if you time out first and report it |
What the error means
Every Vercel Function has a maximum duration. Vercel's docs define it as "the longest time a function invocation can run before Vercel terminates it", and it includes the time spent streaming the response. When an invocation passes that limit, Vercel returns 504 with the code FUNCTION_INVOCATION_TIMEOUT and the name Gateway Timeout.
With Fluid compute, which Vercel turned on by default for new projects on 23 April 2025, the Node.js, Bun and Python runtimes have these limits:
| Plan | Default | Maximum | Extended maximum |
|---|---|---|---|
| Hobby | 300 s | 300 s | none |
| Pro | 300 s | 800 s | 1800 s (beta) |
| Enterprise | 300 s | 800 s | 1800 s (beta) |
Durations above 800 seconds are in beta, must be set per function in code or vercel.json, and work only on specific Node.js, Bun and Python versions. Older projects that do not have Fluid compute turned on can have lower limits. Vercel's error page says Fluid compute "provides significantly longer durations", so check Settings → Functions for your project's current values. The Edge runtime has a different rule: it must begin sending a response within 25 seconds and can then stream for up to 300 seconds.
The limit is wall-clock time. Waiting on a slow API counts toward it even though it does not use CPU.
Common causes
- A slow upstream call. An AI model, a payment API, a third-party webhook or a database query takes longer than the limit, or never answers. Without a
signal,fetchkeeps waiting for the answer. - The handler never returns a response. A code path forgets to
returnaResponse, or an Express-style handler never callsres.send(). Vercel's error page lists this cause: "If no response is returned, the function will time out." - Work that does not fit in a request. Sending hundreds of emails, generating a large export, processing an upload or running a long AI chain inside one request.
- Waiting on a database connection. A pool that is full makes queries wait. See too many clients.
- An infinite loop or a promise that never settles.
- The project limit is lower than you think. The project default in the dashboard, a
vercel.jsonentry or amaxDurationexport sets a shorter limit than the plan allows.
How to fix it
Find the slow step first
Open the deployment's Logs page, or use the CLI:
vercel logs runtime --environment production --query 'level:error' --since 1hAdd timing logs around each upstream call so the next timeout shows which one was still running.
Time out your own upstream calls
Give every external call a deadline shorter than the function's limit. Then you control what happens: you can return a clear error, retry, or record the failure. Node 20 and later support AbortSignal.timeout(), which rejects with a TimeoutError.
// app/api/summary/route.ts
export async function POST(request: Request) {
const body = await request.json();
try {
const res = await fetch('https://api.example.com/summarize', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
signal: AbortSignal.timeout(20_000), // well under the function's limit
});
if (!res.ok) return Response.json({ error: 'upstream_failed' }, { status: 502 });
return Response.json(await res.json());
} catch (err) {
if (err instanceof Error && err.name === 'TimeoutError') {
return Response.json({ error: 'upstream_timeout' }, { status: 504 });
}
throw err;
}
}Do the same for database queries. With node-postgres, set statement_timeout on the pool so a slow query fails quickly:
import { Pool } from 'pg';
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
statement_timeout: 10_000, // ms
});Return a response on every path
export async function GET(request: Request) {
const id = new URL(request.url).searchParams.get('id');
if (!id) {
return Response.json({ error: 'id is required' }, { status: 400 }); // without return, this path hangs
}
const item = await getItem(id);
return Response.json(item);
}Raise maxDuration when the work has a known limit
In the Next.js App Router, export maxDuration (in seconds) from the route file, page or layout. For Server Actions, set it on the page that uses them.
// app/api/report/route.ts
export const maxDuration = 120;
export async function POST(request: Request) {
// work that takes up to about two minutes
return Response.json({ ok: true });
}The Pages Router and plain /api functions use export const config = { maxDuration: 120 }. Other runtimes, and Next.js older than 13.5, use vercel.json:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"functions": {
"api/report.js": { "maxDuration": 120 }
}
}A value above your plan's maximum is not allowed. A longer limit also means you pay for the function's memory for longer when it hangs.
Move long work out of the request
Answer the request quickly and do the work elsewhere. For small follow-up tasks, Next.js 15.1 and later have after() from next/server, which runs after the response is sent. It still runs inside the same function invocation, so it is still bound by the function's maximum duration.
import { after } from 'next/server';
export async function POST(request: Request) {
const job = await request.json();
after(async () => {
await sendReceiptEmail(job); // runs after the response
});
return Response.json({ accepted: true }, { status: 202 });
}For work that takes minutes, put a job on a queue and process it in a worker, or use Vercel Workflows, which Vercel describes as having no duration limit. Return a job id and let the browser poll for the result.
Stream long AI responses
When a model takes a long time to finish, stream tokens back as they arrive. The user sees progress. The stream still counts toward the maximum duration.
How to catch it in production with BugTape
In the browser. The BugTape browser SDK reports a failed request when a fetch or XHR returns HTTP 400 or above, or fails with a network error, within 8 seconds of a click. It reports a hung request when a request started by a click has no response after 15 seconds. A function that times out on Vercel usually runs far longer than 15 seconds, so the report you get is Hung request: /api/summary (no response after 15000ms), with the click that started it, the page, the network log and the console output from before it. If the function has a short maxDuration and the 504 comes back within 8 seconds of the click, you get a failed-request report with the 504 status instead. Requests that are not started by a click, such as polling or a request on page load, are not reported automatically.
On the server. When Vercel stops a function at its limit, the function's code does not get a chance to run again. The Node SDK cannot send a report from a function that has already been stopped, so do not rely on it for the timeout itself. Time out your own upstream call earlier, as in the fix above, and report that:
// app/api/summary/route.ts
import * as bugtape from '@bugtape/node';
export async function POST(request: Request) {
try {
const res = await fetch(UPSTREAM_URL, { signal: AbortSignal.timeout(20_000) });
return Response.json(await res.json());
} catch (err) {
bugtape.captureException(err);
await bugtape.flush(2000); // send before the function is frozen
return Response.json({ error: 'upstream_timeout' }, { status: 504 });
}
}Initialise the Node SDK once in instrumentation.ts (see the Node.js docs), and set release. On Vercel, the SDK reads VERCEL_GIT_COMMIT_SHA when you pass no release. Repeats of the same timeout become one issue with a count. When you mark it fixed, a report from a newer release reopens it as a regression. Your coding agent can read the issue over MCP (npx -y bugtape connect claude, or cursor or codex) and propose the change. You review the pull request.
FAQ
What is the default timeout for a Vercel Function?
With Fluid compute, the default is 300 seconds on Hobby, Pro and Enterprise. Hobby cannot go higher. Pro and Enterprise can set up to 800 seconds, and up to 1800 seconds in beta for specific runtimes. Check Settings → Functions for your project, because a project default or maxDuration can set a lower value.
Why does my function time out on Vercel but work locally?
Locally there is no duration limit, your database is often on the same machine, and you are the only user. On Vercel the database may be in another region, upstream APIs see real traffic, and a full connection pool makes queries wait. Compare the function's region with your database's region, and log how long each upstream call takes.
Does streaming the response avoid the timeout?
No. Vercel counts the time spent sending a streamed response toward the maximum duration. Streaming helps the user see progress, but a stream that runs past the limit is still stopped.
Should I just set maxDuration to the maximum?
Only if the work can really take that long. A higher limit keeps a hung function running, and billing, for longer. Put a timeout on each upstream call first, and move work that takes minutes to a background job.
Sources
- Vercel: FUNCTION_INVOCATION_TIMEOUT, checked 2 Nov 2026.
- Vercel: Functions limits, max duration, checked 2 Nov 2026.
- Vercel: Configuring maximum duration, checked 2 Nov 2026.
- Vercel: Fluid compute (enabled by default for new projects since 23 April 2025), checked 2 Nov 2026.
- Vercel CLI: vercel logs, checked 2 Nov 2026.
- Vercel Community: Task timed out after 15 seconds (log line as reported by a user), checked 2 Nov 2026.
- Next.js: maxDuration and after(), checked 2 Nov 2026.
Related errors
- Vercel FUNCTION_INVOCATION_FAILED:
500: INTERNAL_SERVER_ERROR Code: FUNCTION_INVOCATION_FAILED - Postgres too many clients already:
FATAL: sorry, too many clients already - OpenAI 429 insufficient quota:
429 You exceeded your current quota, please check your plan and billing details. - Express: Cannot set headers after they are sent:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client - Node.js unhandled promise rejection:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
All guides: Fix an error.
Start free. Free plan · No credit card · Set up in minutes. Or try the live demo first.