How do I bypass the 4.5MB body size limit of Vercel Serverless Functions?

When trying to request or upload a large file from a Serverless Function, you may have seen a 413: FUNCTION_PAYLOAD_TOO_LARGE error. There are a few reasons why this error can occur, based on the following limits:

Limit

Streaming functions

Non-streaming functions

Request Body

4.5 MB

4.5 MB

Response Body

No limit

4.5 MB

To understand your function, you may first want to measure the response body size.

If the request body is too large:

  • This is when the body sent from the user/client to the serverless function exceeds the 4.5 MB limit.
  • The most common reason why this happens is when you are uploading large files. Instead, you may want to upload directly to the source.

If the response body is too large:

Measure response body size

To understand how to optimize your function, you first need to know how large its response is.

Local testing

The most efficient way to measure the size of a function response body is to test locally. Perform a local build using production data or environment variables to ensure that the volume of data that the function handles closely matches production.

Once the build is complete, start the local server with pnpm start and then use curl to get the response size of a given route:

curl localhost:3000/your/page -w '%{size_download}'

You can also see this within Chrome DevTools under the Network tab. However, note that by default this will show you the gzipped size. To see the true size, enable Big Request Mode.

Reducing response size

The best way to avoid the 413 error is to reduce the amount of data being returned per function request. For example, filter for more specific data, such as 10 products instead of all products. You could also refactor large pages into smaller parallel routes, each fetching their data independently.

Streaming responses

If reducing the response size is not feasible, consider streaming your function responses. For customers using Next.js, refer to the official Next.js documentation for further guidance on streaming.

Request directly from the source

Serverless Functions are designed to respond quickly to clients and should be treated like a lightweight API layer, not a media server.

If you have a large file like a video that you need to send to a client, you should consider storing those assets in a dedicated media host and making them retrievable with a pre-signed URL that contains access control policies directly in the URL.

This will ensure that your assets are securely accessed in a manner that you control. CloudinaryFaunaDB, and AWS S3 are all examples of services that support this.

Upload directly to the source

Similar to requesting assets securely, you can upload large files directly to a media host from your browser without needing a Serverless Function as a proxy. Vercel Blob, CloudinaryFaunaDB, and AWS S3 are all solutions for this among others.

If you need to upload files larger than 4.5 MB to Vercel Blob, you can use client uploads, where the file is sent directly from the client (e.g. a browser) to Vercel Blob. This transfer is done securely so as not to expose your Vercel Blob store to anonymous uploads. The security mechanism is based on a token exchange between your server and Vercel Blob. Use the following steps to achieve this:

Step 1: Create a client upload page

This page allows you to upload files to Vercel Blob. The files will go directly from the browser to Vercel Blob without going through your server. Behind the scenes, the upload is done securely by exchanging a token with your server before uploading the file.

app/avatar/upload/page.tsx
'use client';
import { type PutBlobResult } from '@vercel/blob';
import { upload } from '@vercel/blob/client';
import { useState, useRef } from 'react';
export default function AvatarUploadPage() {
const inputFileRef = useRef<HTMLInputElement>(null);
const [blob, setBlob] = useState<PutBlobResult | null>(null);
return (
<>
<h1>Upload Your Avatar</h1>
<form
onSubmit={async (event) => {
event.preventDefault();
const file = inputFileRef.current.files[0];
const newBlob = await upload(file.name, file, {
access: 'public',
handleUploadUrl: '/api/avatar/upload',
});
setBlob(newBlob);
}}
>
<input name="file" ref={inputFileRef} type="file" required />
<button type="submit">Upload</button>
</form>
{blob && (
<div>
Blob url: <a href={blob.url}>{blob.url}</a>
</div>
)}
</>
);
}

Step 2: Create a client upload route

The responsibility of this client upload route is to:

  1. Generate tokens for client uploads
  2. Listen for completed client uploads, so you can update your database with the URL of the uploaded file for example

The @vercel/blob npm package exposes a helper to implement said responsibilities.

app/api/avatar/upload/route.ts
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
import { NextResponse } from 'next/server';
export async function POST(request: Request): Promise<NextResponse> {
const body = (await request.json()) as HandleUploadBody;
try {
const jsonResponse = await handleUpload({
body,
request,
onBeforeGenerateToken: async (
pathname: string,
/* clientPayload?: string, */
) => {
// Generate a client token for the browser to upload the file
// ⚠️ Authenticate users before generating the token.
// Otherwise, you're allowing anonymous uploads.
const { user } = await auth(request);
const userCanUpload = canUpload(user, pathname);
if (!userCanUpload) {
throw new Error('Not authorized');
}
return {
allowedContentTypes: ['image/jpeg', 'image/png', 'image/gif'],
tokenPayload: JSON.stringify({
// optional, sent to your server on upload completion
userId: user.id,
}),
};
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
// Get notified of client upload completion
// ⚠️ This will not work on `localhost` websites,
// Use ngrok or similar to get the full upload flow
console.log('blob upload completed', blob, tokenPayload);
try {
// Run any logic after the file upload completed
// const { userId } = JSON.parse(tokenPayload);
// await db.update({ avatar: blob.url, userId });
} catch (error) {
throw new Error('Could not update user');
}
},
});
return NextResponse.json(jsonResponse);
} catch (error) {
return NextResponse.json(
{ error: (error as Error).message },
{ status: 400 }, // The webhook will retry 5 times waiting for a 200
);
}
}

When your local website is served on http://localhost:3000, then the onUploadCompleted step won't succeed as Vercel Blob cannot contact your localhost. Instead, we recommend you run your local application through a tunneling service like ngrok , so you can experience the full Vercel Blob development flow locally.

Couldn't find the guide you need?