Build a behavioral content personalization engine with V0 using Next.js, Supabase with pgvector, and A/B testing. You'll create a rule-based system that segments users by behavior, serves tailored content variants, and tracks experiment results — all in about 2-4 hours from your browser.
What you're building
Personalization is the difference between a product that feels generic and one that feels made for each user. Whether you are recommending content, adjusting UI layouts, or running A/B tests on feature variations, a personalization engine lets you serve the right experience to the right person at the right time.
V0 accelerates this build by generating the Next.js code for event tracking endpoints, rule evaluation logic, and admin dashboards from natural-language prompts. Use the Connect panel to provision Supabase with pgvector enabled, then queue up prompts for each subsystem while V0 generates them in sequence.
The architecture uses Next.js App Router with an Edge-optimized API route for rule evaluation, Server Actions for rule management, Supabase for storing events and segments, pgvector for embedding-based user similarity, and Recharts for A/B test result visualization.
Final result
A complete personalization platform with behavioral event tracking, segment-based rule evaluation, A/B testing with statistical significance indicators, and an admin dashboard for managing personalization rules.
Tech stack
Prerequisites
- A V0 account (Premium or higher recommended for prompt queuing)
- A Supabase project with pgvector extension enabled (free tier works)
- Basic understanding of user segmentation concepts
- A product or website where you want to personalize content
Build steps
Set up the project and Supabase schema with pgvector
Open V0 and create a new project. Use the Connect panel to add Supabase, then prompt V0 to generate the full schema including pgvector for user embeddings. This foundation handles events, rules, segments, and A/B tests.
1// Paste this prompt into V0's AI chat:2// Build a personalization engine. Create a Supabase schema with these tables:3// 1. user_profiles: id (uuid PK), user_id (uuid FK unique), preferences (jsonb), segments (text[]), created_at (timestamptz), updated_at (timestamptz)4// 2. events: id (uuid PK), user_id (uuid FK), event_type (text), event_data (jsonb), page_url (text), created_at (timestamptz)5// 3. personalization_rules: id (uuid PK), name (text), segment_criteria (jsonb), content_variant (jsonb), priority (integer), is_active (boolean), created_at (timestamptz)6// 4. ab_tests: id (uuid PK), rule_id (uuid FK), variant_key (text), impressions (integer default 0), conversions (integer default 0), created_at (timestamptz)7// Enable pgvector extension and add: user_embeddings (id uuid PK, user_id uuid FK, embedding vector(1536), updated_at timestamptz)8// Add RLS policies so only authenticated users can read their own profiles.9// Generate SQL migration and TypeScript types.Pro tip: Use V0's prompt queuing — queue the schema prompt first, then immediately queue the event tracker and rule engine prompts while the first one generates.
Expected result: Supabase is connected via the Connect panel with all five tables created, pgvector extension enabled, and TypeScript types generated.
Build the behavioral event tracking API
Create an API route that ingests user behavioral events. This endpoint is called from your frontend on every meaningful action — page views, clicks, purchases — and stores them in the events table for later segment computation.
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.SUPABASE_URL!,6 process.env.SUPABASE_SERVICE_ROLE_KEY!7)89export async function POST(req: NextRequest) {10 const { user_id, event_type, event_data, page_url } = await req.json()1112 if (!user_id || !event_type) {13 return NextResponse.json(14 { error: 'user_id and event_type are required' },15 { status: 400 }16 )17 }1819 const { error } = await supabase.from('events').insert({20 user_id,21 event_type,22 event_data: event_data ?? {},23 page_url: page_url ?? null,24 })2526 if (error) {27 return NextResponse.json({ error: error.message }, { status: 500 })28 }2930 return NextResponse.json({ success: true })31}Expected result: POST requests to /api/events/track insert behavioral events into Supabase. Each event includes the user ID, event type, custom data, and page URL.
Create the personalization rule evaluation endpoint
Build the core engine that evaluates personalization rules against a user's segments. This endpoint checks which rules match the current user and returns the highest-priority content variant. Use Edge Runtime for minimal latency.
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'34export const runtime = 'edge'56export async function GET(req: NextRequest) {7 const supabase = createClient(8 process.env.SUPABASE_URL!,9 process.env.SUPABASE_SERVICE_ROLE_KEY!10 )1112 const userId = req.nextUrl.searchParams.get('user_id')13 if (!userId) {14 return NextResponse.json({ error: 'user_id required' }, { status: 400 })15 }1617 const { data: profile } = await supabase18 .from('user_profiles')19 .select('segments, preferences')20 .eq('user_id', userId)21 .single()2223 if (!profile) {24 return NextResponse.json({ variant: null, fallback: true })25 }2627 const { data: rules } = await supabase28 .from('personalization_rules')29 .select('id, name, segment_criteria, content_variant, priority')30 .eq('is_active', true)31 .order('priority', { ascending: false })3233 const matchedRule = rules?.find((rule) => {34 const criteria = rule.segment_criteria as { segments?: string[] }35 return criteria.segments?.some((s: string) => profile.segments?.includes(s))36 })3738 if (matchedRule) {39 await supabase.rpc('increment_impressions', { rule_id: matchedRule.id })40 }4142 return NextResponse.json({43 variant: matchedRule?.content_variant ?? null,44 rule_name: matchedRule?.name ?? null,45 fallback: !matchedRule,46 })47}Pro tip: Edge Runtime runs your evaluation endpoint on Vercel's global edge network, cutting latency to under 50ms for most users worldwide.
Expected result: GET /api/personalization/evaluate?user_id=xxx returns the highest-priority matching content variant for that user, or a fallback flag if no rules match.
Build the rule management admin dashboard
Prompt V0 to generate an admin interface where you create and manage personalization rules. Each rule targets specific user segments and defines a content variant to serve. Include priority ordering and active/inactive toggles.
1// Paste this prompt into V0's AI chat:2// Build a personalization rule management dashboard at app/dashboard/personalization/page.tsx.3// Requirements:4// - Fetch all personalization_rules and display in a shadcn/ui Table5// - Each row shows: name, target segments (as Badges), priority (number), is_active (Switch toggle), created_at6// - Add a "Create Rule" Button that opens a Sheet from the right side7// - Sheet contains: Input for rule name, Select for target segments (multi-select), JSON editor for content_variant, Slider for priority (1-100)8// - Switch toggles should immediately update is_active via Server Action9// - Add Tabs at the top: "Rules" and "A/B Tests"10// - A/B Tests tab shows a Table with variant_key, impressions, conversions, conversion rate, and a Badge showing statistical significance (green if >95% confidence)11// - Use Recharts BarChart to visualize conversion rates across variants12// - Use Server Components for data fetching, 'use client' only for interactive elementsExpected result: A dashboard with Tabs for Rules and A/B Tests. Rules are displayed in a Table with Switch toggles. A Sheet opens for creating new rules with segment targeting and priority.
Implement segment computation with database functions
Create a Supabase database function that recomputes user segments based on their event history. This runs periodically via pg_cron and updates the user_profiles.segments array, which the evaluation endpoint reads for fast rule matching.
1// Paste this prompt into V0's AI chat:2// Create a Supabase SQL migration for segment computation:3// 1. A database function compute_user_segments(target_user_id uuid) that:4// - Counts events by type for the user in the last 30 days5// - Assigns segments based on thresholds: 'power_user' (50+ events), 'active' (10-49 events), 'new_user' (<10 events)6// - Checks event_data for purchase events to assign 'buyer' segment7// - Updates user_profiles.segments array and updated_at timestamp8// 2. A wrapper function recompute_all_segments() that loops through all users with recent events9// 3. A pg_cron job that runs recompute_all_segments() every hour10// 4. An RPC function increment_impressions(rule_id uuid) that atomically increments ab_tests.impressions11// Also create an API route at app/api/segments/recompute/route.ts that manually triggers recomputation for testing.Pro tip: Use Supavisor connection pooling (the pooled connection string from Supabase) in your Vars tab to prevent connection exhaustion when serverless functions compute segments concurrently.
Expected result: Segments are automatically recomputed every hour. Users are tagged with segments like power_user, active, new_user, and buyer based on their behavioral data.
Add the client-side event tracking hook
Create a React hook that your frontend components use to track events. This hook sends events to the tracking API and handles batching for performance. It uses useEffect to avoid SSR issues since it needs browser APIs.
1'use client'23import { useCallback, useRef, useEffect } from 'react'45interface TrackEvent {6 event_type: string7 event_data?: Record<string, unknown>8 page_url?: string9}1011export function useEventTracker(userId: string | null) {12 const queue = useRef<TrackEvent[]>([])1314 const flush = useCallback(async () => {15 if (!userId || queue.current.length === 0) return16 const events = [...queue.current]17 queue.current = []1819 await Promise.allSettled(20 events.map((event) =>21 fetch('/api/events/track', {22 method: 'POST',23 headers: { 'Content-Type': 'application/json' },24 body: JSON.stringify({ user_id: userId, ...event }),25 })26 )27 )28 }, [userId])2930 useEffect(() => {31 const interval = setInterval(flush, 5000)32 return () => {33 clearInterval(interval)34 flush()35 }36 }, [flush])3738 const track = useCallback(39 (event_type: string, event_data?: Record<string, unknown>) => {40 queue.current.push({41 event_type,42 event_data,43 page_url: typeof window !== 'undefined' ? window.location.pathname : undefined,44 })45 },46 []47 )4849 return { track }50}Expected result: Components call track('button_click', { button: 'cta' }) and events are batched and sent to the API every 5 seconds for efficient network usage.
Complete code
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'34export const runtime = 'edge'56export async function GET(req: NextRequest) {7 const supabase = createClient(8 process.env.SUPABASE_URL!,9 process.env.SUPABASE_SERVICE_ROLE_KEY!10 )1112 const userId = req.nextUrl.searchParams.get('user_id')13 if (!userId) {14 return NextResponse.json({ error: 'user_id required' }, { status: 400 })15 }1617 const { data: profile } = await supabase18 .from('user_profiles')19 .select('segments, preferences')20 .eq('user_id', userId)21 .single()2223 if (!profile) {24 return NextResponse.json({ variant: null, fallback: true })25 }2627 const { data: rules } = await supabase28 .from('personalization_rules')29 .select('id, name, segment_criteria, content_variant, priority')30 .eq('is_active', true)31 .order('priority', { ascending: false })3233 const matchedRule = rules?.find((rule) => {34 const criteria = rule.segment_criteria as { segments?: string[] }35 return criteria.segments?.some((s: string) =>36 profile.segments?.includes(s)37 )38 })3940 if (matchedRule) {41 await supabase.rpc('increment_impressions', {42 rule_id: matchedRule.id,43 })44 }4546 return NextResponse.json({47 variant: matchedRule?.content_variant ?? null,48 rule_name: matchedRule?.name ?? null,49 fallback: !matchedRule,50 })51}Customization ideas
Add real-time personalization
Use Supabase Realtime subscriptions to update content variants instantly when a user's segment changes, without waiting for a page refresh.
Integrate OpenAI for dynamic content generation
Instead of static content variants, call the OpenAI API to generate personalized copy based on the user's segment and preferences in real time.
Build a visual rule builder
Replace the JSON editor with a drag-and-drop condition builder UI where non-technical team members can create targeting rules visually.
Add multi-armed bandit optimization
Replace static A/B testing with a multi-armed bandit algorithm that automatically routes more traffic to winning variants over time.
Enable geographic targeting
Use the Vercel Edge middleware to detect user location from request headers and add geographic segments for region-specific personalization.
Common pitfalls
Pitfall: Exposing SUPABASE_SERVICE_ROLE_KEY with a NEXT_PUBLIC_ prefix
How to avoid: Store SUPABASE_SERVICE_ROLE_KEY in the Vars tab without any prefix. Only use it in API routes and Server Actions, never in client components.
Pitfall: Evaluating rules client-side instead of server-side
How to avoid: Always evaluate personalization rules in an API route or Server Action. The client only receives the final content variant, never the rules themselves.
Pitfall: Not using connection pooling for serverless segment computation
How to avoid: Use the Supavisor pooled connection string from your Supabase project settings. Set this as SUPABASE_URL in the Vars tab.
Pitfall: Accessing window or localStorage in Server Components for event tracking
How to avoid: Wrap event tracking in a 'use client' component and access window inside useEffect to ensure it only runs in the browser.
Best practices
- Use Edge Runtime for the evaluation endpoint to minimize personalization latency globally
- Batch event tracking calls on the client side to reduce network requests and improve page performance
- Store personalization rules in the database rather than code so non-technical team members can update them
- Use V0's prompt queuing to generate the event tracker, rule engine, and dashboard components in sequence without waiting
- Always compute segments server-side using Supabase database functions to keep business logic secure and performant
- Set up pg_cron for automatic segment recomputation rather than computing on every page load
- Use V0's Design Mode (Option+D) to visually adjust the admin dashboard layout without spending credits
- Test personalization rules with a small segment before rolling out to all users
AI prompts to try
Copy these prompts to build this project faster.
I'm building a personalization engine with Next.js App Router and Supabase. I need help designing the segment computation logic. Given a user_events table with event_type and event_data columns, write a PostgreSQL function that assigns segments based on: purchase count (buyer if >0), event frequency (power_user if 50+/month, active if 10+), and recency (churning if no events in 14 days). Return the function and a pg_cron schedule.
Create a real-time A/B testing dashboard. Use Supabase Realtime to subscribe to ab_tests table changes and update conversion rates live. Show a shadcn/ui Table with variant_key, impressions, conversions, rate, and a Badge that turns green when statistical significance exceeds 95% confidence. Include a Recharts BarChart comparing variants side by side.
Frequently asked questions
What is the minimum V0 plan needed for a personalization system?
V0 Free works for basic builds, but Premium ($20/month) is recommended because prompt queuing lets you generate multiple subsystems (event tracker, rule engine, dashboard) in sequence without waiting for each to finish.
Do I need pgvector for a basic personalization system?
No. For rule-based personalization with segment matching, standard Supabase tables are sufficient. pgvector is only needed if you want embedding-based user similarity for advanced content-based or hybrid personalization.
How do I handle personalization for anonymous users who haven't logged in?
Generate an anonymous ID using crypto.randomUUID() and store it in a cookie via Next.js middleware. Track events against this anonymous ID, then merge it with the authenticated user profile when they sign in.
Will the personalization engine slow down my page loads?
No, if you use Edge Runtime for the evaluation endpoint. Edge functions run globally on Vercel's network with sub-50ms latency. Pre-computed segments in user_profiles mean the evaluation is a simple database lookup, not a complex computation.
How do I deploy my personalization system to production?
Click Share then Publish to Production in V0 — it deploys to Vercel in 30-60 seconds. Alternatively, use the Git panel to connect to GitHub and create an auto-PR for team review before merging to production.
Can I run A/B tests without a third-party tool like Optimizely?
Yes. This build includes a built-in A/B testing framework with impression counting, conversion tracking, and statistical significance calculation — all stored in your own Supabase database with no external dependencies.
Can RapidDev help build a custom personalization system?
Yes. RapidDev has built 600+ apps including advanced personalization engines with real-time segment computation and ML-powered recommendation systems. Book a free consultation to discuss your specific requirements.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation