Build a niche job board with V0 using Next.js, Supabase, Stripe, and shadcn/ui. Employers post and pay for featured listings via Stripe Checkout, applicants search and apply with resume uploads, and webhook-verified payments automatically promote listings — all in about 1-2 hours.
What you're building
Vertical job boards (remote-only, AI jobs, climate tech) outperform generic platforms because they attract focused candidates and employers willing to pay for visibility. A niche job board with paid featured listings is a proven revenue model that can be built in hours instead of months.
V0 makes this fast by generating the job listing UI, employer dashboard, and Stripe integration from prompts. Connect Supabase via the Connect panel for the database and auth, add Stripe via the Vercel Marketplace for instant payment setup, and you have a working job board on Vercel.
The architecture uses Next.js App Router with Server Components for SEO-friendly job pages, Stripe Checkout for payment processing, Supabase Storage for resume uploads, and webhook handlers for automated featured listing activation.
Final result
A fully functional job board with employer posting flows, applicant tracking, Stripe-powered featured listings, searchable job directory, and resume upload support.
Tech stack
Prerequisites
- A V0 account (Premium or higher recommended)
- A Supabase project (free tier works — connect via V0's Connect panel)
- A Stripe account (test mode works — connect via Vercel Marketplace)
- Your niche focus (e.g., remote jobs, AI roles, climate tech positions)
Build steps
Set up the project with Supabase and Stripe
Create a new V0 project. Use the Connect panel to add Supabase for the database and auth. Then add Stripe via the Vercel Marketplace — this auto-provisions your test API keys into the Vars tab.
1// Paste this prompt into V0's AI chat:2// Build a job board platform. Create a Supabase schema with these tables:3// 1. companies: id (uuid PK), name (text), logo_url (text), website (text), owner_id (uuid FK to auth.users)4// 2. jobs: id (uuid PK), company_id (uuid FK to companies), title (text), description (text), location (text), salary_min (int), salary_max (int), job_type (text), is_featured (boolean DEFAULT false), status (text DEFAULT 'draft'), published_at (timestamptz), expires_at (timestamptz), stripe_payment_id (text)5// 3. applications: id (uuid PK), job_id (uuid FK to jobs), applicant_name (text), email (text), resume_url (text), cover_letter (text), status (text DEFAULT 'new'), created_at (timestamptz)6// 4. saved_jobs: user_id (uuid FK to auth.users), job_id (uuid FK to jobs), PRIMARY KEY (user_id, job_id)7// Add RLS policies: anyone can read published jobs, only company owners can manage their jobs and view applications.Pro tip: Add Stripe via the Vercel Marketplace integration in V0 — it auto-provisions STRIPE_SECRET_KEY and NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY into your Vars tab, saving manual configuration.
Expected result: Supabase is connected with all tables created, Stripe keys are in the Vars tab, and RLS policies are active.
Build the searchable job listing page
Create the public-facing job board page with search, filters, and featured job highlighting. Use Server Components for SEO and a client component for the interactive filter sidebar.
1// Paste this prompt into V0's AI chat:2// Create a job listing page at app/jobs/page.tsx.3// Requirements:4// - Fetch published jobs from Supabase ordered by is_featured DESC, published_at DESC5// - Display as shadcn/ui Card grid: job title, company name + logo, location, salary range, job_type Badge6// - Featured jobs have a gold border and 'Featured' Badge7// - Add a Sheet sidebar for filters: job_type RadioGroup, salary range Slider, location Input8// - Add a search Input at the top that filters by title or company name9// - Each Card links to /jobs/[slug] for the full job detail10// - Show total results count and a 'Post a Job' Button linking to /employer/postExpected result: The jobs page displays a searchable, filterable grid of job listings with featured jobs highlighted at the top.
Create the job detail page with apply functionality
Build the individual job page showing full details and a quick-apply form. The apply form uploads resumes to Supabase Storage and creates an application record.
1// Paste this prompt into V0's AI chat:2// Create a job detail page at app/jobs/[slug]/page.tsx.3// Requirements:4// - Fetch the job by ID with company details from Supabase5// - Show job title, company logo + name + website link, location, salary range, job_type Badge, posted date6// - Show full job description in a formatted Card7// - Add an 'Apply Now' Button that opens a Dialog with:8// - applicant_name Input, email Input, cover_letter Textarea9// - Resume file upload (PDF only, max 5MB) to Supabase Storage10// - Submit Button with loading state11// - Add a 'Save Job' Button (heart icon) that toggles saved_jobs12// - Show related jobs from the same company or category at the bottom13// - Server Action for the application submission that inserts into applications tablePro tip: Use Supabase Storage with a private bucket for resumes. Generate signed URLs server-side when employers view applications — this keeps resume files secure and inaccessible to unauthorized users.
Expected result: The job detail page shows full listing information with a working apply Dialog that uploads resumes and creates applications.
Build the employer dashboard and posting flow
Create the employer-facing dashboard for managing listings and viewing applicants. Include a job posting form with a Stripe Checkout redirect for featured listings.
1// Paste this prompt into V0's AI chat:2// Create an employer dashboard at app/employer/dashboard/page.tsx.3// Requirements:4// - Fetch all jobs for the current user's company with application counts5// - Display in a shadcn/ui Table: title, status Badge (draft/active/expired), applications count, is_featured Badge, published_at6// - Each row has actions: Edit, View Applicants, Promote to Featured7// - 'View Applicants' opens a Sheet with application list: name, email, resume link, status Badge (new/reviewed/shortlisted/rejected), Select to change status8// - 'Promote to Featured' Button redirects to Stripe Checkout for $99 featured listing9// - Add a 'Post New Job' Button linking to /employer/post10// - Post page has a form: title, description (Textarea), location, salary_min, salary_max, job_type Select11// - Server Action for job creation that inserts with status 'draft'Expected result: The employer dashboard shows all listings with applicant counts, status management, and a featured listing purchase flow via Stripe.
Set up Stripe webhook for featured listing activation
Create the webhook handler that listens for Stripe checkout.session.completed events and automatically marks the corresponding job as featured. This is the critical piece that connects payment to listing promotion.
1import { NextRequest, NextResponse } from 'next/server'2import Stripe from 'stripe'3import { createClient } from '@supabase/supabase-js'45const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)6const supabase = createClient(7 process.env.SUPABASE_URL!,8 process.env.SUPABASE_SERVICE_ROLE_KEY!9)1011export async function POST(req: NextRequest) {12 const rawBody = await req.text()13 const sig = req.headers.get('stripe-signature')!1415 let event: Stripe.Event16 try {17 event = stripe.webhooks.constructEvent(18 rawBody,19 sig,20 process.env.STRIPE_WEBHOOK_SECRET!21 )22 } catch (err) {23 return NextResponse.json(24 { error: 'Webhook signature verification failed' },25 { status: 400 }26 )27 }2829 if (event.type === 'checkout.session.completed') {30 const session = event.data.object as Stripe.Checkout.Session31 const jobId = session.metadata?.job_id3233 if (jobId) {34 await supabase35 .from('jobs')36 .update({37 is_featured: true,38 stripe_payment_id: session.payment_intent as string,39 status: 'active',40 })41 .eq('id', jobId)42 }43 }4445 return NextResponse.json({ received: true })46}Expected result: After a successful Stripe Checkout payment, the webhook automatically marks the job as featured and active.
Deploy and register the Stripe webhook
Publish the job board to Vercel, then register the webhook URL in the Stripe Dashboard so payment events trigger the featured listing activation in production.
1// Paste this prompt into V0's AI chat:2// Create a Stripe Checkout session creator as a Server Action in app/employer/actions.ts.3// Requirements:4// - Export async function createFeaturedCheckout(jobId: string) with 'use server'5// - Create a Stripe Checkout session with:6// - mode: 'payment'7// - line_items: one item for 'Featured Job Listing' at $99.008// - metadata: { job_id: jobId }9// - success_url: '{origin}/employer/dashboard?featured=success'10// - cancel_url: '{origin}/employer/dashboard?featured=cancelled'11// - Return the session URL for redirect12// - Use redirect() from next/navigation to send user to StripePro tip: After publishing, register your webhook URL in Stripe Dashboard at Developers > Webhooks > Add endpoint. Use your Vercel URL: https://yourdomain.vercel.app/api/webhooks/stripe. Select the checkout.session.completed event.
Expected result: The job board is live on Vercel. Featured listing payments are processed through Stripe Checkout and automatically activate via the webhook.
Complete code
1import { NextRequest, NextResponse } from 'next/server'2import Stripe from 'stripe'3import { createClient } from '@supabase/supabase-js'45const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)6const supabase = createClient(7 process.env.SUPABASE_URL!,8 process.env.SUPABASE_SERVICE_ROLE_KEY!9)1011export async function POST(req: NextRequest) {12 const rawBody = await req.text()13 const sig = req.headers.get('stripe-signature')!1415 let event: Stripe.Event16 try {17 event = stripe.webhooks.constructEvent(18 rawBody,19 sig,20 process.env.STRIPE_WEBHOOK_SECRET!21 )22 } catch (err) {23 console.error('Webhook verification failed:', err)24 return NextResponse.json(25 { error: 'Invalid signature' },26 { status: 400 }27 )28 }2930 if (event.type === 'checkout.session.completed') {31 const session = event.data.object as Stripe.Checkout.Session32 const jobId = session.metadata?.job_id3334 if (jobId) {35 const { error } = await supabase36 .from('jobs')37 .update({38 is_featured: true,39 stripe_payment_id: session.payment_intent as string,40 status: 'active',41 published_at: new Date().toISOString(),42 expires_at: new Date(43 Date.now() + 30 * 24 * 60 * 60 * 100044 ).toISOString(),45 })46 .eq('id', jobId)4748 if (error) console.error('Failed to update job:', error)49 }50 }5152 return NextResponse.json({ received: true })53}Customization ideas
Email notifications for new applications
Send employers an email via Resend whenever a new application is submitted, including the applicant's name and a link to their dashboard.
Company profiles with public pages
Add a /companies/[slug] page showing company info, open positions, and reviews — giving employers a branded presence on your job board.
Subscription-based employer plans
Replace one-time featured listing payments with monthly subscription plans (Basic: 5 postings, Pro: unlimited + featured) using Stripe Billing.
Job alerts and saved searches
Let job seekers save search criteria and receive email notifications when new matching jobs are posted, using a Vercel Cron Job for daily digest emails.
Common pitfalls
Pitfall: Using request.json() instead of request.text() in the Stripe webhook handler
How to avoid: Always use request.text() to get the raw body, then pass it directly to stripe.webhooks.constructEvent(rawBody, sig, webhookSecret).
Pitfall: Putting STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET with a NEXT_PUBLIC_ prefix
How to avoid: Store STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in V0's Vars tab without any prefix. Only NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY should have the prefix.
Pitfall: Not validating the Stripe webhook signature before processing events
How to avoid: Always verify the webhook signature using stripe.webhooks.constructEvent() with the raw body and your STRIPE_WEBHOOK_SECRET before processing any event.
Pitfall: Calculating scores or filtering job results client-side
How to avoid: Use Supabase server-side queries with .ilike() for search, .eq() for filters, and .range() for pagination. The Server Component fetches only the needed results.
Best practices
- Use Server Components for job listing and detail pages to get SEO benefits — search engines see fully rendered HTML with job titles and descriptions
- Store all Stripe secret keys in V0's Vars tab without NEXT_PUBLIC_ prefix — only the publishable key needs the prefix
- Use Supabase Storage private buckets for resume uploads and generate signed URLs when employers view applications
- Add a unique constraint on job slugs for clean, SEO-friendly URLs like /jobs/senior-react-developer-acme
- Use V0's Design Mode (Option+D) to adjust Card layouts, Badge colors, and filter sidebar width without spending credits
- Set job expiration dates (expires_at) and filter them out in queries so stale listings do not appear to candidates
- Use Stripe metadata to pass the job_id through the Checkout session so the webhook knows which job to update
- Implement cursor-based pagination for the job listing page to handle thousands of listings efficiently
AI prompts to try
Copy these prompts to build this project faster.
I'm building a job board with Next.js App Router, Supabase, and Stripe. I need help with the Stripe Checkout flow for featured listings. When an employer clicks 'Promote to Featured', I need to create a Checkout session with the job_id in metadata, redirect them to Stripe, and then handle the checkout.session.completed webhook to update the job's is_featured flag. Please write the Server Action for creating the session and the webhook handler at app/api/webhooks/stripe/route.ts using request.text() for signature verification.
Create a complete employer job posting workflow: a form at app/employer/post/page.tsx with title Input, description Textarea, location Input, salary range (two number Inputs), job_type Select (Full-time, Part-time, Contract, Remote). On submit, use a Server Action to insert into the jobs table with status 'draft'. Add a preview step before submission showing the job as it will appear to candidates. Include validation with zod schema.
Frequently asked questions
How does the featured listing payment work?
When an employer clicks Promote to Featured, a Server Action creates a Stripe Checkout session with the job_id in metadata and redirects them to Stripe's hosted payment page. After payment, Stripe sends a webhook to your app, which verifies the signature and automatically updates the job to is_featured = true.
Can I use the free V0 plan for this project?
You can start on the free tier, but Premium ($20/month) is recommended. The job board requires multiple pages (listings, detail, employer dashboard, webhook handler) and the free tier's limited credits may not cover the full build.
How are resumes stored securely?
Resumes are uploaded to a Supabase Storage private bucket. They are not publicly accessible. When employers view applications, your server generates a signed URL that expires after one hour, ensuring only authorized users can download resume files.
Can I add email notifications when someone applies?
Yes. Add a Resend API call in the application submission Server Action. After inserting the application record, send an email to the employer with the applicant's name and a link to their dashboard. Store RESEND_API_KEY in the Vars tab without a NEXT_PUBLIC_ prefix.
How do I deploy the job board?
Click Share in V0, then Publish to Production. After deploying, register your Stripe webhook URL in the Stripe Dashboard at Developers > Webhooks. Use your production URL: https://yourdomain.vercel.app/api/webhooks/stripe and select checkout.session.completed.
Can I make job listing pages SEO-friendly?
Yes. Job detail pages use Server Components by default, so search engines see fully rendered HTML. Add metadata with generateMetadata() for dynamic page titles and descriptions. Use ISR with revalidate for fast cached pages that update when jobs change.
Can RapidDev help build a custom job board?
Yes. RapidDev has built over 600 apps including job boards and recruitment platforms with advanced features like applicant scoring, interview scheduling, and employer analytics. Book a free consultation to discuss your niche and get a production-ready platform.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation