0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【完全未経験】Geminiを使った個人開発完遂、Vercelの「413 エラー」をクライアント側圧縮で突破した話

Last updated at Posted at 2025-11-29
Page 1 of 2

はじめに

普段は普通のサラリーマンをしており、開発経験はゼロ(HTMLがなんとなく分かるレベル)です。
そんな私が、生成AI(Gemini)の力を借りて、「写真から深層心理を分析して穴場旅行先を提案するアプリ」を個人開発しました。

作ったアプリ:スキバレ
ogp.png
https://suki-bare.vercel.app
(写真を3枚選ぶと、AIがあなたの深層心理を暴きます。よかったら遊んでみてください!)

直面した壁:スマホの写真が送れない

ローカル環境(自分のPC)では完璧に動いていた診断機能が、本番環境(Vercel)に上げたとたん動きませんでした。
VercelのLogで確認したところ「413 error」がでており、AIにエラーログを読ませると、「VercelのHobbyプランは、一度のリクエストで4.5MBまでしかデータを受け取れない」とのこと。 最近のスマホの写真は1枚で3MB〜5MBあるので、3枚送れば合計10MB超え。完全にオーバーです。

解決策:サーバーに送る前に「圧縮」する

「プランをアップグレードするしかないのか…?」と思いましたが、AIが出した解決策は「ブラウザ(クライアント)側で画像を小さく圧縮してから、APIに送信しよう」というものでした。

確かに、今回のアプリは「写真の雰囲気」さえAIに伝わればいいので、4Kのような高画質である必要はありません。

実装したコード
AIに書いてもらった「画像圧縮用」の関数がこちらです。 Canvasを使って画像を読み込み、長辺を800pxにリサイズし、JPEG品質0.7で圧縮しています。

TypeScript

// 画像を圧縮する関数
const compressImage = (file: File): Promise<string> => {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = (event) => {
      const img = document.createElement("img");
      img.src = event.target?.result as string;
      img.onload = () => {
        const canvas = document.createElement("canvas");
        const MAX_WIDTH = 800; // 800pxあればAIの認識には十分
        const scaleSize = MAX_WIDTH / img.width;
        
        // 幅が800pxより大きければ縮小、小さければそのまま
        if (img.width > MAX_WIDTH) {
            canvas.width = MAX_WIDTH;
            canvas.height = img.height * scaleSize;
        } else {
            canvas.width = img.width;
            canvas.height = img.height;
        }

        const ctx = canvas.getContext("2d");
        ctx?.drawImage(img, 0, 0, canvas.width, canvas.height);

        // JPEG形式 品質0.7で圧縮してDataURLとして返す
        const dataUrl = canvas.toDataURL("image/jpeg", 0.7);
        resolve(dataUrl);
      };
    };
  });
};

これを、画像をアップロードする処理(handleImageUpload)の中で呼び出すようにしました。

TypeScript

// 変更前:ファイルをそのままStateに入れていた
// setImages((prev) => [...prev, ...files]);

// 変更後:圧縮してからStateに入れる
const compressedImages = await Promise.all(
  fileArray.map((file) => compressImage(file))
);
setImages((prev) => [...prev, ...compressedImages]);

結果どうなったか

この処理を入れたことで、合計15MB近くあったリクエストが、なんと数百KBまで軽量化されました。 もちろんVercel上でもサクサク動き、エラーも完全に消えました。

副次的なメリット:
→通信が速くなり、ユーザーの待ち時間が減った。
→トークン消費減による、OpenAIのコスト削減

さいごに

知識ゼロからスタートしましたが、エラーが出るたびにAIにログを貼り付け、返ってきたコードを試す…という繰り返しで、なんとかリリースまで辿り着けました。同じようなサービス仕様かつ「413エラー」で困っている方がいたら、ぜひ「クライアント側での圧縮」を試してみてください。

今回作ったアプリ:スキバレ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?