Handling user file uploads usually means provisioning a storage bucket, wiring up credentials, and writing upload logic before you can ship anything. The Vercel Blob Next.js Starter removes that setup by giving you a working image uploader built on Vercel Blob, Vercel's object storage service.
The template uses client uploads, so files up to 50 MB go directly from the browser to your private Blob store without passing through your server. This guide walks through deploying the template, running it locally, and understanding how each part of the code works so you can adapt it to your own application.
Copy link to headingOverview
In this guide, you'll learn how to:
- Deploy the Blob starter template with a Blob store provisioned automatically
- Clone and run the template locally with the right environment variables
- Understand how client uploads secure the browser-to-store transfer
- Serve private blobs through a delivery route that you control
Copy link to headingPrerequisites
- Node.js 22+ and a package manager (e.g., pnpm)
- A Vercel account with Vercel Blob
- Vercel CLI installed (
npm i -g vercel)
Copy link to headingDeploy the template
Deploy the starter from the template page by clicking Deploy. Vercel forks the repository to your Git account, creates a project, provisions a Blob store, and configures the environment variables for you.
When the deployment finishes, open your project's URL and upload an image. Drag a file onto the upload area or click to select one, then select Upload. A progress bar tracks the transfer, and a toast notification links to the uploaded file when it completes.
You can inspect the uploaded blob from your dashboard. Go to your project, open the Storage tab, and select the Blob store to browse files, view metadata, and delete blobs.
Copy link to headingRun the template locally
If you'd rather start from the code, bootstrap the example with create-next-app:
The app needs a BLOB_READ_WRITE_TOKEN environment variable to generate client upload tokens. If you deployed the template first, link your local folder to the Vercel project and pull the variables:
If you haven't deployed yet, create a Blob store from your dashboard under Storage, then copy .env.example to .env.local and paste the token from the store's settings to .env.local.
Start the development server:
The app runs at http://localhost:3000, and uploads from your local machine go to the same Blob store as production.
Copy link to headingHow client uploads work
The template uses client uploads because they bypass the 4.5 MB request body limit that applies when a file passes through a Vercel Function. The file travels directly from the browser to the Blob store, and the template caps uploads at 50 MB.
Direct browser uploads still need authorization, which happens through a token exchange with your server. The flow has three parts:
- The browser calls
upload()from@vercel/blob/client, which first requests a short-lived client token from your/api/uploadroute. - Your server route validates the request with
handleUpload()and returns a token that constrains what the browser can upload. - The browser uploads the file directly to the Blob store using that token.
Because the store uses private access mode, uploaded files aren't reachable at their blob URL from a browser. The template includes a /api/blob delivery route that fetches and streams private blobs on request, which gives you a place to enforce your own access rules.
Copy link to headingWalk through the code
Copy link to headingThe upload component
The uploader in components/uploader.tsx is a client component. It validates that the file is an image under 50 MB, shows a local preview, then calls upload() on submit:
The handleUploadUrl option points at the server route that issues the client token. The onUploadProgress callback fires as parts of the file transfer, which drives the progress bar in the UI. After the upload resolves, the component builds a link to the delivery route using the returned blob.pathname rather than the raw blob URL, since private blobs can't be opened directly.
Copy link to headingThe token route
The route handler in app/api/upload/route.ts uses handleUpload() from @vercel/blob/client to generate client tokens and receive completion callbacks:
The options returned from onBeforeGenerateToken are enforced server-side, so the browser can only upload images up to 50 MB even if someone bypasses the client-side checks. Setting addRandomSuffix: true appends a unique suffix to each pathname, which prevents collisions when two users upload files with the same name.
The onUploadCompleted callback is where you'd persist the blob URL to your database. Vercel calls it as a webhook after the upload finishes, and retries up to 5 times if your route doesn't return a 200 response.
Copy link to headingThe delivery route
The route handler in app/api/blob/route.ts serves private blobs to the browser. It reads a pathname query parameter, fetches the blob server-side with get(), and streams the content back:
This pattern puts your application in the request path for every download, which is what makes private storage useful. Check the user's session before calling get() and you have per-user access control over stored files.
Copy link to headingBest practices
Copy link to headingAuthenticate before generating tokens
The template's onBeforeGenerateToken callback returns a token to any caller, which means anyone who finds your deployment can upload files. Before shipping to production, check the user's session inside the callback and throw an error for unauthenticated requests. You can also store the user's ID in tokenPayload so onUploadCompleted knows who uploaded the file.
Copy link to headingTest upload callbacks with a tunnel
The onUploadCompleted callback won't fire during local development because Vercel can't reach localhost. To test it, expose your dev server with a tunneling tool such as ngrok, or verify the callback behavior on a preview deployment.
Copy link to headingChoose the access mode deliberately
The template uses a private store, where every download goes through your delivery route. If you're storing content that anyone may view, such as public avatars or marketing images, a public store serves files directly from the CDN with browser caching and no function invocation per download. You can't change a store's access mode after creating it, so decide based on your use case up front.
Copy link to headingRelated resources and next steps
- Read the Vercel Blob documentation for access modes, caching, and pricing details
- Learn more about client uploads and the
handleUpload()helper - Review server uploads for files under 4.5 MB that can pass through your server
- Explore the @vercel/blob SDK reference for
put(),get(),list(), anddel() - Check the Next.js App Router documentation for more on Route Handlers