next.js icon indicating copy to clipboard operation
next.js copied to clipboard

formData with app router: Upload error: TypeError: Failed to parse body as FormData.

Open 539hex opened this issue 1 year ago • 18 comments

Link to the code that reproduces this issue

https://codesandbox.io/p/sandbox/twilight-flower-v6pl9s

To Reproduce

i am uploading a file using axios in a client side component in this way:

        const options: AxiosRequestConfig = {
        headers: { 'Content-Type': 'multipart/form-data' }
      };

      const { data } = await axios.post<{ success: boolean; }>(
        '/api/upload',
        formData,
        options
      );

in the /api/upload route i have the following code:

export async function POST(request: NextRequest) {
  try {
    console.log('in post');
    let formData = await request.formData();
    console.log('formData', formData);
    const file = formData.get('file') as File;
  } catch (error) {
    console.error('Upload error:', error);
    return NextResponse.json(
      { error: 'Failed to upload file' },
      { status: 500 }
    );
  }
}

if the file is <~2GB everythings is working, the formData is returned with the correct values, otherwise if the file is bigger i get the following output:

Upload error: TypeError: Failed to parse body as FormData.
    at node:internal/deps/undici/undici:5668:27
    at successSteps (node:internal/deps/undici/undici:5712:27)
    at fullyReadBody (node:internal/deps/undici/undici:4609:9)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async consumeBody (node:internal/deps/undici/undici:5721:7)
    at async POST (/Users/539hex/dev/deft.cx/.next/server/chunks/_bc0f62._.js:282:24)
    at async /Users/539hex/dev/deft.cx/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:55831
    at async eO.execute (/Users/539hex/dev/deft.cx/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:46527)
    at async eO.handle (/Users/539hex/dev/deft.cx/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:57165)
    at async doRender (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/base-server.js:1353:42)
    at async cacheEntry.responseCache.get.routeKind (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/base-server.js:1575:28)
    at async DevServer.renderToResponseWithComponentsImpl (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/base-server.js:1483:28)
    at async DevServer.renderPageComponent (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/base-server.js:1911:24)
    at async DevServer.renderToResponseImpl (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/base-server.js:1949:32)
    at async DevServer.pipeImpl (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/base-server.js:921:25)
    at async NextNodeServer.handleCatchallRenderRequest (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/next-server.js:272:17)
    at async DevServer.handleRequestImpl (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/base-server.js:817:17)
    at async /Users/539hex/dev/deft.cx/node_modules/next/dist/server/dev/next-dev-server.js:339:20
    at async Span.traceAsyncFn (/Users/539hex/dev/deft.cx/node_modules/next/dist/trace/trace.js:154:20)
    at async DevServer.handleRequest (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/dev/next-dev-server.js:336:24)
    at async invokeRender (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/lib/router-server.js:173:21)
    at async handleRequest (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/lib/router-server.js:350:24)
    at async requestHandlerImpl (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/lib/router-server.js:374:13)
    at async Server.requestListener (/Users/539hex/dev/deft.cx/node_modules/next/dist/server/lib/start-server.js:141:13)
 POST /api/upload 500 in 9203ms

Current vs. Expected behavior

I expect always to have the formData returned despite the file size

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.1.0: Thu Oct 10 21:03:11 PDT 2024; root:xnu-11215.41.3~2/RELEASE_ARM64_T6020
  Available memory (MB): 32768
  Available CPU cores: 12
Binaries:
  Node: 23.3.0
  npm: 10.9.0
  Yarn: 1.22.22
  pnpm: 9.12.1
Relevant Packages:
  next: 14.2.18 // An outdated version detected (latest is 15.0.3), upgrade is highly recommended!
  eslint-config-next: N/A
  react: 18.3.1
  react-dom: 18.3.1
  typescript: 5.6.2
Next.js Config:
  output: N/A
 ⚠ An outdated version detected (latest is 15.0.3), upgrade is highly recommended!
   Please try the latest canary version (`npm install next@canary`) to confirm the issue still exists before creating a new issue.
   Read more - https://nextjs.org/docs/messages/opening-an-issue

Which area(s) are affected? (Select all that apply)

Not sure

Which stage(s) are affected? (Select all that apply)

next dev (local)

Additional context

i tried also to update to next 15.0.3 and i have the same exact issue.

539hex avatar Nov 26 '24 17:11 539hex

Possibly related? https://github.com/nodejs/undici/issues/3676

icyJoseph avatar Nov 26 '24 21:11 icyJoseph

Yes it might be related

539hex avatar Nov 26 '24 21:11 539hex

Are you able to downgrade Node.js to the 18-20 range, and test?

