ハマったので調べました。
Fetch APIの仕様上、ファイルアップロードをする際はContent-Typeヘッダに multipart/form-data
を手動で指定する必要はない。
multipart/form-data
は、Requestデータが通常のフォーマットのままではContent-{Type,Disposition}とbodyを一つずつしか持てない所を、bodyをいい感じに分割することで複数のsubpart = Content-{Type,Disposition}+bodyを持てるようにしている。
例えばPNGファイルとCSVファイルを同時に単一リクエストでアップロードしたりすることもこれで実現されている。
その実現のために使われるbodyの境界線=boundaryは、RequestのContent-Typeのパラメータで指定される。つまり、RequestのContent-Typeは
multipart/form-data; boundary=THISISABOUNDARYLINE
といった文字列になる。
Fetch APIは、与えられたbodyがFormDataの場合、multipart/form-data encoding algorithmを使ってエンコーディングを行ったあと、Content-Typeを設定する仕様になっている。
参考: Fetch Standard | Body mixin
自分でContent-Typeを指定した場合、上記に上書きされるか指定した方が使われるのかについては仕様は見つからなかったので未定義と思われますが、少なくともChromeだと指定した方が使われるため失敗します(サーバがRailsだと400になるっぽい)。とりあえず指定しなくても良い様なので指定しないようにしましょう。