🖼️

Vercel Blobを試してみる

2024/11/07に公開

Vercelが出しているクラウドストレージであるVercel Blobを試してみます。2024年11月現在まだBeta版の機能となっています。
https://vercel.com/docs/storage/vercel-blob

ストレージを作成する

VercelのProjectページのStorageタブからCreate Databaseでストレージを作成できます。

Blobを選択します。これでプロジェクトにBlobがConnectされました。

ローカル環境から画像をアップロードしてみる

Getting Startedの通りにvercel linkvercel env pull .env.development.localを実行します。

$ vercel link
Vercel CLI 37.14.0
? Set up “~/********”? yes
? Which scope should contain your project? ********'s projects
? Found project “********s-projects/project-name Link to it? yes
✅  Linked to ********s-projects/project-name (created .vercel)
$ vercel env pull .env.development.local
Vercel CLI 37.14.0
> Downloading `development` Environment Variables for ********-projects/project-name
✅  Created .env.development.local file  [167ms]

packageを追加します。

pnpm add @vercel/blob

サンプルのソースコードを追加します。単純にアップロードする口をapi routeに追加します。

src/app/avatar/upload/page.tsx
'use client';

import type { PutBlobResult } from '@vercel/blob';
import { useState, useRef } from 'react';

export default function AvatarUploadPage() {
  const inputFileRef = useRef<HTMLInputElement>(null);
  const [blob, setBlob] = useState<PutBlobResult | null>(null);
  return (
    <>
      <h1>Upload Your Avatar</h1>

      <form
        onSubmit={async (event) => {
          event.preventDefault();

          if (!inputFileRef.current?.files) {
            throw new Error("No file selected");
          }

          const file = inputFileRef.current.files[0];

          const response = await fetch(
            `/api/avatar/upload?filename=${file.name}`,
            {
              method: 'POST',
              body: file,
            },
          );

          const newBlob = (await response.json()) as PutBlobResult;

          setBlob(newBlob);
        }}
      >
        <input name="file" ref={inputFileRef} type="file" required />
        <button type="submit">Upload</button>
      </form>
      {blob && (
        <div>
          Blob url: <a href={blob.url}>{blob.url}</a>
        </div>
      )}
    </>
  );
}
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');

  // ⚠️ The below code is for App Router Route Handlers only
  const blob = await put(filename, request.body, {
    access: 'public',
  });

  // Here's the code for Pages API Routes:
  // const blob = await put(filename, request, {
  //   access: 'public',
  // });

  return NextResponse.json(blob);
}

// The next lines are required for Pages API Routes only
// export const config = {
//   api: {
//     bodyParser: false,
//   },
// };

画像をアップロードしてみる

ローカルでサンプルコードを起動して画像をアップロードしてみました。

アップロードが成功してVercelの管理画面上にちゃんと表示されました。

クライアント側でアップロードさせる

先程のサンプルコードではサーバ側で画像をアップロードさせています。
Vercel FunctionsにはリクエストBodyは4.5MBまでという制限があるので、api route経由でアップロードさせる方法ではこのサイズ制限に引っかかってしまいます。
https://vercel.com/docs/functions/runtimes#request-body-size

クライアントサイドから直接アップロードさせる方法だとその制限にひっかかりません。
https://vercel.com/docs/storage/vercel-blob/client-upload

アップロードするためのtokenをサーバサイドで発行した後にクライアントから直接Vercel Blobにアップロードします。

src/app/avatar/upload/page.tsx
'use client';
 
import { type PutBlobResult } from '@vercel/blob';
import { upload } from '@vercel/blob/client';
import { useState, useRef } from 'react';
 
export default function AvatarUploadPage() {
  const inputFileRef = useRef<HTMLInputElement>(null);
  const [blob, setBlob] = useState<PutBlobResult | null>(null);
  return (
    <>
      <h1>Upload Your Avatar</h1>
 
      <form
        onSubmit={async (event) => {
          event.preventDefault();
 
          if (!inputFileRef.current?.files) {
            throw new Error('No file selected');
          }
 
          const file = inputFileRef.current.files[0];
 
          const newBlob = await upload(file.name, file, {
            access: 'public',
            handleUploadUrl: '/api/avatar/upload',
          });
 
          setBlob(newBlob);
        }}
      >
        <input name="file" ref={inputFileRef} type="file" required />
        <button type="submit">Upload</button>
      </form>
      {blob && (
        <div>
          Blob url: <a href={blob.url}>{blob.url}</a>
        </div>
      )}
    </>
  );
}

アップロードするためのtokenを生成して返すapi routeです。

src/app/api/avatar/upload/route.ts
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
import { NextResponse } from 'next/server';
 
export async function POST(request: Request): Promise<NextResponse> {
  const body = (await request.json()) as HandleUploadBody;
 
  try {
    const jsonResponse = await handleUpload({
      body,
      request,
      onBeforeGenerateToken: async (
        pathname,
        /* clientPayload */
      ) => {
        // Generate a client token for the browser to upload the file
        // ⚠️ Authenticate and authorize users before generating the token.
        // Otherwise, you're allowing anonymous uploads.
 
        return {
          allowedContentTypes: ['image/jpeg', 'image/png', 'image/gif'],
          tokenPayload: JSON.stringify({
            // optional, sent to your server on upload completion
            // you could pass a user id from auth, or a value from clientPayload
          }),
        };
      },
      onUploadCompleted: async ({ blob, tokenPayload }) => {
        // Get notified of client upload completion
        // ⚠️ This will not work on `localhost` websites,
        // Use ngrok or similar to get the full upload flow
 
        console.log('blob upload completed', blob, tokenPayload);
 
        try {
          // Run any logic after the file upload completed
          // const { userId } = JSON.parse(tokenPayload);
          // await db.update({ avatar: blob.url, userId });
        } catch (error) {
          throw new Error('Could not update user');
        }
      },
    });
 
    return NextResponse.json(jsonResponse);
  } catch (error) {
    return NextResponse.json(
      { error: (error as Error).message },
      { status: 400 }, // The webhook will retry 5 times waiting for a 200
    );
  }
}

認証を必須にしたい→できない

Vercel Blobにアップロードしたファイルに認証をかけることはできないようです。ログインした特定のユーザーのみがアクセスできるようにするといったことはできません。

URLは推測が難しい文字列になるものの、URLを知っていれば誰でもアクセス可能な状態になります。
https://vercel.com/docs/storage/vercel-blob#security

Discussion