Build a GDPR/CCPA compliance toolkit with V0 using Next.js and Supabase that gives users a self-service privacy center for managing consent, requesting data exports, and submitting deletion requests. Includes a cookie consent banner, audit logging, and admin review workflows — all in about 1-2 hours.
What you're building
If your app collects any personal data, GDPR and CCPA require you to give users control over that data. Users must be able to see what you collect, consent to specific uses, export their data, and request deletion. Failing to comply risks fines up to 4% of annual revenue under GDPR.
V0 streamlines building this compliance toolkit by generating the privacy center UI, consent management forms, and export/deletion API routes from natural language prompts. Connect Supabase for storing consent records and audit logs, and use Next.js middleware for the cookie consent gate.
The architecture uses Next.js App Router with Server Components for the privacy dashboard, API routes for data export and deletion endpoints, Next.js middleware for pre-consent script blocking, Server Actions for consent updates, and Supabase for storing consent records, data requests, and a complete audit log.
Final result
A complete privacy compliance toolkit with a user-facing privacy center, consent management with granular toggles, data export and deletion request workflows, cookie consent banner, and a full audit trail for regulatory compliance.
Tech stack
Prerequisites
- A V0 account (Premium recommended for prompt queuing)
- A Supabase project (free tier works — connect via V0's Connect panel)
- Clerk account for authentication (free tier works — users need to be logged in to manage privacy)
- A list of what personal data your app collects (email, name, usage data, etc.)
Build steps
Set up the project and privacy schema
Open V0 and create a new project. Use the Connect panel to add Supabase, then prompt V0 to create the schema for consent records, data requests, and audit logging.
1// Paste this prompt into V0's AI chat:2// Build a GDPR/CCPA privacy compliance toolkit. Create a Supabase schema with:3// 1. consent_records: id (uuid PK), user_id (uuid FK), consent_type (text check analytics/marketing/functional/essential), granted (boolean), ip_address (text), granted_at (timestamptz), revoked_at (timestamptz)4// 2. data_requests: id (uuid PK), user_id (uuid FK), request_type (text check export/deletion/access), status (text check pending/processing/completed/denied), completed_at (timestamptz), download_url (text), created_at (timestamptz)5// 3. audit_log: id (uuid PK), actor_id (uuid), action (text), entity_type (text), entity_id (uuid), details (jsonb), created_at (timestamptz)6// Add RLS: users can only read/write their own consent_records and data_requests. Audit log is insert-only for all, select for admins.7// Generate SQL migration and TypeScript types.Pro tip: Use V0's Git panel to connect to GitHub — this gives you version-controlled history of every privacy policy and consent flow change, which is valuable for regulatory audits.
Expected result: Supabase is connected with consent_records, data_requests, and audit_log tables created. RLS policies enforce per-user data access.
Build the consent management privacy center
Create the main privacy center page where users can see and manage their consent preferences. Each consent category gets a Switch toggle that immediately updates via a Server Action.
1// Paste this prompt into V0's AI chat:2// Build a privacy center at app/privacy/page.tsx.3// Requirements:4// - Show a heading "Your Privacy Settings" with description about data control5// - Display consent categories in shadcn/ui Cards: Essential (always on, disabled Switch), Analytics, Marketing, Functional6// - Each Card has: category name, description of what data is collected, Switch toggle for granted/revoked7// - Switch toggles call a Server Action updateConsent() that updates consent_records and logs to audit_log8// - Below consent, show a "Your Data" section with two Cards:9// - "Export My Data" Card with Button that creates a data_request with type=export10// - "Delete My Data" Card with Button that opens AlertDialog ("This action cannot be undone") then creates a data_request with type=deletion11// - Show a Table of existing data_requests with Badge for status (pending=yellow, completed=green)12// - Add an Accordion at the bottom for privacy policy sections13// - Use Toast for confirmation feedback after each action14// - Server Components for data fetching, 'use client' for Switch toggles and AlertDialogExpected result: A privacy center with Switch toggles for each consent category, export and delete request buttons, and a Table showing request history with status Badges.
Create the data export API route
Build an API route that aggregates all user data across tables into a JSON file. This uses SUPABASE_SERVICE_ROLE_KEY to bypass RLS and join across all tables containing user PII, then stores the export in Supabase Storage.
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'3import { auth } from '@clerk/nextjs/server'45const supabase = createClient(6 process.env.SUPABASE_URL!,7 process.env.SUPABASE_SERVICE_ROLE_KEY!8)910export async function POST(req: NextRequest) {11 const { userId } = await auth()12 if (!userId) {13 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })14 }1516 const { data: request } = await supabase17 .from('data_requests')18 .insert({ user_id: userId, request_type: 'export', status: 'processing' })19 .select()20 .single()2122 const { data: profile } = await supabase23 .from('profiles')24 .select('*')25 .eq('user_id', userId)26 .single()2728 const { data: consents } = await supabase29 .from('consent_records')30 .select('*')31 .eq('user_id', userId)3233 const { data: activities } = await supabase34 .from('audit_log')35 .select('*')36 .eq('actor_id', userId)3738 const exportData = {39 exported_at: new Date().toISOString(),40 profile,41 consent_records: consents,42 activity_log: activities,43 }4445 const blob = new Blob([JSON.stringify(exportData, null, 2)], {46 type: 'application/json',47 })4849 const filePath = `exports/${userId}/${request!.id}.json`50 await supabase.storage.from('data-exports').upload(filePath, blob)5152 const { data: signedUrl } = await supabase.storage53 .from('data-exports')54 .createSignedUrl(filePath, 3600)5556 await supabase57 .from('data_requests')58 .update({59 status: 'completed',60 completed_at: new Date().toISOString(),61 download_url: signedUrl?.signedUrl,62 })63 .eq('id', request!.id)6465 await supabase.from('audit_log').insert({66 actor_id: userId,67 action: 'data_export_completed',68 entity_type: 'data_request',69 entity_id: request!.id,70 details: { file_path: filePath },71 })7273 return NextResponse.json({ download_url: signedUrl?.signedUrl })74}Expected result: POST /api/privacy/export generates a JSON file with all user data, uploads it to Supabase Storage, and returns a signed download URL valid for 1 hour.
Add the cookie consent banner with Next.js middleware
Create a middleware that checks for a consent cookie before allowing analytics and marketing scripts to load. This ensures no tracking happens before the user explicitly grants consent, which is a core GDPR requirement.
1import { NextRequest, NextResponse } from 'next/server'23export function middleware(request: NextRequest) {4 const response = NextResponse.next()5 const consent = request.cookies.get('privacy-consent')?.value67 if (!consent) {8 response.headers.set('x-consent-status', 'none')9 } else {10 try {11 const parsed = JSON.parse(consent)12 response.headers.set(13 'x-consent-analytics',14 parsed.analytics ? 'granted' : 'denied'15 )16 response.headers.set(17 'x-consent-marketing',18 parsed.marketing ? 'granted' : 'denied'19 )20 } catch {21 response.headers.set('x-consent-status', 'invalid')22 }23 }2425 return response26}2728export const config = {29 matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],30}Pro tip: The cookie consent banner must load before any analytics scripts. Read the x-consent-analytics header in your layout.tsx to conditionally include Google Analytics or Plausible script tags.
Expected result: Every request gets consent status headers. Your layout can conditionally render analytics scripts based on these headers, ensuring no tracking before consent.
Build the admin audit log viewer
Create an admin page that displays the complete audit trail of all privacy-related actions. This is essential for demonstrating GDPR compliance during regulatory audits.
1// Paste this prompt into V0's AI chat:2// Build an admin audit log page at app/admin/audit/page.tsx.3// Requirements:4// - Only accessible to admin users (check Clerk user role)5// - Fetch all audit_log entries ordered by created_at descending6// - Display in a shadcn/ui Table with columns: timestamp, actor_id, action, entity_type, entity_id7// - Action column uses Badge with color coding: data_export=blue, consent_update=green, deletion_request=red8// - Add date range filter using Calendar + Popover (DatePicker pattern)9// - Add Select filter for action type10// - Add pagination with 50 entries per page11// - Use Server Components for data fetching12// - Add a "Download Audit Log" Button that exports filtered results as CSVExpected result: An admin audit log page showing all privacy actions in a filterable, paginated Table with color-coded action Badges and CSV export.
Complete code
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'3import { auth } from '@clerk/nextjs/server'45const supabase = createClient(6 process.env.SUPABASE_URL!,7 process.env.SUPABASE_SERVICE_ROLE_KEY!8)910export async function POST(req: NextRequest) {11 const { userId } = await auth()12 if (!userId) {13 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })14 }1516 const { data: request } = await supabase17 .from('data_requests')18 .insert({19 user_id: userId,20 request_type: 'export',21 status: 'processing',22 })23 .select()24 .single()2526 const [profileRes, consentsRes, activitiesRes] = await Promise.all([27 supabase.from('profiles').select('*').eq('user_id', userId).single(),28 supabase.from('consent_records').select('*').eq('user_id', userId),29 supabase.from('audit_log').select('*').eq('actor_id', userId),30 ])3132 const exportData = {33 exported_at: new Date().toISOString(),34 profile: profileRes.data,35 consent_records: consentsRes.data,36 activity_log: activitiesRes.data,37 }3839 const filePath = `exports/${userId}/${request!.id}.json`40 const blob = new Blob([JSON.stringify(exportData, null, 2)])41 await supabase.storage.from('data-exports').upload(filePath, blob)4243 const { data: signed } = await supabase.storage44 .from('data-exports')45 .createSignedUrl(filePath, 3600)4647 await supabase48 .from('data_requests')49 .update({50 status: 'completed',51 completed_at: new Date().toISOString(),52 download_url: signed?.signedUrl,53 })54 .eq('id', request!.id)5556 return NextResponse.json({ download_url: signed?.signedUrl })57}Customization ideas
Add automated data retention policies
Create a scheduled Supabase function that automatically deletes data older than your retention period, with configurable retention windows per data category.
Add email notifications for request status changes
Integrate Resend via Vercel Marketplace to send email notifications when data export or deletion requests are completed.
Build a privacy policy version history
Store each privacy policy version with an effective date, and track which version each user consented to for precise compliance records.
Add third-party consent propagation
When a user revokes marketing consent, automatically call external APIs (Google Analytics, Mixpanel, Mailchimp) to propagate the opt-out.
Common pitfalls
Pitfall: Loading analytics scripts before checking consent status
How to avoid: Use Next.js middleware to set consent headers, then conditionally render script tags in layout.tsx based on the header values — scripts never load without consent.
Pitfall: Using SUPABASE_SERVICE_ROLE_KEY on the client side for data export
How to avoid: Only use SUPABASE_SERVICE_ROLE_KEY in API routes (app/api/*/route.ts) and Server Actions. Set it in the Vars tab without a NEXT_PUBLIC_ prefix.
Pitfall: Hard-deleting user data without an audit trail
How to avoid: Use soft-delete (mark records as deleted) and log every deletion action to the audit_log table before actually removing data. Keep audit logs for the legally required retention period.
Pitfall: Not including all tables in the data export
How to avoid: Create a Supabase database function that joins across ALL tables containing user_id and returns a complete JSON object. Maintain a checklist of PII-containing tables and update the export function when adding new tables.
Best practices
- Always load the cookie consent banner before any analytics or marketing scripts via Next.js middleware
- Log every privacy-related action (consent change, export, deletion) to the audit_log table for regulatory compliance
- Use Supabase Storage with signed URLs for data exports — links expire automatically after 1 hour
- Use V0's Git panel to connect to GitHub so all privacy flow changes are version-controlled and reviewable via PRs
- Set SUPABASE_SERVICE_ROLE_KEY in the Vars tab without NEXT_PUBLIC_ prefix — only use it server-side for data aggregation
- Implement soft-delete with a deleted_at timestamp before hard-deleting data to maintain audit trail integrity
- Use V0's Design Mode (Option+D) to visually adjust the consent banner and privacy center layout without spending credits
AI prompts to try
Copy these prompts to build this project faster.
I'm building a GDPR/CCPA privacy compliance toolkit with Next.js App Router and Supabase. I need a Supabase database function that aggregates all user personal data across multiple tables (profiles, orders, messages, consent_records) by user_id and returns a single JSON object. Also need a cascading soft-delete function that marks all records with a deleted_at timestamp. Write both functions and the audit logging logic.
Create a cookie consent banner component that appears on first visit. Use shadcn/ui Card with Switch toggles for analytics, marketing, and functional cookies. Essential is always on and disabled. Include Accept All, Reject All, and Save Preferences buttons. Store preferences in a cookie and update consent_records in Supabase via Server Action. The banner should prevent any non-essential scripts from loading until preferences are saved.
Frequently asked questions
Do I need a paid V0 plan to build privacy tools?
V0 Free works for the basic build, but Premium ($20/month) is recommended for prompt queuing to generate the consent banner, privacy center, and export endpoint more efficiently.
Does this comply with GDPR and CCPA?
This build implements the core technical requirements: consent management, data export, deletion requests, and audit logging. However, you should consult a legal professional for your specific jurisdiction and data processing activities.
How do I handle the cookie consent banner correctly?
The build uses Next.js middleware to check for a consent cookie on every request. Analytics and marketing scripts are only injected in layout.tsx when the corresponding consent flag is true. This ensures no tracking before explicit consent.
Can users download their data immediately?
Yes. The export API route aggregates all user data in real time, uploads it to Supabase Storage, and returns a signed download URL. For large datasets, consider queuing the export as a background job and notifying the user when ready.
How do I deploy this to production?
Click Share then Publish to Production in V0. Make sure SUPABASE_SERVICE_ROLE_KEY and CLERK_SECRET_KEY are set in the Vars tab without NEXT_PUBLIC_ prefix. Your cookie consent banner will work automatically on the Vercel domain.
What if I add new tables that contain personal data?
Update the data export API route to include the new table in its aggregation query. Maintain a checklist of PII-containing tables and review it whenever you add new data models.
Can RapidDev help build a custom privacy compliance toolkit?
Yes. RapidDev has built 600+ apps including privacy-compliant platforms with automated data retention, consent propagation to third parties, and multi-jurisdiction compliance. Book a free consultation to discuss your requirements.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation