Skip to main content
RapidDev - Software Development Agency

How to Build Resume builder backend with V0

Build a resume builder backend with V0 using Next.js, Supabase, and @react-pdf/renderer that lets job seekers enter their experience, education, and skills into structured forms and export polished, ATS-friendly PDF resumes from multiple templates — all in about 1-2 hours.

What you'll build

  • Section-by-section resume editor with shadcn/ui Tabs for personal info, experience, education, and skills
  • Multiple resume templates with live preview switching via Select component
  • PDF generation using @react-pdf/renderer in a server-side API route
  • Resume list with preview thumbnails and primary resume designation
  • Drag-and-drop section reordering for customizing resume layout
  • Supabase Storage for persisting generated PDF files for re-download
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate9 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a resume builder backend with V0 using Next.js, Supabase, and @react-pdf/renderer that lets job seekers enter their experience, education, and skills into structured forms and export polished, ATS-friendly PDF resumes from multiple templates — all in about 1-2 hours.

What you're building

Job seekers need clean, professional resumes that pass Applicant Tracking Systems (ATS). Most resume builders are subscription-based and lock your data. Building your own gives users full control over their resume data and unlimited PDF exports.

V0 generates the structured resume editor, template preview, and PDF generation pipeline from prompts. The key technical challenge is making the PDF output match the visual preview — @react-pdf/renderer runs server-side in an API route with the same layout logic as the React preview component.

The architecture uses Next.js App Router with Server Components for the resume list, a client component for the interactive editor and live preview, an API route for PDF generation with @react-pdf/renderer, Server Actions for saving resume sections, and Supabase Storage for persisting generated PDFs.

Final result

A resume builder with structured data entry, live template preview, multiple template options, server-side PDF generation, and persistent storage for resumes and exported files.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
@react-pdf/rendererPDF Generation

Prerequisites

  • A V0 account (Premium recommended for the editor complexity)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • Your own resume data for testing the builder

Build steps

1

Set up the project and resume schema

Open V0 and create a new project. Use the Connect panel to add Supabase. Create the normalized schema for resumes with separate tables for personal info, experiences, education, and skills.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a resume builder. Create a Supabase schema with:
3// 1. resumes: id (uuid PK), user_id (uuid FK), title (text), template_id (text), is_primary (boolean default false), created_at (timestamptz), updated_at (timestamptz)
4// 2. personal_info: id (uuid PK), resume_id (uuid FK unique), full_name (text), email (text), phone (text), location (text), linkedin (text), website (text), summary (text)
5// 3. experiences: id (uuid PK), resume_id (uuid FK), company (text), title (text), start_date (date), end_date (date nullable), is_current (boolean), description (text), achievements (text[]), position (integer)
6// 4. education: id (uuid PK), resume_id (uuid FK), institution (text), degree (text), field (text), start_date (date), end_date (date nullable), gpa (text), position (integer)
7// 5. skills: id (uuid PK), resume_id (uuid FK), category (text), items (text[]), position (integer)
8// RLS: users can only CRUD their own resumes and related data.
9// Generate SQL migration and TypeScript types.

Pro tip: Use V0's prompt queuing — queue the schema prompt first, then immediately queue the editor and PDF generation prompts while the schema generates.

Expected result: Supabase is connected with all five tables created. RLS policies ensure users can only access their own resume data.

2

Build the section-by-section resume editor

Create the resume editor page with Tabs for each section — personal info, experience, education, and skills. Each tab has structured form fields that save via Server Actions.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a resume editor at app/resumes/[id]/edit/page.tsx.
3// Requirements:
4// - shadcn/ui Tabs for sections: Personal, Experience, Education, Skills
5// - Personal tab: Input fields for full_name, email, phone, location, linkedin, website. Textarea for summary.
6// - Experience tab:
7// - List of experience entries, each in a Card
8// - Each Card: Input for company and title, date pickers for start/end, Checkbox for is_current (hides end date), Textarea for description, dynamic text[] Input for achievements (add/remove)
9// - "Add Experience" Button, drag handle for reordering
10// - Dialog for adding new experience
11// - Education tab: similar pattern with institution, degree, field, dates, gpa
12// - Skills tab: grouped by category. Each group has category name Input and comma-separated items Input
13// - Auto-save on blur via Server Actions: saveSection()
14// - Right panel: live resume preview (rendered HTML matching the selected template)
15// - Template Select at top to switch between templates (classic/modern/minimal)
16// - 'use client' for the interactive editor, Server Actions for saves

