Vercel Blobを試してみる
Vercelが出しているクラウドストレージであるVercel Blobを試してみます。2024年11月現在まだBeta版の機能となっています。
ストレージを作成する
VercelのProjectページのStorageタブからCreate Databaseでストレージを作成できます。
Blobを選択します。これでプロジェクトにBlobがConnectされました。
ローカル環境から画像をアップロードしてみる
Getting Startedの通りにvercel link
とvercel 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に追加します。
'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>
)}
</>
);
}
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経由でアップロードさせる方法ではこのサイズ制限に引っかかってしまいます。
クライアントサイドから直接アップロードさせる方法だとその制限にひっかかりません。
アップロードするためのtokenをサーバサイドで発行した後にクライアントから直接Vercel Blobにアップロードします。
'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です。
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を知っていれば誰でもアクセス可能な状態になります。
Discussion