でじぼうです。
今回は Azure Blob Storage
にファイルをアップロードする機能を Next.js(App Router)
× React
で実装した方法をご紹介します。

でじぼう
この記事は下記の方がおすすめ!
- Azure側の設定がわからない
- APIはどのように記載するの?
- 実装方法が知りたい
Instagramフォロワー数9,500人を越える人気のもち・大福店 えにかいたもち
全体構成

Azure 側の準備
ファイルを保存する先として Azure Blob Storage
を使うために、まずはAzure側での初期設定を行いましょう。
STEP1:ストレージアカウントを作成
- Azureポータル にログイン
- 左上の「≡」メニューから「ストレージアカウント」を選択し、「+作成」をクリック
- 以下の※1ように項目を入力(最低限でOK)
- 下部の「確認および作成」→「作成」を押す
- デプロイ完了後、「リソースに移動」ボタンをクリック
※1
項目 | 設定値 |
---|---|
サブスクリプション | 任意(通常は1つ) |
リソースグループ | 任意(新規作成でもOK) |
ストレージアカウント名 | 例:myuploadstorage123(一意な名前) |
リージョン | お住まいに近い場所(東日本ならJapan East) |
パフォーマンス | standard |
冗長性 | ローカル冗長ストレージ (LRS) |
STEP2:Blobコンテナーを作成
- ストレージアカウントの画面左メニューから「データストレージ」→「コンテナー」を選択
- 上部の「+コンテナー」ボタンを押して以下の※2ように項目を入力
- 「作成」ボタンを押す
※2
項目 | 設定値 |
---|---|
名前 | 例:upload |
パブリックアクセスレベル | プライベート(推奨) |
STEP3:接続文字列を取得して .env.local に設定
- ストレージアカウントの左メニューをスクロールし、「セキュリティとネットワーク」セクションにある「アクセスキー」をクリック
- 「key1」の項目にある「接続文字列」をコピー
- プロジェクト直下の
.env.local
ファイルに以下を追加
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=(コピーした内容)
AZURE_CONTAINER_NAME=(設定した名前)
.env.local
を変更したあとは、Next.js の開発サーバーを再起動してください。
ファイルアップロード用UIの作成(React)
"use client";
import { useState, useRef } from "react";
export default function DragAndDropUploader() {
// ドラッグ中かどうかの状態
const [isDragging, setIsDragging] = useState(false);
// メッセージ表示用(成功・エラー)
const [message, setMessage] = useState("");
// input要素の参照(隠してボタンから開く用)
const fileInputRef = useRef<HTMLInputElement>(null);
// 選択されたファイル一覧
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
// ファイルがドロップされた時の処理
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);
const files = Array.from(e.dataTransfer.files); // FileList → 配列に変換
if (files.length === 0) return;
setSelectedFiles(files);
};
// ドラッグ中(枠が赤くなる)
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
};
// ドラッグが外れたとき(赤枠を戻す)
const handleDragLeave = () => setIsDragging(false);
// ファイル選択時の処理
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
if (files.length === 0) return;
setSelectedFiles(files);
};
// 一括取消(ファイルクリア)
const handleClearFiles = () => {
setSelectedFiles([]);
setMessage("");
};
// ボタンクリック → input をクリックさせる
const handleButtonClick = () => {
fileInputRef.current?.click();
};
// アップロード処理
const handleUpload = async () => {
if (selectedFiles.length === 0) {
setMessage("❌ ファイルが選択されていません");
return;
}
const formData = new FormData();
selectedFiles.forEach((file) => formData.append("files", file));
try {
// APIにPOST
const res = await fetch("/api/upload", {
method: "POST",
body: formData,
});
if (res.ok) {
setMessage("✅ アップロード成功しました");
} else {
const data = await res.json();
setMessage("❌ アップロード失敗: " + (data.error || res.statusText));
}
} catch {
setMessage("❌ アップロード中にエラーが発生しました");
}
};
return (
<div className="m-5 mx-auto w-fit">
<h2>📥 スキルシートをアップロード</h2>
{/* ドラッグ&ドロップ枠 */}
<div
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
className={`w-[620px] h-[320px] border-2 border-dashed rounded p-5 text-center transition-colors duration-300 ${
isDragging ? "border-red-500" : "border-gray-300"
} bg-gray-100 flex items-center justify-center`}
>
<div>
<p>{isDragging ? "ここにドロップ" : "ファイルをドラッグ&ドロップ"}</p>
<p>または</p>
<button
onClick={handleButtonClick}
className="bg-red-500 text-white px-4 py-2 rounded"
>
ファイルを選択
</button>
</div>
{/* ファイル選択用input(非表示) */}
<input
type="file"
multiple
ref={fileInputRef}
onChange={handleFileSelect}
style={{ display: "none" }}
/>
</div>
{/* 選択されたファイルの表示 + ✕ボタン */}
{selectedFiles.length > 0 && (
<div className="mt-4 text-left">
<div className="flex items-start justify-between mb-2">
<p className="font-semibold">選択中のファイル</p>
<button
onClick={handleClearFiles}
className="font-semibold text-sm text-red-600 hover:underline"
>
一括取消
</button>
</div>
<ul>
{selectedFiles.map((file, index) => (
<li key={index} className="flex items-center justify-between mb-1">
<span>{file.name}</span>
<button
onClick={() =>
setSelectedFiles((prev) =>
prev.filter((_, i) => i !== index)
)
}
className="text-red-600 font-bold"
>
✕
</button>
</li>
))}
</ul>
</div>
)}
{/* アップロードボタン */}
<div>
<button
onClick={handleUpload}
className="bg-green-700 text-white px-4 py-2 rounded mt-5"
>
アップロード
</button>
{message && <p className="mt-2">{message}</p>}
</div>
</div>
);
}
ファイルアップロードAPIの作成(Next.js × Azure SDK)
仕様
ファイル名が一意になるように、アップロードされたファイル名の末尾に
「_YYYYMMDD_hhmm」と日時を付与してからAzure Blob Storage
にアップロードする
import { NextResponse } from "next/server";
import { BlobServiceClient } from "@azure/storage-blob";
// POSTメソッドで呼び出されたときの処理
export async function POST(req: Request) {
try {
// フロントから送られてきたファイル付きのデータ(FormData)を受け取る
const formData = await req.formData();
// name="files" という名前で送られてきたファイル全部取得
const files = formData.getAll("files") as File[];
// ファイルが1つもなかったらエラーを返す
if (!files.length) {
return NextResponse.json(
{ error: "ファイルがありません" },
{ status: 400 }
);
}
// Azure Storage に接続するためのクライアントを作成
const blobService = BlobServiceClient.fromConnectionString(
process.env.AZURE_STORAGE_CONNECTION_STRING! // .env.local に設定した接続文字列
);
// コンテナー(事前に作成済み)に接続
const container = blobService.getContainerClient(
process.env.AZURE_CONTAINER_NAME!
);
// ファイルごとに繰り返す
for (const file of files) {
// タイムスタンプを付けて、ファイル名の重複を防ぐ
const now = new Date();
const timestamp = `${now.getFullYear()}${String(
now.getMonth() + 1
).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}_${String(
now.getHours()
).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
// 元のファイル名と拡張子を分離
const originalName = file.name;
const ext = originalName.includes(".")
? "." + originalName.split(".").pop()
: "";
const base = originalName.replace(ext, "");
// 一意なファイル名を作成(例:resume_20250626_1030.pdf)
const uniqueName = `${base}_${timestamp}${ext}`;
// Fileオブジェクト → ArrayBuffer に変換
const arrayBuffer = await file.arrayBuffer();
// ArrayBuffer → Node.js の Buffer に変換(Azure用)
const buffer = Buffer.from(arrayBuffer);
// Azure上の「保存先ファイル(Blob)」を指定
const blockBlob = container.getBlockBlobClient(uniqueName);
// ファイルをアップロード(bufferとして保存)
await blockBlob.uploadData(buffer);
}
// 成功レスポンスを返す
return NextResponse.json({
message: "すべてのファイルをアップロードしました",
});
} catch (error) {
// エラーが起きた場合はログを出してエラーを返す
console.error("アップロードエラー:", error);
return NextResponse.json({ error: "アップロード失敗" }, { status: 500 });
}
}
実行画面
Azure Blob Storage
にまだファイルがアップロードされていない状態

アップロード画面

ファイルをアップロードすると、選択中のファイルが表示される

アップロードボタンを押下すると、アップロード完了

Azure Blob Storage
にファイルが格納されていることを確認

コメント