Expected result: A tabbed resume editor with structured forms for each section. Changes auto-save on blur. A live preview panel on the right shows how the resume will look.

3

Create the PDF generation API route

Build the API route that generates a PDF from the resume data using @react-pdf/renderer. The PDF uses the same layout logic as the preview component, ensuring what you see matches what you get.

app/api/resumes/[id]/pdf/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3import { renderToBuffer } from '@react-pdf/renderer'
4import { ClassicTemplate } from '@/lib/resume-templates/classic'
5
6export const maxDuration = 30
7
8const supabase = createClient(
9 process.env.SUPABASE_URL!,
10 process.env.SUPABASE_SERVICE_ROLE_KEY!
11)
12
13export async function GET(
14 req: NextRequest,
15 { params }: { params: Promise<{ id: string }> }
16) {
17 const { id } = await params
18
19 const { data: resume } = await supabase
20 .from('resumes')
21 .select('*, personal_info(*), experiences(*), education(*), skills(*)')
22 .eq('id', id)
23 .single()
24
25 if (!resume) {
26 return NextResponse.json({ error: 'Resume not found' }, { status: 404 })
27 }
28
29 const pdfBuffer = await renderToBuffer(
30 ClassicTemplate({ resume })
31 )
32
33 const filePath = `pdfs/${id}/${Date.now()}.pdf`
34 await supabase.storage.from('resumes').upload(filePath, pdfBuffer, {
35 contentType: 'application/pdf',
36 })
37
38 return new NextResponse(pdfBuffer, {
39 headers: {
40 'Content-Type': 'application/pdf',
41 'Content-Disposition': `attachment; filename="${resume.personal_info?.full_name ?? 'resume'}.pdf"`,
42 },
43 })
44}

Expected result: GET /api/resumes/[id]/pdf generates a PDF using @react-pdf/renderer, stores it in Supabase Storage, and returns it as a downloadable file.

4

Build the resume list page with template switching

Create the main resumes page showing all of the user's resumes. Each resume has a Card with a preview thumbnail, template info, and actions for editing, downloading, duplicating, and deleting.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a resume list at app/resumes/page.tsx.
3// Requirements:
4// - Protected by auth
5// - Grid of resume Cards showing:
6// - Preview thumbnail (small HTML render of the resume)
7// - Resume title, template name Badge, last updated date
8// - Star icon for primary resume (is_primary)
9// - DropdownMenu with actions: Edit, Download PDF, Duplicate, Delete
10// - "Create New Resume" Button opens Dialog with title Input and template Select (classic/modern/minimal)
11// - Server Actions: createResume(), duplicateResume(), deleteResume(), setPrimary()
12// - Server Components for data fetching
13// - Empty state with illustration and "Create Your First Resume" prompt
14// - Use shadcn/ui Card, Badge, DropdownMenu, Dialog, Button

Expected result: A grid of resume Cards with preview thumbnails, template Badges, and action menus. Users can create, duplicate, download, and delete resumes from this page.

Complete code

app/actions/resume.ts
1'use server'
2
3import { createClient } from '@/lib/supabase/server'
4import { revalidatePath } from 'next/cache'
5import { redirect } from 'next/navigation'
6
7export async function saveSection(
8 resumeId: string,
9 section: string,
10 data: Record<string, unknown>
11) {
12 const supabase = await createClient()
13
14 switch (section) {
15 case 'personal_info': {
16 const { data: existing } = await supabase
17 .from('personal_info')
18 .select('id')
19 .eq('resume_id', resumeId)
20 .single()
21
22 if (existing) {
23 await supabase
24 .from('personal_info')
25 .update(data)
26 .eq('resume_id', resumeId)
27 } else {
28 await supabase
29 .from('personal_info')
30 .insert({ resume_id: resumeId, ...data })
31 }
32 break
33 }
34 case 'experience':
35 await supabase
36 .from('experiences')
37 .upsert({ resume_id: resumeId, ...data })
38 break
39 case 'education':
40 await supabase
41 .from('education')
42 .upsert({ resume_id: resumeId, ...data })
43 break
44 case 'skills':
45 await supabase
46 .from('skills')
47 .upsert({ resume_id: resumeId, ...data })
48 break
49 }
50
51 await supabase
52 .from('resumes')
53 .update({ updated_at: new Date().toISOString() })
54 .eq('id', resumeId)
55
56 revalidatePath(`/resumes/${resumeId}/edit`)
57}
58
59export async function duplicateResume(resumeId: string) {
60 const supabase = await createClient()
61 const { data: { user } } = await supabase.auth.getUser()
62
63 const { data: original } = await supabase
64 .from('resumes')
65 .select('*, personal_info(*), experiences(*), education(*), skills(*)')
66 .eq('id', resumeId)
67 .single()
68
69 if (!original) return
70
71 const { data: copy } = await supabase
72 .from('resumes')
73 .insert({
74 user_id: user?.id,
75 title: `${original.title} (Copy)`,
76 template_id: original.template_id,
77 })
78 .select()
79 .single()
80
81 if (!copy) return
82 revalidatePath('/resumes')
83 redirect(`/resumes/${copy.id}/edit`)
84}

