Closed3

最小Vercel Blob体験

js4000alljs4000all

とりあえず、npmが使える状態にする。
コンテナの中で作業するなら、イメージはnode:22を使うのが良いだろう(VercelのNode.jsバージョンがデフォルトで22.xなので)。

ガワを作る

npm create vite@latest vercel-blob-demo -- --template react-ts
cd vercel-blob-demo
git init
git switch -c dev
git add .
git commit -m "Initial commit"

GitHubリポジトリをVercelと結びつける

GitHubリポジトリを適当に作る。
Vercelアカウントを作り、GitHubアカウントと関連付ける。
Settingsで、Vercelからリポジトリが見えるようにする。


Vercel側でインポートする。

viteを使うのでビルドコマンドと出力先ディレクトリを変更する。

Deployするとエラーになるが、インポートはできている。

手元のReactプロジェクトをpushする

手元の作業ディレクトリと結びつけ、とりあえずそのままpush。

git remote add origin https://github.com/js4000all/vercel-blob-demo.git
git fetch
git rebase -X theirs origin/main
git push --set-upstream origin dev

Vercel側でデプロイされる。

js4000alljs4000all

Vercel Blobストアを作る

当該プロジェクトのStorageから。hobbyプランだとアカウントあたり1個しか作れないので、作成済みのストアがあるならconnectする。

これで、Vercel Function内でVercel Blob SDKを使えば、Vercel Blobにアクセスできるようになった(アクセストークンは環境変数として設定されており、SDKが勝手に参照する)。

試してないが、Next.jsのAPI Routes内でSDKを使うことができるようだ。Vercel Functionもその類型。

js4000alljs4000all

Vercel Blob SDKをインストール

npm install @vercel/blob @vercel/node

Vercel Functionを実装

put関数で保存するAPI。

api/save-blob.ts
import { put } from '@vercel/blob';
import { VercelRequest, VercelResponse } from '@vercel/node';

export default async function handler(req: VercelRequest, res: VercelResponse) {
    if (req.method !== 'POST') {
        return res.status(405).json({ error: 'Method not allowed' });
    }
    const { key, content } = req.body;

    if (!key || !content) {
        return res.status(400).json({ error: 'Key and value are required' });
    }
    const result = await put(key, content, {
        access: "public",
        addRandomSuffix: false,
        cacheControlMaxAge: 0,
    });
    return res.status(200).json(result);
}

head関数でメタ情報を取得するAPI。

api/head-blob.ts
import { head } from '@vercel/blob';
import { VercelRequest, VercelResponse } from '@vercel/node';

export default async function handler(req: VercelRequest, res: VercelResponse) {
    const { key } = req.query as { key: string };
    if (!key) {
        return res.status(400).json({ error: 'Key is required' });
    }
    const blob = await head(key);
    return res.status(200).json(blob);
}

これらのAPIをVercel Functionのランタイムで動作させるには、ESMとしてコンパイルされないといけない。そのためのTypeScriptコンパイラへの指示を追加する。

tsconfig.json
  "compilerOptions": {
    "module": "ESNext"
  }

アプリ本体を実装

Vercel Functionをfetchでコールすることでアップロードとダウンロードを行うUIを実装。

App.tsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

export default function App() {
  const [key, setKey] = useState('');
  const [content, setContent] = useState('');
  const [fetched, setFetched] = useState('');
  const [message, setMessage] = useState('');

  const handleUpload = async () => {
    setMessage('Uploading...');
    try {
      const res = await fetch('/api/save-blob', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ key, content }),
      });
      const data = await res.json();
      setMessage(`✅ Uploaded: ${data.url}`);
    } catch (err) {
      setMessage('❌ Upload failed');
    }
  };

  const handleDownload = async () => {
    setMessage('Downloading...');
    try {
      const res = await fetch(`/api/head-blob?key=${encodeURIComponent(key)}`);
      const data = await res.json();
      const fileRes = await fetch(data.url);
      const text = await fileRes.text();
      setFetched(text);
      setMessage('✅ Download complete');
    } catch (err) {
      setMessage('❌ Download failed');
    }
  };

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>🗂 Vercel Blob Demo</h1>
      <div className="card">
        <label>
          ファイル名(key): <br />
          <input
            value={key}
            onChange={(e) => setKey(e.target.value)}
            placeholder="example.txt"
            style={{ width: '100%', padding: 8 }}
          />
        </label>

        <br /><br />

        <label>
          ファイル内容(content): <br />
          <textarea
            value={content}
            onChange={(e) => setContent(e.target.value)}
            rows={5}
            style={{ width: '100%', padding: 8 }}
          />
        </label>

        <br /><br />

        <button onClick={handleUpload} style={{ marginRight: 10 }}>
          ⬆️ アップロード
        </button>
        <button onClick={handleDownload}>
          ⬇️ 読み取り
        </button>

        <p>{message}</p>

        {fetched && (
          <>
            <h3>📄 読み取った内容:</h3>
            <pre style={{ background: '#f4f4f4', padding: 10 }}>{fetched}</pre>
          </>
        )}
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

ビルドできればとりあえずOK。pushすると、vercelでビルド&デプロイされる。

yarn build
git push
このスクラップは6ヶ月前にクローズされました