How I Built CVRole with Next.js 16 and Claude AI
CVRole is an AI-powered CV builder for Arabic professionals. Here's the full technical breakdown of how it was built.
Tech Stack
- Frontend: Next.js 16 App Router, TypeScript, Tailwind CSS v4
- AI: Anthropic Claude claude-sonnet-4-6 via the Anthropic SDK
- Database: Neon PostgreSQL with Prisma ORM
- Auth: NextAuth v5 (Google OAuth + email/password)
- Payments: Stripe
- PDF Generation: Puppeteer + react-dom/server
- Deployment: Vercel
Architecture Decisions
Why Next.js App Router?
Server components let us fetch CV data without any client-side loading states for the initial render. The template rendering happens server-side, which is faster and better for SEO.
The App Router's layout system also made it trivial to share authentication state across dashboard, builder, and settings pages.
Why Claude AI?
We evaluated GPT-4, Gemini, and Claude for CV generation. Claude consistently produced:
- Better structured output — consistently returns valid JSON without hallucinating fields
- More professional tone — understands nuance in career writing
- Better Arabic — Claude's Arabic is significantly better than competing models
- Reliable tool use — Claude follows complex instructions more consistently
The core generation function:
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 3000,
system: systemPrompt,
messages: [{ role: "user", content: prompt }],
});PDF Generation with Puppeteer
Generating pixel-perfect PDFs from HTML templates is non-trivial. Our approach:
- Next.js renders a hidden /cv/[id]/print route — a pure HTML page with no nav, no JS
- Puppeteer launches Chromium, navigates to that URL
- page.pdf({ format: "A4" }) captures it
The advantage: the PDF looks exactly like the preview. No CSS-to-PDF conversion artifacts.
Arabic RTL in Templates
CSS supports RTL via dir="rtl" and logical properties (margin-inline-start instead of margin-left). But RTL in PDFs has additional challenges:
- Google Fonts must include the Arabic character set
- Cairo font must be loaded before Puppeteer renders
- unicode-bidi: embed must be set on Arabic text containers
The Freemium Architecture
The free tier tracks:
- AI generations used (aiGenerationsUsed on the User model)
- PDF downloads via ActivityLog entries
- CV count (free = 1 CV max)
Free users get a watermark burned into the PDF via Puppeteer's page.evaluate() — it runs inside the browser context, making it impossible to remove via CSS overrides.
What's Next
- Mobile apps (React Native)
- LinkedIn profile sync
- Direct job application integration
Try CVRole at cvrole.com.