icyJoseph avatar Nov 26 '24 22:11 icyJoseph

@icyJoseph i just tested with Node 18.20.5 and things are getting even stranger.

If i upload a file <2GB everythings is working as before, if the file is >2GB for example 2.65GB i am not getting any exception but the process will stuck right after the let formData = await request.formData(); and will hang forever without any message. If the file is even bigger (4.32GB) i get the following output

in post
Upload error: RangeError: offset is out of bounds
    at Buffer.set (<anonymous>)
    at BufferList.concat (node:internal/streams/buffer_list:72:7)
    at fromList (node:internal/streams/readable:1373:26)
    at Readable.read (node:internal/streams/readable:553:11)
    at createAsyncIterator (node:internal/streams/readable:1163:54)
    at createAsyncIterator.next (<anonymous>)
    at Object.pull (node:internal/deps/undici/undici:792:52)
    at ensureIsPromise (node:internal/webstreams/util:192:19)
    at readableStreamDefaultControllerCallPullIfNeeded (node:internal/webstreams/readablestream:2250:5)
    at node:internal/webstreams/readablestream:2341:7
 POST /api/upload 500 in 721ms

539hex avatar Nov 27 '24 08:11 539hex

Before I dive into this further, does it happen with simple fetch?

I suspect there's some Buffer allocation going on, https://stackoverflow.com/a/8974841, but it is just an early guess...

icyJoseph avatar Nov 27 '24 12:11 icyJoseph

Same issue using native fetch.

Looks like the new node release ( 23.3.0 ) increased the buffer to 8192 TB, the 18.20.5 got it capped at 4 GB.

Still the issue is present in both versions.

➜  ~ node
Welcome to Node.js v23.3.0.
Type ".help" for more information.
> (require('buffer').constants.MAX_LENGTH + 1) / 2**30
8388608
>


➜  ~ node
Welcome to Node.js v18.20.5.
Type ".help" for more information.
> (require('buffer').constants.MAX_LENGTH + 1) / 2**30
4.000000000931323
>

539hex avatar Nov 27 '24 16:11 539hex

Still the same issue with the latest node 23.4.0

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

539hex avatar Dec 18 '24 11:12 539hex

I am experiencing the same issue with our React Native project. I have already tried running it with a normal Bun server, and React Native is working fine. Additionally, I have tested with different Node versions (v20.13.1 and v22.2.0), but the issue persists.

Interestingly, the functionality works fine when tested with Insomnia. Are there any logs or debugging steps I can help with? @icyJoseph

Mohamed-kassim avatar Dec 23 '24 15:12 Mohamed-kassim

Update as of Node 24.0.2 - still the same issue

Unhandled error in POST /upload: [TypeError: Failed to parse body as FormData.] { [cause]: [TypeError: expected CRLF] }

some useful refs https://philna.sh/blog/2025/01/14/troubles-with-multipart-form-data-fetch-node-js/

@icyJoseph

539hex avatar May 15 '25 19:05 539hex

Thanks for the link @539hex , informative

I was seeing this issue on node 22, then upgraded to node v24.4.1, and got this stack trace:

TypeError: Failed to parse body as FormData.
    at parsingError (node:internal/deps/undici/undici:5914:14)
    at multipartFormDataParser (node:internal/deps/undici/undici:5688:15)
    ... 3 lines matching cause stack trace ...
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
  [cause]: TypeError: missing boundary in content-type header

My particular app is a sveltekit app. However, sending multipart/form-data via an html form element like: <form enctype="multipart/form-data"> worked. It was only once I switched to submitting the data via a fetch call that I started receiving "Failed to parse body as FormData" errors...

FlexWilliams avatar Jul 23 '25 00:07 FlexWilliams

I'm facing a similar issue here. If someone makes a POST request to my app with Content-Type: multipart/form-data and a request body containing quotes, e.g.:

"formdata":"d"

I get a 500 back from next and the following in the logs:

