diff --git a/errors/api-routes-body-size-limit.md b/errors/api-routes-body-size-limit.md new file mode 100644 index 0000000000000..10636b93cf281 --- /dev/null +++ b/errors/api-routes-body-size-limit.md @@ -0,0 +1,13 @@ +# API Routes Body Size Limited to 5mb + +#### Why This Error Occurred + +API Routes are meant to respond quickly and are not intended to support responding with large amounts of data. The maximum size of responses is 5 MB. + +#### Possible Ways to Fix It + +Limit your API Route responses to less than 5 MB. If you need to support sending large files to the client, you should consider using a dedicated media host for those assets. See link below for suggestions. + +### Useful Links + +[Tips to avoid the 5 MB limit](https://vercel.com/support/articles/how-to-bypass-vercel-5mb-body-size-limit-serverless-functions) diff --git a/errors/manifest.json b/errors/manifest.json index c18353284f39e..17059bb427d90 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -13,6 +13,10 @@ "title": "amp-export-validation", "path": "/errors/amp-export-validation.md" }, + { + "title": "api-routes-body-size-limit", + "path": "/errors/api-routes-body-size-limit.md" + }, { "title": "api-routes-static-export", "path": "/errors/api-routes-static-export.md" diff --git a/packages/next/server/api-utils.ts b/packages/next/server/api-utils.ts index 91640e82b7b37..c64644080ade4 100644 --- a/packages/next/server/api-utils.ts +++ b/packages/next/server/api-utils.ts @@ -63,6 +63,26 @@ export async function apiResolver( ) } + let contentLength = 0 + const writeData = apiRes.write + const endResponse = apiRes.end + apiRes.write = (...args: any[2]) => { + contentLength += Buffer.byteLength(args[0]) + return writeData.apply(apiRes, args) + } + apiRes.end = (...args: any[2]) => { + if (args.length && typeof args[0] !== 'function') { + contentLength += Buffer.byteLength(args[0]) + } + + if (contentLength >= 5 * 1024 * 1024) { + console.warn( + `API response for ${req.url} exceeds 5MB. This will cause the request to fail in a future version. https://nextjs.org/docs/messages/api-routes-body-size-limit` + ) + } + + endResponse.apply(apiRes, args) + } apiRes.status = (statusCode) => sendStatusCode(apiRes, statusCode) apiRes.send = (data) => sendData(apiReq, apiRes, data) apiRes.json = (data) => sendJson(apiRes, data) diff --git a/test/integration/api-support/pages/api/large-chunked-response.js b/test/integration/api-support/pages/api/large-chunked-response.js new file mode 100644 index 0000000000000..4907877101fe7 --- /dev/null +++ b/test/integration/api-support/pages/api/large-chunked-response.js @@ -0,0 +1,6 @@ +export default (req, res) => { + for (let i = 0; i <= 5 * 1024 * 1024; i++) { + res.write('.') + } + res.end() +} diff --git a/test/integration/api-support/pages/api/large-response.js b/test/integration/api-support/pages/api/large-response.js new file mode 100644 index 0000000000000..efe57ccdca2d0 --- /dev/null +++ b/test/integration/api-support/pages/api/large-response.js @@ -0,0 +1,4 @@ +export default (req, res) => { + let body = '.'.repeat(5 * 1024 * 1024) + res.send(body) +} diff --git a/test/integration/api-support/test/index.test.js b/test/integration/api-support/test/index.test.js index 1b7972ff9c875..62a559fffce71 100644 --- a/test/integration/api-support/test/index.test.js +++ b/test/integration/api-support/test/index.test.js @@ -398,6 +398,20 @@ function runTests(dev = false) { expect(data).toBe('hi') }) + it('should warn if response body is larger than 5MB', async () => { + let res = await fetchViaHTTP(appPort, '/api/large-response') + expect(res.ok).toBeTruthy() + expect(stderr).toContain( + 'API response for /api/large-response exceeds 5MB. This will cause the request to fail in a future version.' + ) + + res = await fetchViaHTTP(appPort, '/api/large-chunked-response') + expect(res.ok).toBeTruthy() + expect(stderr).toContain( + 'API response for /api/large-chunked-response exceeds 5MB. This will cause the request to fail in a future version.' + ) + }) + if (dev) { it('should compile only server code in development', async () => { await fetchViaHTTP(appPort, '/api/users')