Skip to main content
友田 陽大
Vercel in production
Vercel
ストレージ
Next.js
TypeScript
コスト最適化
アーキテクチャ設計
PostgreSQL

Vercel storage implementation guide: choosing Blob, Edge Config, and Marketplace (Neon/Upstash) correctly by use

A storage-selection and implementation guide faithful to Vercel's official docs. With real code, it explains: Vercel Blob (public/private, @vercel/blob's put/del/list, client/server upload, conditional writes ifMatch, multipart, S3 backend 11 nines), Edge Config (P99 sub-15ms global reads, feature flags/redirects/IP block), and Marketplace (Neon Postgres, Upstash Redis; Vercel Postgres/KV discontinued).

Published
Reading time
7 min read
Author
友田 陽大
Share

"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 storeOptionIn one line
Objects (images, video, PDFs, large files)Vercel BlobS3-backed object storage. CDN delivery
Small data read with ultra-low latency (flags, redirects, IP lists)Edge ConfigGlobal reads with P99 sub-15ms
Relational DBMarketplace: Neon (Postgres)Serverless Postgres. Auto env injection
KV / cache / rate limitingMarketplace: 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).

PrivatePublic
WriteAuth requiredAuth required
ReadAuth required (token needed)Anyone who knows the URL
DeliveryVia a function (get())Direct Blob URL
Suited forConfidential documents, user contentLarge 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), ifMatch for 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.

  1. Objects in Blob (client upload, immutable, private/public correctly)
  2. High-read settings in Edge Config (ultra-low latency, no redeploy)
  3. Relational in Neon, KV/Redis in Upstash (Marketplace integration)
  4. In serverless, connection pooling to prevent exhaustion
  5. 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.

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

I can take on the implementation from this article as an engagement

Vercel apps, from design to production and cost optimization

Function design assuming Fluid Compute (safe global state, waitUntil, Cron), four-layer caching (ISR/CDN/Runtime Cache/Cache Components), safe deploys (preview/Promote/Instant Rollback/Rolling Releases), entry-point defense (Firewall/WAF/BotID), storage selection (Blob/Edge Config/Marketplace), and Active-CPU-billing-aware cost optimization. With experience running Next.js products on Vercel in production, I deliver fast, cheap, and secure.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading