Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Wasabi with V0

To use Wasabi with V0 by Vercel, generate your file upload UI in V0, then create a Next.js API route using the AWS SDK v3 S3 client pointed at Wasabi's S3-compatible endpoints. Store WASABI_ACCESS_KEY_ID, WASABI_SECRET_ACCESS_KEY, and your bucket name in Vercel environment variables. Wasabi is up to 80% cheaper than AWS S3 with no egress fees — ideal for apps with large file storage needs.

What you'll learn

  • How to configure the AWS SDK v3 S3Client to point at Wasabi's S3-compatible API
  • How to create a Next.js API route for file uploads, downloads, and listing objects
  • How to generate presigned URLs for secure direct client uploads and downloads
  • How to store WASABI_ACCESS_KEY_ID and WASABI_SECRET_ACCESS_KEY safely in Vercel
  • Why Wasabi has no egress fees and how that changes your cost model versus AWS S3
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read25 minutesStorageApril 2026RapidDev Engineering Team
TL;DR

To use Wasabi with V0 by Vercel, generate your file upload UI in V0, then create a Next.js API route using the AWS SDK v3 S3 client pointed at Wasabi's S3-compatible endpoints. Store WASABI_ACCESS_KEY_ID, WASABI_SECRET_ACCESS_KEY, and your bucket name in Vercel environment variables. Wasabi is up to 80% cheaper than AWS S3 with no egress fees — ideal for apps with large file storage needs.

Adding Wasabi Object Storage to Your V0 Next.js App

When you need to store user-uploaded files — profile photos, documents, audio files, or video — you need object storage. AWS S3 is the most well-known option, but Wasabi offers the same S3-compatible API at up to 80% lower cost with a critical pricing difference: zero egress fees. Every time a user downloads a file from S3, you pay for the data transfer. With Wasabi, downloads are free. For apps where users frequently view or download stored content, this can eliminate a major cost driver entirely.

The best part for V0 developers is that Wasabi works with the AWS SDK v3 S3Client without any modifications — you just point the client at Wasabi's endpoint URL instead of AWS's. No new SDK to learn, no new API patterns. If you've seen S3 integration code before, Wasabi is identical except for the endpoint and credentials.

This tutorial covers the complete workflow: generating a file upload UI in V0, configuring the AWS SDK v3 to use Wasabi, creating API routes for uploads and downloads, generating presigned URLs for secure direct uploads, and deploying to Vercel with credentials stored safely in environment variables.

Integration method

Next.js API Route

V0 generates the file upload or media management UI. You then create a Next.js API route that uses the AWS SDK v3 S3 client configured with Wasabi's custom endpoint, keeping your access credentials server-side. The frontend calls the API route to upload files, generate signed download URLs, and list bucket contents.

Prerequisites

  • A V0 account at v0.dev — the free tier is sufficient for this tutorial
  • A Wasabi account at wasabi.com — the free trial includes 1 TB for 30 days
  • A Wasabi bucket created in your preferred region (e.g., us-east-1 at s3.wasabisys.com)
  • A Wasabi access key ID and secret access key from the Wasabi console under Access Keys
  • A Vercel account (free) for environment variable storage and deployment

Step-by-step guide

1

Create a Wasabi Bucket and Access Keys

Before writing any code, set up the Wasabi resources you need. Log in to the Wasabi console at console.wasabisys.com. Click Create Bucket in the Buckets section. Give the bucket a unique name (e.g., my-app-uploads), select a region — Wasabi has regions in the US, EU, and Asia-Pacific, all with the same pricing — and leave the default settings (versioning off, logging off) for now. Click Create Bucket. The endpoint URL for your bucket follows this pattern: s3.REGION.wasabisys.com. For example, us-east-1 is s3.wasabisys.com, eu-central-1 is s3.eu-central-1.wasabisys.com. Note your bucket's endpoint. Next, create an access key: go to Access Keys in the left navigation and click Create New Access Key. Wasabi shows you the access key ID and the secret access key once — copy both immediately and save them somewhere safe. The secret key is only shown once and cannot be recovered. Unlike AWS IAM, Wasabi's access keys have full access to all your buckets by default; there is no bucket-level IAM policy per key in the basic Wasabi console. For production apps with multiple services, consider using sub-users to scope access. Once you have the bucket name, endpoint, access key ID, and secret key, you have everything needed to configure the SDK.

Pro tip: Write down the endpoint URL for your bucket region now — it's easy to forget, and using the wrong endpoint causes confusing 301 redirect errors that are hard to debug.

Expected result: A Wasabi bucket exists with a known name and region endpoint URL. An access key ID and secret access key are saved securely (not in code).

2

Generate the File Upload UI in V0

Open V0 at v0.dev and describe the file upload interface you want. V0 generates React components with Tailwind CSS and shadcn/ui. For file uploads, be specific about the interaction: drag-and-drop zone, click-to-browse fallback, file type restrictions, upload progress indicator, and the success state. V0's generated code will use the browser's native File API and show a preview — at this stage, the upload logic will call a placeholder API route. Once the UI looks right in V0's preview, use Design Mode (Option+D) to refine the visual design without spending credits. Pay attention to how V0 structures the upload logic in the component — it will likely use a FormData object and a fetch call to your API route. Note the field name V0 uses for the file in the FormData (usually 'file') so you can match it in your API route. V0 may generate code that calls the storage service directly from the browser — you'll replace this with calls to your API route in a later step to keep credentials server-side. The key outcome of this step is a polished upload UI component that accepts a file, shows upload progress, and displays the result after completion.

V0 Prompt

Create a file upload component with a large dashed border drop zone that accepts image files and PDFs. Show an upload icon and the text 'Drop files here or click to browse'. When a file is selected, show a preview thumbnail (for images) or a file icon (for PDFs) with the filename and file size. Add an upload progress bar that fills from 0% to 100% during upload. After successful upload, show a green checkmark and the file URL. Call POST /api/storage/upload with the file as FormData.

Paste this in V0 chat

Pro tip: Ask V0 to accept only specific file types by adding the accept attribute to the file input: accept='image/*,application/pdf'. This prevents users from uploading unexpected file types.

Expected result: A polished file upload component renders in V0 preview with a drop zone, file preview, and progress bar. The component is wired to call POST /api/storage/upload.

3

Create the Wasabi Storage API Route

In V0's code editor, create app/api/storage/route.ts. This API route handles file uploads and generates signed download URLs using the AWS SDK v3 configured for Wasabi. Install the required packages by adding @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner to package.json. The critical configuration step is setting the endpoint field in the S3Client constructor to your Wasabi endpoint URL — this is what makes the AWS SDK connect to Wasabi instead of AWS. The endpoint format is https://s3.REGION.wasabisys.com. For uploads, the POST handler reads the FormData from the request, extracts the file, converts it to an ArrayBuffer, and calls PutObjectCommand. Always set ContentType from the file's type property so browsers can serve the file correctly later. For generating download URLs, the GET handler uses the getSignedUrl function from @aws-sdk/s3-request-presigner with a GetObjectCommand. Presigned URLs are time-limited — set expiresIn to a value in seconds (3600 for 1 hour, 86400 for 24 hours). For files that should be publicly accessible (like profile photos), you can also set a bucket policy in the Wasabi console to allow public read access and skip presigned URLs entirely. For the file key (the path inside the bucket), generate a unique name to avoid collisions: use a combination of a UUID and the original filename. The crypto.randomUUID() function is available in the Next.js App Router runtime without any import.

app/api/storage/route.ts
1import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
2import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
3import { NextRequest } from 'next/server';
4
5const s3 = new S3Client({
6 endpoint: `https://${process.env.WASABI_ENDPOINT}`,
7 region: process.env.WASABI_REGION ?? 'us-east-1',
8 credentials: {
9 accessKeyId: process.env.WASABI_ACCESS_KEY_ID!,
10 secretAccessKey: process.env.WASABI_SECRET_ACCESS_KEY!,
11 },
12 forcePathStyle: true, // Required for Wasabi compatibility
13});
14
15const BUCKET = process.env.WASABI_BUCKET_NAME!;
16
17export async function POST(request: NextRequest) {
18 const formData = await request.formData();
19 const file = formData.get('file') as File | null;
20
21 if (!file) {
22 return Response.json({ error: 'No file provided' }, { status: 400 });
23 }
24
25 const key = `uploads/${crypto.randomUUID()}-${file.name.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
26 const buffer = await file.arrayBuffer();
27
28 await s3.send(new PutObjectCommand({
29 Bucket: BUCKET,
30 Key: key,
31 Body: Buffer.from(buffer),
32 ContentType: file.type,
33 }));
34
35 // Generate a signed URL valid for 24 hours
36 const url = await getSignedUrl(
37 s3,
38 new GetObjectCommand({ Bucket: BUCKET, Key: key }),
39 { expiresIn: 86400 }
40 );
41
42 return Response.json({ key, url });
43}
44
45export async function GET(request: NextRequest) {
46 const { searchParams } = new URL(request.url);
47 const key = searchParams.get('key');
48
49 if (key) {
50 // Generate a fresh signed URL for an existing object
51 const url = await getSignedUrl(
52 s3,
53 new GetObjectCommand({ Bucket: BUCKET, Key: key }),
54 { expiresIn: 3600 }
55 );
56 return Response.json({ url });
57 }
58
59 // List all objects in the uploads prefix
60 const result = await s3.send(new ListObjectsV2Command({
61 Bucket: BUCKET,
62 Prefix: 'uploads/',
63 MaxKeys: 100,
64 }));
65
66 const files = (result.Contents ?? []).map((obj) => ({
67 key: obj.Key,
68 size: obj.Size,
69 lastModified: obj.LastModified,
70 }));
71
72 return Response.json({ files });
73}

Pro tip: The forcePathStyle: true option is required for Wasabi compatibility — without it, the SDK uses virtual-hosted style URLs that Wasabi doesn't support the same way as AWS.

Expected result: The file app/api/storage/route.ts exists with POST (upload) and GET (list/presign) handlers using the AWS SDK v3 S3Client pointed at Wasabi.

4

Add Wasabi Credentials to Vercel Environment Variables

With the API route written, add your Wasabi credentials to Vercel's environment variable system so they're available to the serverless function at runtime but never exposed in client-side JavaScript. Connect your V0 project to GitHub using V0's Git panel, then open your Vercel project at vercel.com/dashboard. Go to Settings → Environment Variables and add four variables. Set WASABI_ACCESS_KEY_ID to your access key ID. Set WASABI_SECRET_ACCESS_KEY to your secret access key. Set WASABI_BUCKET_NAME to your bucket name. Set WASABI_ENDPOINT to your bucket's endpoint hostname without the https:// prefix (e.g., s3.wasabisys.com for us-east-1 or s3.eu-central-1.wasabisys.com for EU). Optionally add WASABI_REGION matching the region code (e.g., us-east-1). For all variables, check all three environments: Production, Preview, and Development. Click Save after each. None of these variables should use the NEXT_PUBLIC_ prefix — they are server-only secrets and must never reach the browser. After adding variables, trigger a redeployment from the Vercel Dashboard. For local development, create a .env.local file with all five variables and add it to .gitignore. The same API route code reads from process.env in both environments — locally from .env.local, on Vercel from the Dashboard-configured variables.

.env.local
1# .env.local never commit to git
2WASABI_ACCESS_KEY_ID=your_access_key_id
3WASABI_SECRET_ACCESS_KEY=your_secret_access_key
4WASABI_BUCKET_NAME=your-bucket-name
5WASABI_ENDPOINT=s3.wasabisys.com
6WASABI_REGION=us-east-1

Pro tip: If your bucket is in eu-central-1, use s3.eu-central-1.wasabisys.com as the endpoint. Using the wrong regional endpoint causes 301 redirects and upload failures.

Expected result: All five Wasabi variables appear in Vercel Dashboard → Settings → Environment Variables. The .env.local file exists locally for development but is not committed to git.

5

Test Uploads and Deploy to Production

With the API route deployed and credentials configured, test the full upload and download flow. Push your project to GitHub from V0's Git panel to trigger a Vercel deployment. Once the deployment succeeds, open the production URL and test the file upload component. Select a file, watch the progress bar fill, and verify the returned URL. Click the URL to confirm the file is accessible from Wasabi's storage. To monitor the integration, check two places: the Vercel Dashboard → Functions tab shows API route invocations and any errors from the upload handler; the Wasabi console → Buckets → your bucket → Files shows the uploaded objects and their storage size. One critical V0-specific limitation to keep in mind: V0's browser preview sandbox cannot make outbound requests to Wasabi's API during in-browser testing. The upload functionality only works after deploying to Vercel. Design and test the UI layout in V0 preview with mock responses, then verify the actual Wasabi integration using the deployed Vercel URL. For apps that need users to upload very large files (over 100 MB), consider implementing multipart uploads using the S3 multipart upload API — it handles large files more reliably and shows granular progress. For complex storage architectures with multiple buckets, user-scoped prefixes, and file type validation, RapidDev can help design and implement a production-grade storage layer.

Pro tip: After a successful upload, verify in the Wasabi console that the file appears under your bucket's Files tab. This confirms the SDK is connecting to the right bucket and region.

Expected result: Files uploaded through the UI appear in the Wasabi console bucket. Presigned download URLs work and expire after the configured time. The Vercel Functions tab shows successful invocations of /api/storage.

Common use cases

User Profile Photo Upload

Build a profile settings page where users can upload and replace their profile photo. Files upload to Wasabi via a presigned URL, then the public URL is saved to your database. Because Wasabi has no egress fees, serving profile photos to millions of page views is cost-free beyond the storage charge.

V0 Prompt

Create a profile settings form with a circular avatar image that shows the current profile photo. Add a camera icon overlay that opens a file picker when clicked. After the user selects an image, show a preview and a Save button. On save, call POST /api/storage/upload with the file as FormData and update the avatar src with the returned URL.

Copy this prompt to try it in V0

Document Storage and Download Portal

Build a secure document portal where users can upload PDFs, spreadsheets, and images to their personal folder in a Wasabi bucket. Documents are only accessible to the owner via time-limited presigned download URLs, preventing unauthorized access to private files.

V0 Prompt

Build a file manager UI with a list of uploaded documents showing filename, file size, upload date, and a Download button. Add a drag-and-drop upload zone at the top. The list should fetch from GET /api/storage/files and each Download button should call GET /api/storage/download?key=filename to get a temporary signed URL.

Copy this prompt to try it in V0

Media Library for Content Creators

Create a media library dashboard where content creators upload images and short video clips. The library shows thumbnails in a grid, supports bulk uploads, and generates public CDN URLs for embedding in blog posts or social media. No egress fees make it economical to serve high-resolution media at scale.

V0 Prompt

Create a media library grid layout showing uploaded images as thumbnails in a masonry grid. Add a header with an Upload button that opens a multi-file picker. Each thumbnail has a hover overlay with a Copy URL button and a Delete button. Fetch the grid items from GET /api/storage/list and handle uploads via POST /api/storage/upload.

Copy this prompt to try it in V0

Troubleshooting

S3 client throws 'PermanentRedirect' or 301 errors on upload

Cause: The endpoint is set to the wrong regional URL. Wasabi routes requests to the correct region, but the SDK interprets the redirect as an error when using forcePathStyle.

Solution: Match the endpoint in your environment variable to the exact region where your bucket was created. Check the Wasabi console bucket list — the region column shows the correct endpoint prefix. For example, a bucket in ap-southeast-1 needs endpoint: s3.ap-southeast-1.wasabisys.com.

typescript
1// Correct endpoint format — match your bucket's region
2endpoint: `https://${process.env.WASABI_ENDPOINT}`, // e.g., 'https://s3.eu-central-1.wasabisys.com'

Upload succeeds but the presigned URL returns 'Access Denied'

Cause: Wasabi's default bucket policy blocks public access even for presigned URLs if the bucket has a restrictive ACL, or if the presigned URL was generated with the wrong region credentials.

Solution: Ensure the same credentials used to upload the file are used to generate the presigned URL. Verify the key parameter in the GetObjectCommand exactly matches the key used in PutObjectCommand — keys are case-sensitive. Check the Wasabi console to confirm the object exists under the exact key path.

File upload works locally but fails on Vercel with a 413 error

Cause: Vercel serverless functions have a 4.5 MB request body limit. Uploading files larger than this limit through the API route causes a 413 Payload Too Large error.

Solution: For files larger than 4.5 MB, use presigned upload URLs instead. Generate a presigned PutObject URL server-side and return it to the client, then have the client upload directly to Wasabi — bypassing Vercel entirely. This also reduces Vercel function execution costs.

typescript
1// Generate a presigned upload URL for direct client-to-Wasabi upload
2import { PutObjectCommand } from '@aws-sdk/client-s3';
3import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
4
5const uploadUrl = await getSignedUrl(
6 s3,
7 new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: fileType }),
8 { expiresIn: 300 } // 5 minutes to complete the upload
9);

