{"slug": "i-built-a-full-stack-b2b-marketplace-in-3-days-with-v0-next-js-16-and-vercel-s-s", "title": "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", "summary": "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.", "body_md": "*The honest account: what worked, what broke, and what I'd do differently.*\n\nThree 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.\n\nThis is not a \"look how smooth AI coding is\" post. It's the honest version.\n\nLet 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.\n\nHere's the prompt structure that worked:\n\n```\nBuild [app name]. DO NOT provision any databases.\n\nWHAT IT DOES: [one paragraph, clear]\nBUILD THESE PAGES: [numbered list, each with specific UI requirements]\nDESIGN DIRECTION: [specific hex colors, font choices, component library]\nDO NOT: [explicit list of what not to do]\n```\n\nThe \"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.\n\nThe result after one well-structured prompt:\n\nGenuinely 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.\n\n**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).\n\n**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.\n\nThis one cost an hour.\n\nIn Next.js 15+, route params are now a Promise. If you have a route like `app/api/drops/[dropId]/route.ts`\n\n, the params signature changed:\n\n```\n// Next.js 14 (old)\nexport async function GET(\n  req: NextRequest,\n  { params }: { params: { dropId: string } }\n) {\n  const { dropId } = params; // fine\n}\n\n// Next.js 16 (current)\nexport async function GET(\n  req: NextRequest,\n  { params }: { params: Promise<{ dropId: string }> }\n) {\n  const { dropId } = await params; // required\n}\n```\n\nThe error message when you get this wrong is:\n\n```\nType 'typeof import(\"...route\")' does not satisfy the constraint \n'RouteHandlerConfig<\"/api/drops/[dropId]\">'.\n```\n\nThis error is not particularly clear about what's actually wrong. The fix is `await params`\n\neverywhere you destructure route segment params. I had three routes that needed fixing.\n\nThe Vercel AWS Marketplace integration is genuinely slick. You click through a wizard in the Vercel dashboard and it:\n\nThe 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.\n\nThe code is clean:\n\n``` js\nimport { awsCredentialsProvider } from \"@vercel/functions/oidc\";\nimport { Signer } from \"@aws-sdk/rds-signer\";\n\nconst signer = new Signer({\n  hostname: process.env.PGHOST!,\n  port: Number(process.env.PGPORT ?? 5432),\n  username: process.env.PGUSER!,\n  region: process.env.AWS_REGION!,\n  credentials: awsCredentialsProvider({\n    roleArn: process.env.AWS_ROLE_ARN!,\n    clientConfig: { region: process.env.AWS_REGION },\n  }),\n});\n\nconst pool = new Pool({\n  host: process.env.PGHOST,\n  password: () => signer.getAuthToken(), // fresh token per connection\n  ssl: { rejectUnauthorized: false },\n});\n```\n\n**The caveats:**\n\n**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`\n\n, `AWS_ROLE_ARN`\n\n). DynamoDB, when installed second, gets a prefix that depends on what you name it during setup — ours ended up as `DYB_`\n\n(`DYB_AWS_ROLE_ARN`\n\n, `DYB_DYNAMODB_TABLE_NAME`\n\n). This isn't documented prominently. Check your actual `.env.local`\n\nafter running `vercel env pull`\n\n.\n\n**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`\n\nreturns only `VERCEL_OIDC_TOKEN`\n\n, this is why — go back to the integration settings and check Development.\n\n**3. Local OIDC auth depends on the IAM role trust policy.** The `VERCEL_OIDC_TOKEN`\n\nthat `vercel env pull`\n\ninjects 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`\n\nfailed locally even though production worked fine. The pragmatic fix: add `dotenv`\n\nto `aurora.ts`\n\nso the module works in both environments, and test primarily against the deployed URL.\n\n**4. The dotenv import must precede all AWS imports.** In ES modules, static imports are hoisted and executed before runtime code — including\n\n`config()`\n\n. This means:\n\n``` js\n// WRONG: AWS SDK is imported before .env.local loads\nimport { awsCredentialsProvider } from \"@vercel/functions/oidc\";\nimport { config } from \"dotenv\";\nconfig({ path: \".env.local\" });\njs\n// CORRECT: dotenv first\nimport { config } from \"dotenv\";\nconfig({ path: \".env.local\" });\nimport { awsCredentialsProvider } from \"@vercel/functions/oidc\";\n```\n\nThis caused a `Value null at 'roleArn'`\n\nerror that took longer than it should have to diagnose.\n\nWith a 3-day deadline, there was no time to set up Prisma or Drizzle. The approach: a single `/api/migrate`\n\nroute protected by a `MIGRATE_SECRET`\n\nenv var. Hit it once after deploy and all tables are created:\n\n```\nexport async function POST(req: NextRequest) {\n  const { secret } = await req.json();\n  if (secret !== process.env.MIGRATE_SECRET) {\n    return NextResponse.json({ error: \"unauthorized\" }, { status: 401 });\n  }\n  await query(`CREATE TABLE IF NOT EXISTS brands (...)`);\n  await query(`CREATE TABLE IF NOT EXISTS lots (...)`);\n  // etc.\n  return NextResponse.json({ ok: true });\n}\n```\n\nThis 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.\n\nDutch-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.\n\nThe fix: a `/api/ensure-drops`\n\nendpoint that checks how many live, unexpired drops exist and reseeds automatically if the count falls below 3:\n\n``` js\nexport async function GET() {\n  const countResult = await query<{ count: string }>(\n    `SELECT COUNT(*) AS count FROM drops\n     WHERE status = 'live' AND ends_at > NOW()`\n  );\n  const liveCount = parseInt(countResult[0].count, 10);\n  if (liveCount >= 3) {\n    return NextResponse.json({ seeded: false, liveDrops: liveCount });\n  }\n  await seedAllDrops(); // creates 6 new drops with fresh timers\n  return NextResponse.json({ seeded: true, liveDrops: 6 });\n}\n```\n\nThe frontend calls this silently on every page load via `useEffect`\n\n. A Vercel cron job calls it daily as a backup. Judges always see a live marketplace regardless of when they visit.\n\nThe cron configuration in `vercel.json`\n\n(note: Hobby plan limits to daily frequency):\n\n```\n{\n  \"crons\": [\n    {\n      \"path\": \"/api/ensure-drops\",\n      \"schedule\": \"0 0 * * *\"\n    }\n  ]\n}\n```\n\nAfter 3 days:\n\nThe 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.\n\nThe 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`\n\nand confirmed the variables immediately after installation, the rest of the build would have been smoother.\n\n**Use V0 Max for complex multi-page builds.** The quality difference from Mini is significant. For simple components, Mini is fine.\n\n**Check vercel env pull output immediately after installing any integration.** Note the exact variable names — they may not be what you expect.\n\n**Put dotenv at the top of any module that initialises AWS clients.** Don't rely on the calling code to load it first.\n\n**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.\n\n** await params in all App Router route handlers.** It's a breaking change in Next.js 15+. Grep for\n\n`{ params }:`\n\nin your route files and add `await`\n\nbefore 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.*", "url": "https://wpnews.pro/news/i-built-a-full-stack-b2b-marketplace-in-3-days-with-v0-next-js-16-and-vercel-s-s", "canonical_source": "https://dev.to/arek_h/i-built-a-full-stack-b2b-marketplace-in-3-days-with-v0-nextjs-16-and-vercels-aws-integration--4ma4", "published_at": "2026-06-27 08:20:08+00:00", "updated_at": "2026-06-27 09:04:06.100945+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "ai-tools", "ai-products", "ai-infrastructure"], "entities": ["V0", "Next.js", "Vercel", "AWS", "Aurora PostgreSQL", "DynamoDB", "OIDC", "AWS Marketplace"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-full-stack-b2b-marketplace-in-3-days-with-v0-next-js-16-and-vercel-s-s", "markdown": "https://wpnews.pro/news/i-built-a-full-stack-b2b-marketplace-in-3-days-with-v0-next-js-16-and-vercel-s-s.md", "text": "https://wpnews.pro/news/i-built-a-full-stack-b2b-marketplace-in-3-days-with-v0-next-js-16-and-vercel-s-s.txt", "jsonld": "https://wpnews.pro/news/i-built-a-full-stack-b2b-marketplace-in-3-days-with-v0-next-js-16-and-vercel-s-s.jsonld"}}