
API Routesのレスポンスで画像を返す方法(Next.js)
はじめに
ふだん 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化された文字列に変換するためにArrayBuffer → Buffer → Stringと経由して型を変換する。
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を指定する場合