"Where to put data on Vercel" changed greatly in 2026. The former Vercel Postgres / Vercel KV are discontinued, and now it's a design of choosing the optimal storage per use. Get the selection wrong and you'll suffer on cost, latency, or connection exhaustion. This article, faithful to the official specs of Vercel Blob, Edge Config, and Marketplace, collects how to choose and use them in real code.
For the big picture, see the Vercel production-operations guide.
Choose by use: four options
| What you want to store | Option | In one line |
|---|---|---|
| Objects (images, video, PDFs, large files) | Vercel Blob | S3-backed object storage. CDN delivery |
| Small data read with ultra-low latency (flags, redirects, IP lists) | Edge Config | Global reads with P99 sub-15ms |
| Relational DB | Marketplace: Neon (Postgres) | Serverless Postgres. Auto env injection |
| KV / cache / rate limiting | Marketplace: Upstash (Redis) | Serverless Redis |
Decision shortcuts:
- Files users upload → Blob (client upload)
- A high-read, low-write setting like "maintenance mode ON/OFF" → Edge Config
- Core data needing transactions/relations/aggregation → Neon (Postgres)
- Sessions, cache, distributed rate limiting → Upstash (Redis)
Vercel Blob: object storage
public and private (unchangeable after creation)
A Blob store chooses public or private at creation and can't change it later. The delivery path changes by access mode (Blob docs).
| Private | Public | |
|---|---|---|
| Write | Auth required | Auth required |
| Read | Auth required (token needed) | Anyone who knows the URL |
| Delivery | Via a function (get()) | Direct Blob URL |
| Suited for | Confidential documents, user content | Large media, public assets |
Don't get the choice wrong: confidential things like invoice PDFs and identity documents are private, no question. Avatars and public images are public. Since you can't change it after creation, split stores per use.
Basic operations (@vercel/blob)
import { put, del, list, head, copy } from "@vercel/blob";
// アップロード(put)
const blob = await put(`invoices/${id}.pdf`, pdfBuffer, {
access: "private", // 'public' も
addRandomSuffix: true, // 'invoices/abc-<random>.pdf' 衝突防止
contentType: "application/pdf",
});
// メタデータ取得(head)— ETag も取れる
const meta = await head(blob.url);
// 一覧(list)— prefix でフォルダ的に絞る
const { blobs } = await list({ prefix: "invoices/", limit: 1000 });
// 削除(del)— 課金対象外(無料)
await del(blob.url);
User uploads go via client upload
If you receive a user-uploaded file at the server (function) once and transfer it to Blob, the function's data-transfer billing applies, and you hit the 4.5MB request-body limit. With client upload, you can send directly to Blob from the browser, with no data-transfer billing either.
// サーバー:アップロード用トークンを発行(認可をここで行う)
// app/api/upload/route.ts
import { handleUpload, type HandleUploadBody } from "@vercel/blob/client";
export async function POST(request: Request): Promise<Response> {
const body = (await request.json()) as HandleUploadBody;
const json = await handleUpload({
body,
request,
onBeforeGenerateToken: async (pathname) => {
// ★ ここで認証・拡張子・サイズ制限などの認可を行う
await assertAuthenticated(request);
return {
allowedContentTypes: ["image/jpeg", "image/png", "application/pdf"],
addRandomSuffix: true,
};
},
onUploadCompleted: async ({ blob }) => {
// アップロード完了後の後処理(DBへURL保存など)
await saveBlobReference(blob.url);
},
});
return Response.json(json);
}
// クライアント:ブラウザから直接 Blob へ(サーバーを経由しない)
"use client";
import { upload } from "@vercel/blob/client";
async function onFile(file: File) {
const blob = await upload(file.name, file, {
access: "public",
handleUploadUrl: "/api/upload", // ↑のトークン発行エンドポイント
});
return blob.url;
}
For files over 100MB, multipart upload is recommended (put/upload auto-handle splitting, parallelism, and resume; even on a network drop, only the relevant part is resent).
Concurrent updates safely: conditional writes (ifMatch)
When multiple processes might update the same Blob (a shared config file, etc.), use optimistic concurrency control. Pass the ETag obtained from head()/get() to ifMatch, and it succeeds only if it hasn't changed since.
import { head, put, BlobPreconditionFailedError } from "@vercel/blob";
const meta = await head("config.json");
try {
await put("config.json", JSON.stringify(next), {
access: "private",
allowOverwrite: true,
ifMatch: meta.etag, // 他プロセスが更新していたら失敗
});
} catch (e) {
if (e instanceof BlobPreconditionFailedError) {
// 競合:再読込してリトライ or 衝突処理
}
throw e;
}
Caching and immutable operation
Blob is cached on the CDN for 1 month by default (changeable with cacheControlMaxAge). Reflecting an update/delete takes up to 60 seconds, and the browser cache is separate. So the official recommendation is "treat Blob as immutable" — instead of updating, create with a new pathname (addRandomSuffix or timestamp/UUID). Only for data you really must update at the same URL (a ranking JSON every 5 minutes, etc.), overwrite with a short cacheControlMaxAge.
Durability is 99.999999999% (11 nines) on the S3 backend, and availability 99.99% (4 nines). You can safely use it as a production file foundation.
Edge Config: ultra-low-latency global reads
What's good about it
Edge Config is a global store for "fast reads, rare writes" data. At P99 sub-15ms (often sub-1ms), it reads near the user without hitting an external DB (Edge Config docs).
Representative uses:
- Feature flags / A-B tests: ON/OFF without redeploying
- Emergency redirects / maintenance mode: on an incident, redirect instantly without touching code
- IP blocklist: reject malicious IPs without calling the upstream
Reading (SDK)
// Middleware や関数で(@vercel/edge-config)
import { get, getAll, has } from "@vercel/edge-config";
// 機能フラグ
const newCheckout = await get<boolean>("feature_new_checkout");
// メンテモード(Routing Middleware で全リクエストに対し超低遅延で判定)
export async function middleware(request: Request) {
if (await get<boolean>("maintenance_mode")) {
return Response.redirect(new URL("/maintenance", request.url));
}
}
Writing and protecting
- Reads are protected with a read access token, writes with an API token (secure by default).
- Writes are designed to happen outside the request path (the dashboard or REST API). In other words, it's not something you "write during the app's response."
- Vercel's read optimization works on the Edge / Node.js runtime (others require an application).
Not a substitute for a DB: Edge Config is for "small, rarely updated settings." Send user data and frequently written data to Neon/Upstash. The difference from environment variables is that it can be updated without a redeploy, with no downtime risk.
Marketplace: integrating Neon and Upstash
Integrate a relational DB or Redis from the Vercel Marketplace. Connecting with vercel integration auto-injects environment variables like the connection string and unifies billing to Vercel.
# Marketplace 統合の接続(例)
vercel integration add neon # サーバーレス Postgres
vercel integration add upstash # サーバーレス Redis
vercel env pull .env.local # 注入された接続情報をローカルへ
Mind connection exhaustion in serverless
Postgres's "1 connection = 1 process" is heavy, and under Fluid Compute's concurrency connections tend to be exhausted. The countermeasure is connection pooling — use Neon's pooled connection string (internally PgBouncer-equivalent) and connect to the pooled endpoint from serverless.
// Neon:pooled 接続を使う(サーバーレスでは必須級)
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!); // pooled エンドポイント
export async function GET() {
const rows = await sql`SELECT id, title FROM posts ORDER BY created_at DESC LIMIT 20`;
return Response.json(rows);
}
The design principles of connection pooling (why it's mandatory in serverless, the transaction-mode constraints) are detailed in the PostgreSQL connection-pooling / PgBouncer guide. To put a type-safe ORM over it, see Drizzle ORM or Prisma v7, and for distributed rate limiting with Redis, serverless rate limiting.
Production checklist (storage)
- Choose the storage location by the nature of the data (object/config/relational/KV)
- Settle Blob public/private by use (unchangeable after creation)
- User uploads via client upload (avoid transfer billing and the 4.5MB limit), with authorization at token issuance
- Immutable operation for Blob (
addRandomSuffix),ifMatchfor concurrent updates - Confidential files are private + delivered via a function
- Feature flags and maintenance mode in Edge Config (no redeploy)
- Postgres prevents connection exhaustion with pooled connections
- Marketplace integration's environment variables are auto-injected; don't write secrets in code
Conclusion
2026's Vercel storage isn't "everything in one DB" but a design of combining the optimal solution per use.
- Objects in Blob (client upload, immutable, private/public correctly)
- High-read settings in Edge Config (ultra-low latency, no redeploy)
- Relational in Neon, KV/Redis in Upstash (Marketplace integration)
- In serverless, connection pooling to prevent exhaustion
- Secrets in environment variables, Blob uploads with authorization at token issuance
Next, go to the cost / Active CPU optimization guide for operating these cheaply.
This article is based on the official documentation of Vercel Blob / Edge Config / Marketplace (as of June 2026). The spec, limits, and pricing get updated, so confirm the latest values in the official docs when adopting in production.