Customization ideas

Add AI-powered content suggestions

Use the OpenAI API to suggest improved bullet points for experience descriptions based on the job title and industry.

Build a cover letter generator

Add a cover letter tab that generates personalized cover letters using the resume data and a job description input.

Add more templates

Create additional PDF templates (creative, executive, academic) with different layouts, fonts, and color schemes using @react-pdf/renderer styles.

Build a job application tracker

Add a page to track which resume was sent to which company, application status, and follow-up dates.

Common pitfalls

Pitfall: Using browser-based PDF generation instead of server-side @react-pdf/renderer

How to avoid: Use @react-pdf/renderer in an API route (server-side). It produces consistent, high-quality PDFs with precise layout control and no browser dependency.

Pitfall: Not setting maxDuration for the PDF generation API route

How to avoid: Add export const maxDuration = 30 to the API route file to give the PDF renderer up to 30 seconds to complete.

Pitfall: Storing all resume data in a single JSONB column

How to avoid: Use normalized tables (personal_info, experiences, education, skills) with proper types and constraints. This enables section-level queries and validation.

Best practices

  • Use @react-pdf/renderer in API routes for consistent, high-quality PDF output without browser dependencies
  • Set maxDuration = 30 in the PDF API route for Vercel serverless to handle complex multi-page resumes
  • Store generated PDFs in Supabase Storage for re-download without regeneration
  • Use V0's prompt queuing to generate the editor, preview, and PDF template in rapid sequence
  • Normalize resume data into separate tables (personal_info, experiences, education, skills) for clean queries and validation
  • Auto-save on blur in the editor so users never lose progress

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a resume builder with Next.js App Router and @react-pdf/renderer. Write a React PDF template component that renders a professional resume with sections for personal info (name, email, phone centered), summary paragraph, experience entries (company, title, dates, bullet points), education (institution, degree, dates), and skills (grouped by category). Use @react-pdf/renderer's Document, Page, View, Text, and StyleSheet. Include proper typography and spacing.

Build Prompt

Create a live resume preview component that renders an HTML representation of the resume matching a selected template. Accept resume data as props (personal_info, experiences, education, skills) and template_id. Render with proper typography, section headers, bullet points, and date formatting. This is the 'what you see is what you get' preview that matches the @react-pdf/renderer output.

Frequently asked questions

What V0 plan do I need for a resume builder?

V0 Free works for the basic build, but Premium ($20/month) is recommended because the editor has multiple complex tabs and a live preview that benefit from prompt queuing.

Will the PDF match the visual preview exactly?

Yes, if you use shared style constants between the React preview component and the @react-pdf/renderer template. Define font sizes, spacing, and colors in a shared config file used by both.

Are the generated PDFs ATS-friendly?

@react-pdf/renderer produces text-based PDFs (not images), so ATS systems can parse the content. Stick to standard fonts, avoid complex layouts, and use proper heading hierarchy for best ATS compatibility.

How do I add more resume templates?

Create new @react-pdf/renderer components in lib/resume-templates/ with different layouts and styles. Add the template ID to the Select options in the editor. The PDF route dynamically loads the template based on template_id.

How do I deploy the resume builder?

Click Share then Publish to Production in V0. Set SUPABASE_SERVICE_ROLE_KEY in the Vars tab without NEXT_PUBLIC_ prefix. PDF generation runs server-side so no special browser requirements.

Can RapidDev help build a custom resume builder?

Yes. RapidDev has built 600+ apps including resume platforms with AI content suggestions, custom template engines, and job application tracking. Book a free consultation to discuss your needs.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.