I Built a Full-Stack B2B Marketplace in 3 Days With V0, Next.js 16, and Vercel's AWS Integration — Here's What Actually Happened A developer built a full-stack B2B Dutch-auction marketplace in three days using V0, Next.js 16, and Vercel's AWS integration. The project revealed that V0 excels at generating multi-page UI from detailed prompts but struggles with data wiring, requiring significant back-and-forth. The developer also encountered breaking changes in Next.js 16's route params and highlighted the benefits and caveats of Vercel's AWS Marketplace integration for credential-free database authentication. The honest account: what worked, what broke, and what I'd do differently. Three days. One B2B Dutch-auction marketplace. Here's the realistic version of what building with V0, Next.js 16 App Router, Aurora PostgreSQL, and DynamoDB actually looks like when you're going fast. This is not a "look how smooth AI coding is" post. It's the honest version. Let me start with the honest assessment. V0 is genuinely impressive at one specific thing: generating multi-page UI from a detailed prompt and producing something that's 70–80% of the way to production-quality on the first pass. Here's the prompt structure that worked: Build app name . DO NOT provision any databases. WHAT IT DOES: one paragraph, clear BUILD THESE PAGES: numbered list, each with specific UI requirements DESIGN DIRECTION: specific hex colors, font choices, component library DO NOT: explicit list of what not to do The "DO NOT" section is not optional. V0 will try to add authentication, spin up a database, create server actions, and add 47 npm packages you didn't ask for if you don't explicitly forbid it. Write the constraint list first. The result after one well-structured prompt: Genuinely good output. The thing that V0 gets right is visual design decisions — the layout hierarchy, the card structure, the urgency indicators. These are the things that take hours to hand-craft and minutes to generate. What V0 doesn't handle well: data wiring. V0's proxy routes work for simple cases but any non-trivial API shape requires going back and being very explicit. Prompt 2 wiring to real data required more back-and-forth than Prompt 1 building the UI . The annotation feature is underused. In V0 Max, you can click any element in the preview and leave an annotation. "Make this number bigger." "This progress bar should be red when below 20%." This is dramatically faster than re-prompting the whole component. Use it. This one cost an hour. In Next.js 15+, route params are now a Promise. If you have a route like app/api/drops/ dropId /route.ts , the params signature changed: // Next.js 14 old export async function GET req: NextRequest, { params }: { params: { dropId: string } } { const { dropId } = params; // fine } // Next.js 16 current export async function GET req: NextRequest, { params }: { params: Promise<{ dropId: string } } { const { dropId } = await params; // required } The error message when you get this wrong is: Type 'typeof import "...route" ' does not satisfy the constraint 'RouteHandlerConfig<"/api/drops/ dropId " '. This error is not particularly clear about what's actually wrong. The fix is await params everywhere you destructure route segment params. I had three routes that needed fixing. The Vercel AWS Marketplace integration is genuinely slick. You click through a wizard in the Vercel dashboard and it: The result: your serverless functions authenticate to AWS databases using short-lived OIDC tokens with zero stored credentials. No Secrets Manager, no password rotation, no long-lived access keys. The code is clean: js import { awsCredentialsProvider } from "@vercel/functions/oidc"; import { Signer } from "@aws-sdk/rds-signer"; const signer = new Signer { hostname: process.env.PGHOST , port: Number process.env.PGPORT ?? 5432 , username: process.env.PGUSER , region: process.env.AWS REGION , credentials: awsCredentialsProvider { roleArn: process.env.AWS ROLE ARN , clientConfig: { region: process.env.AWS REGION }, } , } ; const pool = new Pool { host: process.env.PGHOST, password: = signer.getAuthToken , // fresh token per connection ssl: { rejectUnauthorized: false }, } ; The caveats: 1. Environment variable prefix chaos. When you install two AWS integrations we installed both Aurora and DynamoDB , the env var prefixes differ. Aurora uses no prefix PGHOST , AWS ROLE ARN . DynamoDB, when installed second, gets a prefix that depends on what you name it during setup — ours ended up as DYB DYB AWS ROLE ARN , DYB DYNAMODB TABLE NAME . This isn't documented prominently. Check your actual .env.local after running vercel env pull . 2. Development environment checkbox. When connecting integrations to your project, there's a checkbox for which environments Production, Preview, Development get the env vars. It's easy to miss Development. If vercel env pull returns only VERCEL OIDC TOKEN , this is why — go back to the integration settings and check Development. 3. Local OIDC auth depends on the IAM role trust policy. The VERCEL OIDC TOKEN that vercel env pull injects locally is a development token from a different issuer than the production token. Whether it works locally depends on whether the IAM role's trust policy includes the development issuer. Ours didn't initially, so npm run db:test failed locally even though production worked fine. The pragmatic fix: add dotenv to aurora.ts so the module works in both environments, and test primarily against the deployed URL. 4. The dotenv import must precede all AWS imports. In ES modules, static imports are hoisted and executed before runtime code — including config . This means: js // WRONG: AWS SDK is imported before .env.local loads import { awsCredentialsProvider } from "@vercel/functions/oidc"; import { config } from "dotenv"; config { path: ".env.local" } ; js // CORRECT: dotenv first import { config } from "dotenv"; config { path: ".env.local" } ; import { awsCredentialsProvider } from "@vercel/functions/oidc"; This caused a Value null at 'roleArn' error that took longer than it should have to diagnose. With a 3-day deadline, there was no time to set up Prisma or Drizzle. The approach: a single /api/migrate route protected by a MIGRATE SECRET env var. Hit it once after deploy and all tables are created: export async function POST req: NextRequest { const { secret } = await req.json ; if secret == process.env.MIGRATE SECRET { return NextResponse.json { error: "unauthorized" }, { status: 401 } ; } await query CREATE TABLE IF NOT EXISTS brands ... ; await query CREATE TABLE IF NOT EXISTS lots ... ; // etc. return NextResponse.json { ok: true } ; } This is not a production pattern. It's a hackathon pattern that works fine when your schema is stable and you control when migrations run. For a demo with a 3-day deadline, it's the right call. Dutch-auction drops expire. A 3-hour drop is great for live demo urgency but terrible for a submitted hackathon project that judges might visit at 11pm three days later. The fix: a /api/ensure-drops endpoint that checks how many live, unexpired drops exist and reseeds automatically if the count falls below 3: js export async function GET { const countResult = await query<{ count: string } SELECT COUNT AS count FROM drops WHERE status = 'live' AND ends at NOW ; const liveCount = parseInt countResult 0 .count, 10 ; if liveCount = 3 { return NextResponse.json { seeded: false, liveDrops: liveCount } ; } await seedAllDrops ; // creates 6 new drops with fresh timers return NextResponse.json { seeded: true, liveDrops: 6 } ; } The frontend calls this silently on every page load via useEffect . A Vercel cron job calls it daily as a backup. Judges always see a live marketplace regardless of when they visit. The cron configuration in vercel.json note: Hobby plan limits to daily frequency : { "crons": { "path": "/api/ensure-drops", "schedule": "0 0 " } } After 3 days: The thing I'm most satisfied with is the claim handler. It does exactly one thing — decrement inventory atomically, record the transaction relationally — and it does it correctly under concurrent load. That's the core of the product. The thing I'd do differently: start the database connectivity earlier. The OIDC auth quirks and env var prefix confusion cost nearly a day. If I'd run vercel env pull and confirmed the variables immediately after installation, the rest of the build would have been smoother. Use V0 Max for complex multi-page builds. The quality difference from Mini is significant. For simple components, Mini is fine. Check vercel env pull output immediately after installing any integration. Note the exact variable names — they may not be what you expect. Put dotenv at the top of any module that initialises AWS clients. Don't rely on the calling code to load it first. Test your API routes against the deployed Vercel URL, not locally, when using OIDC auth. Local OIDC works if the role trust policy includes the dev issuer; production OIDC always works. await params in all App Router route handlers. It's a breaking change in Next.js 15+. Grep for { params }: in your route files and add await before you deploy. ClearLot was built for the H0: Hack the Zero Stack hackathon. Stack: Next.js 16, TypeScript, Tailwind, shadcn/ui, V0, Aurora PostgreSQL, DynamoDB, Vercel. Live at clearlot.vercel.app.