All posts
ffmpeg vercel

FFmpeg on Vercel Functions: What Breaks and What to Do

Running FFmpeg in a Vercel Function: the 4.5 MB body limit, timeouts, /tmp space and bundle size that break video jobs, with fixes and when to offload.

You can run FFmpeg in a Vercel Function on the Node.js, Bun or Python runtime, with a static binary in the bundle. It breaks in four places: video uploads hit the 4.5 MB body limit, long encodes hit the duration limit, large files fill the 500 MB /tmp, and the binary can push the bundle past 250 MB or be left out of it.

Below are the limits from Vercel’s docs, the errors you will see, the fixes for each, and when to move the FFmpeg work out of the function. For other platforms, see how to run FFmpeg in the cloud.

The Vercel Functions limits that matter for video

LimitHobbyPro and Enterprise
Maximum duration300 s800 s (1,800 s in beta)
Memory and CPU2 GB / 1 vCPUUp to 4 GB / 2 vCPU
Request or response body4.5 MB4.5 MB
Writable /tmpUp to 500 MBUp to 500 MB
Bundle size (uncompressed)250 MB (up to 5 GB with large functions, beta)Same

Sources: Vercel Functions limits and runtimes, checked on 16 October 2026. Values are for Fluid compute.

The Edge runtime runs in V8 isolates, so it cannot start a binary at all. Use the Node.js runtime for FFmpeg.

What breaks, and the fix for each

413 FUNCTION_PAYLOAD_TOO_LARGE on upload

The function body is at most 4.5 MB, so a user cannot upload a video through it.

Fix: upload from the browser straight to storage (Vercel Blob client uploads, or a presigned S3 or R2 URL), then send the function only the file URL. FFmpeg can read the input from an HTTPS URL.

ENOENT or spawn ... ffmpeg errors

The binary is not in the deployed function. Bundlers trace JavaScript imports; a binary file next to them is often left out.

Fix: use a package that ships a static Linux binary (such as ffmpeg-static) and tell the bundler to include it. In Next.js:

next.config.mjs
export default {
outputFileTracingIncludes: {
'/api/transcode': ['./node_modules/ffmpeg-static/ffmpeg'],
},
}

Check that the binary matches the function’s operating system and architecture, and watch the bundle size: a full FFmpeg build uses a large part of the 250 MB.

504 FUNCTION_INVOCATION_TIMEOUT

The encode took longer than the function’s maximum duration.

Fix: set maxDuration up to your plan’s limit, use a faster preset or a lower resolution, and keep long jobs out of the request path entirely (see below).

app/api/transcode/route.ts
export const runtime = 'nodejs'
export const maxDuration = 300

ENOSPC (no space left on device)

The input and output files in /tmp passed 500 MB.

Fix: read the input from its URL in place of a download, write only the output to /tmp, upload it, and delete it. For outputs near the limit, the function is the wrong place for the job.

Workarounds people use

Raise the limits. Pro gives 800 seconds and 4 GB, which is enough for many short clips. It does not change the body limit or /tmp.

The Container runtime. Vercel can run a function from an OCI image built from a Dockerfile.vercel, which makes the FFmpeg build easier to control. The duration and body limits still apply to requests.

Vercel Workflows. For multi-step work with no duration limit, Workflows can pause and resume. A single long encode still needs a place to run that allows it.

A separate worker service. Run FFmpeg on a container platform that reads a queue, and keep Vercel for the app. This works, but you now run two systems.

Or send the job to an API

Keep the FFmpeg arguments and move the execution. The route handler sends one small JSON request and returns at once; Xora runs FFmpeg and calls a webhook route when the job ends.

app/api/transcode/route.ts
export const runtime = 'nodejs'
export async function POST(request: Request) {
const { videoUrl } = (await request.json()) as { videoUrl: string }
const res = await fetch('https://api.xora.sh/v1/jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.XORA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
mode: 'ffmpeg',
input_files: { in_1: videoUrl },
output_files: { out_1: 'upload_720p.mp4' },
ffmpeg: {
args: ['-i', '{{in_1}}', '-vf', 'scale=-2:720', '-c:v', 'libx264', '-crf', '23', '-c:a', 'aac', '{{out_1}}'],
},
webhookUrl: `${process.env.APP_URL}/api/xora-webhook`,
}),
})
return Response.json(await res.json(), { status: res.status })
}
app/api/xora-webhook/route.ts
export const runtime = 'nodejs'
export async function POST(request: Request) {
const { data } = (await request.json()) as { data: { jobId: string } }
// Read the job back with your key in place of trusting the request body.
const res = await fetch(`https://api.xora.sh/v1/jobs/${data.jobId}`, {
headers: { Authorization: `Bearer ${process.env.XORA_API_KEY}` },
})
const job = await res.json()
if (job.state === 'completed') {
// Save job.output.signedUrl (or copy the file to your storage) here.
}
return new Response(null, { status: 200 })
}

Both functions finish in the time an HTTP request takes, so the duration, /tmp, memory and bundle limits stop applying to the video work. Return 200 from the webhook quickly: Xora retries a failed delivery a few times and then stops (see webhooks). Credits are billed for machine time and output size; failed and rejected jobs cost nothing. See pricing.

Next steps

Frequently asked questions

Can I run FFmpeg on Vercel?

Yes, in a Vercel Function on the Node.js, Bun or Python runtime, with a static FFmpeg binary in the function bundle. Not on the Edge runtime. The job must fit the function limits: 300 seconds on Hobby and up to 800 seconds on Pro and Enterprise, 2 GB of memory on Hobby and up to 4 GB on Pro, 500 MB of /tmp, and a 4.5 MB request body.

Why do I get FUNCTION_PAYLOAD_TOO_LARGE when I upload a video?

The request or response body of a Vercel Function is at most 4.5 MB, so a video upload through the function fails with a 413 error. Upload the file from the browser straight to storage (for example with a presigned URL or a client upload), then send the function the file URL.

Why can't my Vercel Function find the FFmpeg binary?

The binary is not in the deployed bundle. Bundlers trace JavaScript imports, not binary files. In Next.js, add the binary path to outputFileTracingIncludes for the route, and check that the binary is built for Linux on the function's architecture.

How do I run long FFmpeg jobs from a Vercel app?

Do not run them in the request. Send the job to a service that has no request timeout, such as a container platform or an FFmpeg API, and receive the result with a webhook. Vercel also has a Container runtime and Vercel Workflows for long-running work.

Ready to try Xora?

Replace your local FFmpeg binaries with a secure, high-performance cloud API.