I just finished building and launching AI Talent Hub — a complete,
production-ready AI & Machine Learning job board. Here's exactly
how I built it, what I learned, and the challenges I faced.
🚀 Live Demo
https://ai-talent-hub-delta.vercel.app
Why I Built This
The AI job market is exploding. Companies are hiring ML engineers,
data scientists, NLP researchers and AI product managers at record
rates. But there was no dedicated, clean job board focused purely
on AI roles.
I decided to build one — and package it as a template for other
developers to launch their own niche job boards.
What I Built
A full-stack job board with:
- 🔐 Employer authentication (signup/login)
- 📋 Job posting with Stripe payments
- 💰 3 pricing tiers (Standard, Featured, Premium)
- 🗄️ Supabase database for jobs and employers
- 📊 Employer dashboard to manage listings
- 🔍 Job search and filters
- 📱 Mobile responsive design
- ⚡ Deployed on Vercel
Tech Stack
| Technology | Purpose |
|---|---|
| Next.js 14 | Frontend + API routes |
| TypeScript | Type safety |
| Tailwind CSS | Styling |
| Supabase | Database + Auth |
| Stripe | Payments |
| Framer Motion | Animations |
| Vercel | Deployment |
Architecture Overview
The app uses Next.js 14 App Router with a mix of server and
client components.
Server Components handle:
- Fetching job listings from Supabase
- Dashboard data (employer's jobs)
- Static pages (about, pricing)
Client Components handle:
- Authentication forms
- Job search filters
- Navbar with auth state
The Payment Flow
This was the most interesting part to build. Here's how it works:
- Employer fills out job posting form
- Job data is stored in localStorage temporarily
- User is redirected to Stripe Checkout
- After payment, Stripe fires a webhook
- Webhook receives job data from Stripe metadata
- Job is inserted into Supabase database
- Job appears on the board instantly
// Stripe webhook handler
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const { planId, employerEmail, jobData } = session.metadata;
const parsedJobData = JSON.parse(jobData);
await supabase.from('jobs').insert({
title: parsedJobData.title,
company: parsedJobData.company,
is_featured: planId === 'featured' || planId === 'premium',
employer_email: employerEmail,
posted_at: new Date().toISOString(),
});
}
The Hardest Challenge — Supabase Auth on Vercel
This took me the longest to figure out. The issue was that
@supabase/ssr uses cookies() from Next.js, which behaves
differently in server components vs client components.
The fix was using createBrowserClient for client-side auth
and createServerClient with proper cookie handling for
server-side auth.
// Browser client (client components)
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
// Server client (server components)
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export function createClient() {
const cookieStore = cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
}
Lessons Learned
1. Next.js 14 App Router is powerful but tricky
The distinction between server and client components takes
getting used to. Any component using browser APIs must be
marked 'use client'.
2. Stripe webhooks need careful setup
Make sure your webhook secret is updated every time you
change the endpoint URL. Test with Stripe CLI locally.
3. Supabase RLS policies matter
Don't forget to add Row Level Security policies for every
table. Without them, inserts will silently fail.
4. Deploy early, debug on production
Some issues only appear in production. Deploy to Vercel
early and test there — don't wait until the end.
Database Schema
-- Employers table
create table employers (
id uuid primary key default gen_random_uuid(),
email text unique not null,
company_name text not null,
created_at timestamp default now()
);
-- Jobs table
create table jobs (
id uuid primary key default gen_random_uuid(),
title text not null,
company text not null,
location text not null,
type text not null,
category text not null,
description text not null,
salary text,
is_featured boolean default false,
employer_email text,
posted_at timestamp default now()
);
Results
The template is now live and available for other developers
to use to launch their own niche job boards.
🔗 Live Demo: https://ai-talent-hub-delta.vercel.app
💰 Get the source code ($39): kvinodh.gumroad.com/l/ai-job-board-nextjs-supabase-stripe
What's Next
- Add job application functionality
- Email notifications for employers
- Analytics dashboard
- Multiple job board niches (crypto, web3, remote only)
Conclusion
Building this taught me a ton about Next.js 14, Supabase SSR
auth, and Stripe webhook flows. The biggest takeaway is to
test on production early — many issues only show up there.
If you're thinking about building a niche job board, I hope
this article helps! Feel free to ask questions in the comments.
Built with ❤️ by Vinodh Kavishka — Founder of DK Solutions
Top comments (0)