Vercel Blobのファイルストレージを使って画像をNext.jsで操作する
Vercel Blobのファイルストレージを使って画像をNext.jsで操作する方法を公式から調べ詳細をまとめました。
Vercel Blobとは
Vercel Blob は、画像、動画、オーディオファイルなど、静的アセットを保存するための、スケーラブルでコスト効率的なオブジェクトストレージサービスです。
現時点では、Hobby プランと Pro プランでベータ版として提供されています。
公式のドキュメントはこちら
Vercel Blobの特徴
ファイルを Vercel Blob にアップロードするには、2つの方法があります。
Server uploads: サーバ側でアップロードの処理をします。4.5 MB以上はアップロードできません。
Client uploads: クライアント (ブラウザなど) から Vercel Blob に安全に直接送信します。最大 5 TB (5,000 GB) のファイルをアップロードできます。
Vercel Blobのセットアップ
ますはパッケージをインストールします。
npm i @vercel/blob
次にVercelでプロジェクトを作成しておきましょう。
OverviewタブからAdd New...でProjectで作成します。Githubなどと連携しておきます。すでに連携済のプロジェクトがある場合はスキップで大丈夫です。
1. Blob ストアを作成する
Vercel上で、BLOB ストアを追加するプロジェクトに移動します。
「ストレージ」タブを選択し、「データベースに接続」ボタンを選択します。
[新規作成]タブで、[Blob]を選択し、[続行]ボタンを選択します。
「Images」という名前を使用し、 [新しい Blob ストアの作成]を選択します。
読み取り/書き込みトークンを含める環境を選択します。
詳細オプションで環境変数のプレフィックスを更新することもできます
作成すると、Vercel Blob ストア ページが表示されます。
先ほど作成したProjectとConnectしておきましょう
2. ローカル環境のセットアップする
プロジェクトで BLOB ストアを作成後、BLOB_READ_WRITE_TOKEN
という環境変数が自動的に作成され、プロジェクトに追加されました。
この環境変数をローカルで使用するには、Vercel CLI を使用して環境変数を取得することをお勧めします。
まずvercel
コマンドが利用できるようにします。
npm i -g vercel
Vercelのprojectとリンクさせます。
vercel link
環境変数を引っ張ってきます。
vercel env pull .env.development.local
ローカルでも使用するので、.env.localにBLOB_READ_WRITE_TOKEN
をコピーしておきます
3. Next.configの設定
next/image
で最適化された Vercel Blob ストアの画像を表示するために使用できるようしておきます。
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'my-blob-store.public.blob.vercel-storage.com',
port: '',
},
],
},
};
module.exports = nextConfig;
サーバー側でのアップロードと表示
サーバーアクションでアップロード
Next.js App Router でサーバー アクションを使用してファイルを Vercel Blob にアップロードするフォームのコンポーネントを作成します。
// app/components/form.tsx
import { put } from '@vercel/blob';
import { revalidatePath } from 'next/cache';
export async function Form() {
async function uploadImage(formData: FormData) {
'use server'; // サーバーで処理
// 画像ファイルを取得
const imageFile = formData.get('image') as File;
// Vercel Blobにアップロード
const blob = await put(imageFile.name, imageFile, {
access: 'public',
});
// topページのキャッシュ削除(ISRリセット)
revalidatePath('/');
return blob;
}
return (
<form action={uploadImage}>
<label htmlFor="image">Image</label>
<input type="file" id="image" name="image" required />
<button>Upload</button>
</form>
);
}
アップロードした画像を一覧で表示するコンポーネントを作成します。
// app/components/images.tsx
import { list } from '@vercel/blob';
import Image from 'next/image';
export async function Images() {
async function allImages() {
// Vercel Blobにアップロードした画像一覧を取得
const blobs = await list();
return blobs;
}
const images = await allImages();
return (
<section>
{images.blobs.map((image) => (
<Image
priority
key={image.pathname}
src={image.url}
alt="Image"
width={200}
height={200}
/>
))}
</section>
);
}
API ルートを使用してアップロード
fetch
を使用してアップロード
"use client";
import type { PutBlobResult } from "@vercel/blob";
import { useState, useRef } from "react";
export default function AvatarUploadPage() {
// input要素のDOM
const inputFileRef = useRef<HTMLInputElement>(null);
// アップロードしたVercelBlob画像
const [blob, setBlob] = useState<PutBlobResult | null>(null);
// サブミット送信イベント
const onSubmit = async (event) => {
event.preventDefault();
// ファイルの存在確認
if (!inputFileRef.current?.files) {
throw new Error("No file selected");
}
// ファイル取得
const file = inputFileRef.current.files[0];
// 画像アップロードAPIにPOSTリクエスト
const response = await fetch(
`/api/avatar/upload?filename=${file.name}`, {
method: "POST",
body: file,
});
// 新しい画像データをVercelBlobの型で取得
const newBlob = (await response.json()) as PutBlobResult;
// 格納
setBlob(newBlob);
};
// ...
}
画像アップロードAPIルートの作成
// src/app/api/avatar/upload/route.ts
import { put } from '@vercel/blob';
import { NextResponse } from 'next/server';
export async function POST(request: Request): Promise<NextResponse>; {
// クエリパラメータを取得
const { searchParams } = new URL(request.url);
// ファイル名を取得
const filename = searchParams.get('filename');
// Vercel Blobにアップロード
const blob = await put(filename, request.body, {
access: 'public',
});
return NextResponse.json(blob);
}
SDK
put()
このput
メソッドは、Blob オブジェクトを Blob ストアにアップロードします。
put(pathname, body, options);
パラメータ
pathname
: (必須) 戻り URL の基本値を指定する文字列body
: (必須)これらのサポートされているボディ タイプReadableStream
に基づく、String
、 、ArrayBuffer
またはとしての BLOB オブジェクトBlob
options
: (必須)JSON
次の必須パラメータとオプションのパラメータを持つオブジェクト:
オプション
返り値の例
{
pathname: 'profilesv1/user-12345.txt',
contentType: 'text/plain',
contentDisposition: 'attachment; filename="user-12345.txt"',
url: 'https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-NoOVGDVcqSPc7VYCUAGnTzLTG2qEM2.txt'
downloadUrl: 'https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-NoOVGDVcqSPc7VYCUAGnTzLTG2qEM2.txt?download=1'
}
del()
このdel
メソッドは、BLOB ストアから BLOB オブジェクトを削除します。
del(url, options);
パラメータ
url
: (必須) 削除する BLOB オブジェクトの一意の URL を指定する文字列または文字列の配列options
: (オプション)JSON
次のオプションのパラメータを持つオブジェクト:
オプション
token: リクエストを行うときに使用する読み取り/書き込みトークンを指定する文字列。
head()
このhead
メソッドは、BLOB オブジェクトのメタデータを返します。
head(url, options);
パラメータ
url
: (必須) 削除する BLOB オブジェクトの一意の URL を指定する文字列または文字列の配列options
: (オプション)JSON
次のオプションのパラメータを持つオブジェクト
オプション
token: リクエストを行うときに使用する読み取り/書き込みトークンを指定する文字列。
list()
このlist
メソッドは、Blob ストア内の BLOB オブジェクトのリストを返します。
list(options);
パラメータ
options
: (オプション)JSON
次のオプションのパラメータを持つオブジェクト:
オプション
token: リクエストを行うときに使用する読み取り/書き込みトークンを指定する文字列。
limit: 返される BLOB オブジェクトの最大数を指定する数値。デフォルトは 1000 です。
prefix: 特定のフォルダー名でフィルターするための文字列。
cursor: 結果のページネーションのために前の応答から取得した文字列。
mode: 応答形式を指定する文字列。expanded(デフォルト)。foldedで1 つのフォルダー文字列エントリ。
返り値の例
blobs: {
size: `number`;
uploadedAt: `Date`;
pathname: `string`;
url: `string`;
downloadUrl: `string`
}[]
cursor?: `string`;
hasMore: `boolean`;
folders?: `string[]`
copy()
このcopy
メソッドは、既存の BLOB オブジェクトを BLOB ストア内の新しいパスにコピーします。
copy(fromUrl, toPathname, options);
パラメータ
fromUrl
: (必須) 既存の BLOB を識別する BLOB URLtoPathname
: (必須) BLOB ストア内の新しいパスを指定する文字列。これは戻り URL の基本値になります。options
: (必須)JSON
次の必須パラメータとオプションのパラメータを持つオブジェクト:
オプション
access: 必須。
public
-private
のサポートが予定されていますcontentType: メディアの種類を示す文字列。デフォルトでは、toPathname の拡張子から抽出されます。
token: リクエストを行うときに使用するトークンを指定する文字列。
addRandomSuffix: パス名にランダムなサフィックスを追加するかどうかを指定するブール値。デフォルトは
false
ですcacheControlMaxAge: エッジおよびブラウザーのキャッシュを構成する秒数。デフォルトは 1 年。
返り値の例
{
pathname: 'profilesv1/user-12345-copy.txt',
contentType: 'text/plain',
contentDisposition: 'attachment; filename="user-12345-copy.txt"',
url: 'https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-copy.txt'
downloadUrl: 'https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-copy.txt?download=1'
}