Vercel Blobのファイルストレージを使って画像をNext.jsで操作する

Nextman
Nextman

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 ストア ページが表示されます。

先ほど作成したProjectConnectしておきましょう

2. ローカル環境のセットアップする

プロジェクトで BLOB ストアを作成後、BLOB_READ_WRITE_TOKENという環境変数が自動的に作成され、プロジェクトに追加されました。

この環境変数をローカルで使用するには、Vercel CLI を使用して環境変数を取得することをお勧めします。

まずvercelコマンドが利用できるようにします。

npm i -g vercel

Vercelのprojectとリンクさせます。

vercel link

環境変数を引っ張ってきます。

vercel env pull .env.development.local

ローカルでも使用するので、.env.localBLOB_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 URL

  • toPathname: (必須) 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'
}
AIへの質問や指示のプロンプト共有コミュニティ Promptolkクリエイターのための情報共有コミュニティ Utan