AWS SDK v3 throws 'Credential is missing' despite env vars being set

Cause: The environment variables were added to Vercel after the last deployment. Vercel injects environment variables at build/deploy time, not dynamically — a new deployment is required for changes to take effect.

Solution: Go to Vercel Dashboard → Deployments and click Redeploy on the latest deployment. This triggers a new build that picks up the newly added environment variables. After redeployment, the credentials will be available to process.env in the API route.

Best practices

  • Always use server-side API routes to interact with Wasabi — never expose your access key ID or secret key in client-side JavaScript
  • Use forcePathStyle: true in the S3Client configuration — Wasabi requires path-style URL access, unlike AWS S3
  • Generate presigned URLs for files larger than 4.5 MB so users upload directly from the browser to Wasabi, bypassing Vercel's request body size limit
  • Prefix object keys with user IDs or session identifiers (e.g., users/user123/filename.pdf) to organize files by user and simplify access control
  • Set presigned URL expiry times based on the use case: short (5-15 minutes) for upload URLs, longer (1-24 hours) for download URLs shown in the UI
  • Validate file type and size server-side in your API route before accepting the upload — don't rely on the browser's file input accept attribute alone
  • Store the Wasabi object key in your database alongside the file metadata so you can regenerate download URLs at any time without storing the URL itself