TypeError: Failed to parse body as FormData.
    at parsingError (node:internal/deps/undici/undici:6114:14)
    at parseMultipartFormDataHeaders (node:internal/deps/undici/undici:6058:17)
    ... 4 lines matching cause stack trace ...
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5) {
  page: '/encode_image',
  [cause]: TypeError: expected CRLF

I never actually hit any handlers / middleware, and the path being requested does not exist.

If I try this with node 18, I get a 200, but on the latest node 22, 24, 25 I get this error.

MDUK0001 avatar Nov 12 '25 12:11 MDUK0001

Well looks like the upstream fix https://github.com/nodejs/undici/issues/3676 didn't do the trick right? 🤔

icyJoseph avatar Nov 12 '25 13:11 icyJoseph

@icyJoseph Correct. I have added logging to confirm the undici version (using process.versions) and I was on 7.16.0 which should contain that fix.

I've had a look at the relevant undici code and I'm pretty sure it's throwing here. From a next.js side, are we catching that error? If the caller sends us an invalid body, it seems we should catch a failed parse and return a 4xx.

MDUK0001 avatar Nov 12 '25 13:11 MDUK0001

Hi @MDUK0001. This issue started to appear for us when bumping to 15.4.8. I can consistently reproduce the issue with 15.4.8 and consistently not with 15.4.7. Which is problematic since 15.4.8 fixes a critical CVE.

Edit: 15.5.7 and 16.0.7 have the issue as well

dispix avatar Dec 04 '25 16:12 dispix

What issue exactly? @dispix - I have been able to repro with 16.0.6 and 16.0.7, that uploading to a Route Handler, 2gb+ fails. It is a Node.js issue, or at least that's what the evidence is suggesting so far? Could you explain a bit more of your setup.

icyJoseph avatar Dec 04 '25 19:12 icyJoseph

Our issues started yesterday, after upgrading from [email protected] to [email protected]. Uploading a file even 10Mb fails. I'm not sure this is size related. Here's the error:

error: {
  "type": "TypeError",
  "message": "Failed to parse body as FormData.: expected boundary after body",
  "stack":
      TypeError: Failed to parse body as FormData.
          at parsingError (node:internal/deps/undici/undici:6114:14)
          at multipartFormDataParser (node:internal/deps/undici/undici:5923:19)
          at node:internal/deps/undici/undici:6364:34
          at successSteps (node:internal/deps/undici/undici:6414:27)
          at readAllBytes (node:internal/deps/undici/undici:5380:13)
          at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
      caused by: TypeError: expected boundary after body
          at parsingError (node:internal/deps/undici/undici:6114:74)
          at multipartFormDataParser (node:internal/deps/undici/undici:5923:19)
          at node:internal/deps/undici/undici:6364:34
          at successSteps (node:internal/deps/undici/undici:6414:27)
          at readAllBytes (node:internal/deps/undici/undici:5380:13)
          at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
}
POST /api/library/upload 500 in 71ms (compile: 3ms, proxy.ts: 54ms, render: 14ms)

We can see it's indeed coming from undici, however using [email protected] doesn't solve the issue. Here's what process.versions shows:

{
  node: '24.11.0',
  acorn: '8.15.0',
  ada: '3.3.0',
  amaro: '1.1.4',
  ares: '1.34.5',
  brotli: '1.1.0',
  cjs_module_lexer: '2.1.0',
  cldr: '47.0',
  icu: '77.1',
  llhttp: '9.3.0',
  modules: '137',
  napi: '10',
  nbytes: '0.1.1',
  ncrypto: '0.0.1',
  nghttp2: '1.66.0',
  openssl: '3.5.4',
  simdjson: '3.13.0',
  simdutf: '6.4.0',
  sqlite: '3.50.4',
  tz: '2025b',
  undici: '7.16.0',
  unicode: '16.0',
  uv: '1.51.0',
  uvwasi: '0.0.23',
  v8: '13.6.233.10-node.28',
  zlib: '1.3.1-470d3a2',
  zstd: '1.5.7'
}

dispix avatar Dec 04 '25 20:12 dispix

That often happens when you set the content type manually. fetch/axios recognize the body type and generate the content type header with the boundary and all (of course, this is likely not your issue, but let's start there) - any chance you could make a quick create-next-app showing the problem?

icyJoseph avatar Dec 04 '25 20:12 icyJoseph

@icyJoseph I've managed to reproduce my issue with an unmodified create-next-app and node 24.8.0. Steps:

  • npx [email protected] formdata-repro
  • cd formdata-repo
  • npm run dev
  • curl --request POST \ --url http://localhost:3000/ \ --header 'Content-Type: multipart/form-data' \ --data-raw '"formdata":"d"'

Output (from next server logs):

 ⨯ TypeError: Failed to parse body as FormData.
    at ignore-listed frames {
  [cause]: TypeError: missing boundary in content-type header
      at ignore-listed frames
}
 ⨯ TypeError: Failed to parse body as FormData.
    at ignore-listed frames {
  page: '/',
  [cause]: TypeError: missing boundary in content-type header
      at ignore-listed frames
}
 POST / 500 in 25ms (compile: 17ms, render: 7ms)

I'd expect to be able to handle this error, or have next return a 400. Adding an error.tsx or global-error.tsx doesn't change the behaviour.

MDUK0001 avatar Dec 04 '25 21:12 MDUK0001