# Fix: Vercel 504 GATEWAY_TIMEOUT FUNCTION_INVOCATION_TIMEOUT

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

1. **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`, `fetch` keeps waiting for the answer.
2. **The handler never returns a response.** A code path forgets to `return` a `Response`, or an Express-style handler never calls `res.send()`. Vercel's error page lists this cause: "If no response is returned, the function will time out."
3. **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.
4. **Waiting on a database connection.** A pool that is full makes queries wait. See [too many clients](/guides/postgres-too-many-clients/).
5. **An infinite loop or a promise that never settles.**
6. **The project limit is lower than you think.** The project default in the dashboard, a `vercel.json` entry or a `maxDuration` export 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:

```bash
vercel logs runtime --environment production --query 'level:error' --since 1h
```

Add 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`.

```ts
// 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:

```ts
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

```ts
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.

```ts
// 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`:

```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.

```ts
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:

```ts
// 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](/docs/node/)), 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](https://vercel.com/docs/errors/FUNCTION_INVOCATION_TIMEOUT), checked 2 Nov 2026.
- [Vercel: Functions limits, max duration](https://vercel.com/docs/functions/limitations#max-duration), checked 2 Nov 2026.
- [Vercel: Configuring maximum duration](https://vercel.com/docs/functions/configuring-functions/duration), checked 2 Nov 2026.
- [Vercel: Fluid compute](https://vercel.com/docs/fluid-compute) (enabled by default for new projects since 23 April 2025), checked 2 Nov 2026.
- [Vercel CLI: vercel logs](https://vercel.com/docs/cli/logs), checked 2 Nov 2026.
- [Vercel Community: Task timed out after 15 seconds](https://community.vercel.com/t/vercel-runtime-timeout-error-task-timed-out-after-15-seconds/3234) (log line as reported by a user), checked 2 Nov 2026.
- [Next.js: maxDuration](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/maxDuration) and [after()](https://nextjs.org/docs/app/api-reference/functions/after), checked 2 Nov 2026.

## Related errors

- [Vercel FUNCTION_INVOCATION_FAILED](/guides/vercel-function-invocation-failed/): `500: INTERNAL_SERVER_ERROR Code: FUNCTION_INVOCATION_FAILED`
- [Postgres too many clients already](/guides/postgres-too-many-clients/): `FATAL: sorry, too many clients already`
- [OpenAI 429 insufficient quota](/guides/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](/guides/express-cannot-set-headers-after-sent/): `Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client`
- [Node.js unhandled promise rejection](/guides/node-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](/guides/).

[Start free](/console/register?utm_source=bugtape_seo&utm_campaign=error-guides). Free plan · No credit card · Set up in minutes. Or [try the live demo](/demo/?from=landing&start=1#capture) first.
