Vercel Blob Uploads (Project-Specific)
Use this skill when you need to replace remote image/file URLs with Vercel Blob URLs for this repo.
Prereqs
- •Dependency already exists:
@vercel/blobinpackage.json. - •You need a Vercel Blob read/write token for this project.
Set the token in your shell (do not commit it):
bash
export BLOB_READ_WRITE_TOKEN="<token>"
Or load it from this repo's .env:
bash
set -a source .env set +a
Upload a remote URL (download + upload)
Run from the repo root:
bash
node --input-type=module <<'NODE'
import { put } from '@vercel/blob';
const url = 'https://example.com/image.png';
const filename = 'portfolio/example-image.png';
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to download: ${res.status} ${res.statusText}`);
const contentType = res.headers.get('content-type') ?? 'application/octet-stream';
const buffer = Buffer.from(await res.arrayBuffer());
const blob = await put(filename, buffer, {
access: 'public',
contentType,
addRandomSuffix: false,
});
console.log(blob.url);
NODE
Upload a local file
bash
node --input-type=module <<'NODE'
import { put } from '@vercel/blob';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
const filePath = '/tmp/logo.png';
const filename = `portfolio/${path.basename(filePath)}`;
const buffer = await readFile(filePath);
const blob = await put(filename, buffer, {
access: 'public',
contentType: 'image/png',
addRandomSuffix: false,
});
console.log(blob.url);
NODE
Multiple files (pattern)
bash
node --input-type=module <<'NODE'
import { put } from '@vercel/blob';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
const files = ['/tmp/logo-1.png', '/tmp/logo-2.png'];
for (const filePath of files) {
const buffer = await readFile(filePath);
const filename = `portfolio/${path.basename(filePath)}`;
const blob = await put(filename, buffer, {
access: 'public',
contentType: 'image/png',
addRandomSuffix: false,
});
console.log(`${filePath} -> ${blob.url}`);
}
NODE
After upload
- •Replace the old URLs in content sources (often
content/data/portfolio.jsonandcontent/pages/portfolio.md). - •Keep blob filenames organized by area (
portfolio/,talks/, etc.). - •Do not store tokens in repo files or scripts.
Deterministic paths
All examples above use addRandomSuffix: false so the blob URL matches the exact filename you provide (no random suffix appended). Essay images use stable paths like blog/<slug>/chart.png.
Initial uploads omit allowOverwrite (defaults to false). This is intentional — if the path already exists, the upload fails, catching accidental overwrites from mistyped or reused filenames.
Re-uploads (e.g., replacing images with optimized versions) require allowOverwrite: true:
js
const blob = await put(filename, buffer, {
access: 'public',
contentType,
addRandomSuffix: false,
allowOverwrite: true, // only for intentional re-uploads
});
Notes from prior sessions
- •Uploads were done via inline Node scripts using
@vercel/blob’sput. - •
BLOB_READ_WRITE_TOKENis required in the environment.