はじめに
こんにちは!Yuyaです!
コメント機能にファイル添付を実装することになりました。
SlackやNotionのように、画像やPDFをサッと貼り付けられるあの体験です。
いや、シンプルに<input type="file"> を置いて、選択されたファイルをアップロードすればいいんじゃね?って実装前は思いました。
ただ、ここで設計上の問いが生まれました。
「どのファイル形式を許可するか?」
- 全てのファイルを許可する?
- 画像だけに絞る?
- 許可リストを作る?拒否リストを作る?
この記事では、コメント添付機能を実装する中で行ったMIMEタイプの設計判断と、その理由を共有します。
MIMEタイプとの出会い
正直なところ、普段のフロントエンド開発でMIMEタイプを意識する場面は少なかったです。
APIを叩けば application/json が返ってくる。Content-Type: application/json を設定してリクエストを送る。扱うMIMEタイプはほとんど application/json
だけでした。
しかし、ファイル添付機能を作るとなると話が変わります。
- 画像は
image/jpeg?image/png?image/webp? - 動画は
video/mp4?video/quicktime? - ドキュメントは
application/pdf?text/csv?
「JSONの世界」から「バイナリファイルの世界」に踏み出すことで、MIMEタイプと向き合う必要が出てきました。
なぜファイル形式を識別する必要があるのか
ファイル添付機能を作るとき、「ファイルをそのまま保存すればいいのでは?」と思うかもしれません。
しかし、ファイル形式を識別する必要がある場面は多くあります。
1. 表示方法を切り替えるため
画像ならプレビュー表示、PDFならビューワー、動画なら再生プレイヤー。
ファイルの種類によって、UIで表示する方法が変わります。
2. バリデーションのため
許可していない形式(例: 実行ファイル)を弾いたり、ファイルサイズの上限をカテゴリごとに変えたりする必要があります。
3. バックエンド処理のため
画像ならサムネイル生成、動画ならトランスコード。
ファイル形式に応じて、サーバー側で異なる処理を行うことがあります。
MIMEタイプとは
MIMEタイプ(Media Type)は、ファイルの種類を識別するための文字列です。
- image/jpeg → JPEG画像
- image/png → PNG画像
- video/mp4 → MP4動画
- application/pdf → PDFドキュメント
ブラウザでファイルを選択すると、File オブジェクトの .type プロパティからMIMEタイプを取得できます。
const file = event.target.files[0];
console.log(file.type); // "image/jpeg"
MIMEタイプは膨大にある
ここで重要なのは、MIMEタイプの種類は膨大にあるということです。
例えば application/ で始まるものだけでも:
- application/json
- application/xml
- application/zip
- application/pdf
- application/javascript
- application/octet-stream
- application/vnd.ms-excel
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
...
「全てを許可する」という選択は、つまり「何が来るかわからない」ということです。プレビュー表示できるか?安全か?サーバーで処理できるか?全て不明なまま受け入れることになります。
ファイル形式の制限方法
<input type="file"> の accept 属性では、MIMEタイプを使って許可するファイル形式を指定します。
// 画像のみ許可
<input type="file" accept="image/*" />
// 特定の形式のみ許可
<input type="file" accept="image/jpeg, image/png, application/pdf" />
つまり、どのMIMEタイプを許可するかが、そのままユーザーが添付できるファイルの範囲を決めることになります。
なぜ全てのファイルを許可しなかったのか
ファイル添付機能を設計するとき、最初に考えたのは「全て許可するか、制限するか」でした。
結論として、許可リスト方式(ホワイトリスト) を採用しました。
つまり、「許可したものだけ受け入れる」というアプローチです。
その理由を説明します。
1. セキュリティリスク
全てのファイルを許可すると、以下のようなファイルもアップロードできてしまいます。
- 実行ファイル(
.exe,.bat,.sh) - スクリプトファイル(
.js,.py,.php) - アーカイブ内の危険なファイル(
.zipに実行ファイルを含める)
コメント機能という文脈では、これらのファイルを添付する正当な理由はほぼありません。「許可しない」というデフォルトが安全です。
2. ストレージコストと管理
全てのファイルを許可すると、以下のような問題が発生します。
- 巨大なファイル(動画、アーカイブ)が無制限にアップロードされる
- ストレージコストが予測できない
- 不要なファイルの管理コストが増える
カテゴリごとにファイルサイズの上限を設けるためにも、許可するMIMEタイプを明確にする必要がありました。
3. UX観点:プレビューできないファイルは体験が悪い
SlackやNotionの添付ファイル体験を思い出してください。画像はインライン表示、PDFはプレビュー、動画は再生プレイヤー。
一方で、.zip や .dat のような汎用ファイルはどうでしょうか?
アイコンとファイル名が表示されるだけです。
プレビューできないファイルばかりになると、添付機能の価値が下がります。
許可するファイル形式を絞ることで、「添付されたファイルは基本的にプレビューできる」という体験を担保できます。
4. 許可リスト vs 拒否リスト
ファイル制限には2つのアプローチがあります。
| 方式 | 考え方 | メリット | デメリット |
|---|---|---|---|
| 許可リスト | 許可したものだけ受け入れる | 安全、予測可能 | 柔軟性が低い |
| 拒否リスト | 禁止したもの以外は受け入れる | 柔軟性が高い | 漏れがあると危険 |
今回は 許可リスト方式 を採用しました。
理由は単純で、「知らないMIMEタイプは受け入れたくない」からです。
拒否リスト方式だと、新しいMIMEタイプや想定外の形式を見落とすリスクがあります。
許可したMIMEタイプとその理由
最終的に、以下のMIMEタイプを許可しました。
export const ACCEPTED_IMAGE_TYPES = {
"image/jpeg": [".jpg", ".jpeg"],
"image/png": [".png"],
"image/gif": [".gif"],
"image/webp": [".webp"],
};
export const ACCEPTED_VIDEO_TYPES = {
"video/mp4": [".mp4"],
"video/quicktime": [".mov"],
"video/webm": [".webm"],
};
export const ACCEPTED_DOCUMENT_TYPES = {
"application/pdf": [".pdf"],
"text/csv": [".csv"],
};
それぞれの選定理由を説明します。
画像 (image/jpeg, image/png, image/gif, image/webp)
- ユースケース: スクリーンショット共有、UIの説明
コメント機能で最も使われるのは画像です。
「ここがおかしい」とスクリーンショットを貼る場面は非常に多い。
| MIMEタイプ | 選定理由 |
|---|---|
| image/jpeg | 最も普及している画像形式。スクショ、写真に対応 |
| image/png | 透過対応。UI要素のスクショに必須 |
| image/gif | アニメーション対応。操作手順の説明に便利 |
| image/webp | モダンな形式。Chromeスクショのデフォルト |
この4形式で、ブラウザからコピー&ペーストされる画像のほぼ全てをカバーできます。
動画 (video/mp4, video/quicktime, video/webm)
- ユースケース: 画面録画、デモ動画
文章や画像では伝わりにくい「動き」を共有するために動画は必要です。
| MIMEタイプ | 選定理由 |
|---|---|
| video/mp4 | 最も普及している動画形式。ほぼ全ての環境で再生可能 |
| video/quicktime | macOSの画面録画デフォルト形式(.mov) |
| video/webm | Webネイティブな形式。ブラウザ拡張の録画によく使われる |
macユーザーが多い環境では video/quicktime(.mov)の対応は必須でした。
ドキュメント(application/pdf, text/csv)
- ユースケース: 資料共有、データ連携
参考データの共有としてドキュメントも追加する必要がありました
| MIMEタイプ | 選定理由 |
|---|---|
| application/pdf | 資料、仕様書、レポートの共有 |
| text/csv | データのやり取り。Excelからエクスポートしたデータなど |
ドキュメント系は最小限に絞りました。
あえて入れなかったものと理由
許可リストを作る過程で、「入れるか迷ったもの」がいくつかありました。
最終的に入れなかったものと、その理由を共有します。
.heic(image/heic)— MVP はWeb優先
結論: 今回は見送り。将来のモバイル対応時に追加予定。
.heic(HEIF)はiPhoneで撮影した写真のデフォルト形式です。
iPhoneユーザーが写真を添付しようとしたとき、.heicが弾かれるのは体験として良くない。
しかし、今回は見送りました。理由は以下の通りです。
1. ブラウザサポートが限定的
| ブラウザ | .heic 表示 |
|---|---|
| Safari | ✅ 対応 |
| Chrome | ❌ 非対応 |
| Firefox | ❌ 非対応 |
| Edge | ❌ 非対応 |
Safari以外では表示できません。
プレビュー表示を実現するには、サーバー側でJPEGに変換する処理が必要になります。
2. MVP の対象をWebに絞った
今回のMVPでは、デスクトップWebブラウザでの利用を優先しました。
デスクトップでは、スクリーンショットや画面録画が主なユースケースです。
これらは .png や .mp4 で保存されるため、.heic の優先度は低いと判断しました。
3. 将来のモバイル対応で追加予定
モバイルアプリやモバイルWebを本格対応する際には、.heic は必須になります。
その際はサーバー側の変換処理と合わせて実装する予定です。
Excel / Word — プレビュー実装コストが高い
結論: 将来検討。現時点ではPDF変換を推奨。
- application/vnd.ms-excel(.xls)
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(.xlsx)
- application/msword(.doc)
- application/vnd.openxmlformats-officedocument.wordprocessingml.document(.docx)
Excelファイル(.xlsx)やWordファイル(.docx)を添付したい場面はあります。
しかし、これらの問題は プレビューが難しい ことです。
- ブラウザでネイティブ表示できない
- プレビューにはサードパーティライブラリやサーバー処理が必要
- レイアウト崩れなど、完璧な再現が難しい
現時点では「PDFに変換して添付してください」という運用で対応します。
ユーザーからの要望が多ければ、将来的に対応を検討します。
zip / 実行ファイル — セキュリティリスク
結論: zip以外対応予定なし。
- application/zip(.zip)
- application/x-rar-compressed(.rar)
- application/x-msdownload(.exe)
- application/x-sh(.sh)
これらは明確に除外しました。
zip ファイルの問題:
- 中身が見えない(何が入っているかわからない)
- 悪意のあるファイルを隠せる
- プレビューできない
実行ファイルの問題:
- コメント機能で実行ファイルを共有する正当なユースケースがない
- セキュリティリスクしかない
コメント添付という文脈では、これらを許可するメリットがありません。
将来 zip 対応が必要になった場合
以下の対策をセットで検討する必要があります。
- ウイルススキャンの導入
- 中身の検証(許可されたファイル形式のみか)
- ファイル一覧のプレビュー表示
- サイズ制限の厳格化
上記の理由でサーバー側の実装コストが高いため、今回は見送りました
設計と実装
許可するMIMEタイプが決まったら、次は実装です。
ここでは、コードの構造と設計判断を説明します。
カテゴリ別に定数を分けた理由
export const ACCEPTED_IMAGE_TYPES = {
"image/jpeg": [".jpg", ".jpeg"],
"image/png": [".png"],
"image/gif": [".gif"],
"image/webp": [".webp"],
};
export const ACCEPTED_VIDEO_TYPES = {
"video/mp4": [".mp4"],
"video/quicktime": [".mov"],
"video/webm": [".webm"],
};
export const ACCEPTED_DOCUMENT_TYPES = {
"application/pdf": [".pdf"],
"text/csv": [".csv"],
};
export const ACCEPTED_FILE_TYPES = {
...ACCEPTED_IMAGE_TYPES,
...ACCEPTED_VIDEO_TYPES,
...ACCEPTED_DOCUMENT_TYPES,
};
全てを1つのオブジェクトにまとめることもできましたが、カテゴリ別に分けました。
理由:
- カテゴリごとに異なる処理ができる
- 画像だけサムネイル生成
- 動画だけファイルサイズ上限を大きく
- ドキュメントだけ別のプレビューア - 拡張しやすい
- 「画像に.heicを追加」→ ACCEPTED_IMAGE_TYPES だけ変更
- 「Excelを追加」→ ACCEPTED_DOCUMENT_TYPES だけ変更 - UIで使い分けられる
- 「画像だけ選択」というUIを作りたいとき、ACCEPTED_IMAGE_TYPES だけ渡せる
型設計:UI用とAPI用を分けた
ファイルの状態は2種類あります。
- 選択中(未送信) — UI上で一時的に保持
- 送信済み — バックエンドに保存され、URLが発行された
これを2つの型で表現しました。
// UI用:選択中のファイル
type PendingAttachment = {
id: string;
file: File; // 実際のFileオブジェクト
name: string;
size: number;
mimeType: string;
};
// API用:送信済みの添付ファイル
type CommentAttachment = {
id: string;
commentId: string;
fileName: string;
fileType: "image" | "video" | "document";
mimeType: string;
fileSizeBytes: number;
url: string; // ダウンロード/表示用URL
createdAt: string;
};
なぜ分けたか:
- PendingAttachment は File オブジェクトを持つ(ブラウザ内でのみ有効)
- CommentAttachment は url を持つ(サーバーから返される)
- ライフサイクルが異なるものを同じ型で扱うと混乱する
まとめ
コメント添付機能のMIMEタイプ設計を通じて、いくつかの学びがありました。
制限は制約ではなく設計
「全てのファイルを許可する」のは一見ユーザーフレンドリーに見えます。
しかし実際には、セキュリティリスク、プレビューできない体験、予測できないコストを招きます。
許可するものを明確に選ぶことで、安全で一貫した体験を提供できます。
MVP思考:今必要なものに絞る
.heic や Excel 対応など、「あったら便利」な機能は多くあります。
しかし、全てを最初から実装する必要はありません。
今回は以下の判断をしました。
- 対象: デスクトップWebブラウザ
- ユースケース: スクリーンショット、画面録画、PDF共有
- 見送り: .heic(モバイル対応時に追加)、Excel/Word(要望次第)
まずは最小限でリリースし、ユーザーの反応を見てから拡張する方針です。
今後の拡張予定
- .heic 対応(モバイル対応時)
- プレビュー表示の実装
- ファイルサイズ制限のカテゴリ別設定
ファイル添付機能を実装する際、MIMEタイプの設計は避けて通れません。
この記事が、同じような設計判断をする方の参考になれば幸いです。


