logo

API Routesのレスポンスで画像を返す方法(Next.js)

投稿日:2025.11.10

はじめに

ふだん APIで画像を返す = Base64エンコード と思い込むぐらい同じ実装をしていましたが、ほかにどんな方法があるか気になったのでまとめてみました。

画像に焦点を当てて解説をしていますが、PDFやExcelなどその他のバイナリファイルも同じ方法で扱えます。

Base64エンコード

API Routesの実装1

Base64エンコードを行い バイナリ → テキストデータ に変換し、Jsonでレスポンスを行う。

app/api/base64/route.ts

export async function GET() {
  // 画像を取得
  const response = await fetch("https://example.com/example.png");
  const arrayBuffer = await response.arrayBuffer();

  // Base64に変換
  const buffer = Buffer.from(arrayBuffer)
  const base64 = buffer.toString("base64");

  // JSONで返す
  return NextResponse.json({ data: base64 });
}

Base64文字列に変換

Base64化された文字列に変換するためにArrayBufferBufferStringと経由して型を変換する。

page.tsxの実装1

app/base64/page.tsx

"use client"

import { useEffect, useState } from "react";

export default function Page() {
  const [dataUrl, setDataUrl] = useState<string>("");

  useEffect(() => {
    async function fetchImage() {
      const response = await fetch("/api/base64");
      const json = await response.json();

      const base64 = json.data;

      setDataUrl("data:image/png;base64," + base64);
    }

    fetchImage();
  }, []);

  return (dataUrl && <img src={dataUrl}/>);
}

Base64のメリット・デメリット

メリット

✅ 文字列で値を扱えるため、Jsonで返すことができる。
✅ 複数の画像を一度で返すことができる。
✅ メタデータと一緒に返しやすい。

デメリット

❌ データサイズが 約33% 増加する。
❌ データを一度メモリにすべて保存するため、メモリ使用量が大きい。


Buffer

API Routesの実装2

バイナリとして扱いやすいBuffer型に変換して、バイナリデータでレスポンスを行う。

export async function GET() {
  // 画像を取得
  const response = await fetch("https://example.com/example.png");
  const arrayBuffer = await response.arrayBuffer();

  // Bufferに変換
  const buffer = Buffer.from(arrayBuffer);

  // バイナリで返す
  return new NextResponse(buffer, {
    headers: {
      "Content-Type": "image/png",
      "Content-Length": buffer.length.toString()
    }
  });
}

バイナリを加工する

ライブラリのSharpなどを使うことで、データを簡単に加工することができます。

// 画像サイズを変更したバイナリを作成
const resized = await sharp(buffer)
  .resize(300, 200)
  .toBuffer();

// 画像をトリミングしたバイナリを作成
const covered = await sharp(buffer)
  .resize(300, 200, { fit: "cover" })
  .toBuffer();

page.tsxの実装2

バイナリデータをblobで取得し、ObjectUrlを生成してimgタグに埋め込んでいます。

"use client"

import { useEffect, useState } from "react";

export default function Page() {
  const [objectUrl, setObjectUrl] = useState<string>("");

  useEffect(() => {
    // クリーンアップようにObjectURLを保持する変数
    let url: string | null = null;

    async function fetchImage() {
      // 画像をバイナリで取得
      const response = await fetch("/api/buffer");
      const blob = await response.blob();

      // ObjectURLを生成
      url = URL.createObjectURL(blob);

      setObjectUrl(url);
    }

    fetchImage();

    // ObjectURLをクリーンアップ
    return () => {
      if (url) {
        URL.revokeObjectURL(url);
      }
    };
  }, []);

  return (objectUrl && <img src={objectUrl}/>);
}

imgタグに直接APIのエンドポイントを記載

実装したAPIのエンドポイントをimgタグのsrcに書くことで、画像を表示することができます。

<img src="/api/buffer"/>;

Bufferのメリット・デメリット

メリット

✅ データのオーバーヘッドが小さく、転送効率が良い。
✅ Node.jsの主要なライブラリとの相性が良い。(Sharp, fs...)
imgタグのsrcに直接URLを記載することができる。

デメリット

❌ データを一度メモリにすべて保存するため、メモリ使用量が大きい。
❌ データを加工しない場合は、不要な型の変換の処理を書く必要がある。


使い分け

Base64を使うべきケース

  • レスポンスをJsonで受け取りたい場合
  • 一度のレスポンスで複数の画像データを返したい場合
    {
      "images": [
        "image1": "base64...",
        "image2": "base64..."
      ]
    }
    
  • 画像とメタデータを一緒に返したい場合
    {
      "id": 1,
      "title": "画像タイトル",
      "createdAt": "2025-01-01",
      "image": "base64..."
    }
    

Bufferを使うべきケース

  • 単一の画像のみを返す場合
  • 画像のサイズが大きい場合
    Base64でエンコードした場合、データサイズが大きくなるため
  • 画像の加工処理を行う場合
  • imgタグに直接URLを指定する場合