Alternatives

Frequently asked questions

Can I use the AWS SDK v3 with Wasabi without any modifications?

Yes, with one small change. Wasabi is fully S3-compatible, so the AWS SDK v3 S3Client works without any code modifications. You just need to set two configuration options: endpoint pointing to your Wasabi regional URL (e.g., https://s3.wasabisys.com) and forcePathStyle: true. Everything else — commands, presigned URLs, multipart uploads — works identically to AWS S3.

Why is Wasabi cheaper than AWS S3?

Wasabi's pricing model is simpler: you pay a flat rate per GB stored (around $7/TB/month) with no charges for API calls, no egress fees for downloads, and no charges for delete operations. AWS S3 charges for storage, API requests, and data transfer out — the egress fees in particular add up quickly for media-heavy apps. Wasabi's all-inclusive pricing makes costs predictable and significantly lower for apps with heavy download traffic.

Does Wasabi have a CDN like CloudFront?

Wasabi does not have its own CDN, but you can put Cloudflare in front of your Wasabi bucket for free. Configure Cloudflare's CDN to proxy requests to your Wasabi bucket URL. This adds global edge caching for your files and further reduces latency without additional storage costs. For most V0 apps, Cloudflare's free plan is more than sufficient.

What happens to files I upload in V0's preview sandbox?

V0's browser preview sandbox cannot make outbound HTTP requests, so file uploads to Wasabi won't work in the preview. The upload component will appear to work visually (the UI state updates) if you mock the API response, but no actual files are sent to Wasabi. Real uploads only work after deploying to Vercel. This is a V0 platform limitation, not a Wasabi issue.

How do I handle large file uploads over 4.5 MB?

Vercel's serverless functions have a 4.5 MB request body limit. For larger files, generate a presigned PutObject URL in your API route and return it to the browser. The browser then uploads directly to Wasabi using the presigned URL — completely bypassing Vercel. The file never passes through your server, which also reduces bandwidth costs and upload latency.

Can multiple Vercel preview deployments share the same Wasabi bucket?

Yes. Since Wasabi credentials are set at the Vercel project level and applied to all environments, all preview deployments use the same bucket. To avoid preview uploads cluttering production data, use key prefixes to separate environments: uploads/production/ for production, uploads/preview/ for preview deployments. You can pass the Vercel environment (process.env.VERCEL_ENV) as a prefix in your upload key logic.

Does Wasabi support private buckets with public file serving?

Yes. You can keep the bucket private (blocking all public access) and serve files via presigned URLs — the approach shown in this tutorial. Alternatively, you can set a bucket policy in the Wasabi console that allows public read access to specific prefixes (like a public/ folder) while keeping other folders private. Public files can be served directly via their Wasabi URL without presigning.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.