ResumeIQ is a production-ready AI resume analyzer built with modern web technologies. This document explains the architecture, data flow, and key design decisions.
- React 19: Latest React with concurrent features
- TanStack Router: File-based routing with type safety
- TanStack Query: Server state management
- Tailwind CSS: Utility-first CSS framework
- TanStack Start: Full-stack React framework with SSR
- Server Functions: Type-safe server actions
- Vinxi: Build tool and development server
- Supabase PostgreSQL: Relational database with RLS
- Supabase Storage: File storage for resumes
- Supabase Auth: Authentication with JWT
- Stripe: Payment processing and subscriptions
- OpenAI (stubbed): AI-powered resume analysis
ResumeIQ/
├── app/
│ ├── components/ # React components
│ │ ├── DashboardLayout.tsx
│ │ ├── Header.tsx
│ │ └── LoadingSpinner.tsx
│ │
│ ├── lib/ # Core library code
│ │ ├── supabase.client.ts # Browser Supabase client
│ │ ├── supabase.server.ts # Server Supabase client
│ │ └── stripe.server.ts # Stripe configuration
│ │
│ ├── routes/ # File-based routes
│ │ ├── __root.tsx # Root layout
│ │ ├── index.tsx # Home page
│ │ ├── login.tsx # Login page
│ │ ├── signup.tsx # Signup page
│ │ ├── pricing.tsx # Pricing page
│ │ ├── _authenticated.tsx # Auth layout
│ │ └── _authenticated.dashboard.* # Dashboard routes
│ │
│ ├── styles/ # Global styles
│ │ └── globals.css
│ │
│ ├── types/ # TypeScript types
│ │ ├── index.ts # App types
│ │ └── supabase.ts # Database types
│ │
│ ├── utils/ # Server actions
│ │ ├── auth.server.ts # Authentication
│ │ ├── resume.server.ts # Resume operations
│ │ └── stripe.server.ts # Stripe operations
│ │
│ ├── client.tsx # Client entry point
│ ├── router.tsx # Router configuration
│ ├── routeTree.gen.ts # Generated route tree
│ └── ssr.tsx # SSR entry point
│
├── supabase/
│ └── migrations/ # Database migrations
│ ├── 001_initial_schema.sql
│ └── 002_storage_setup.sql
│
├── .env.example # Environment variables template
├── .gitignore
├── app.config.ts # App configuration
├── package.json
├── postcss.config.js
├── tailwind.config.ts
└── tsconfig.json
-
Sign Up/Sign In
User → Client Form → signUp/signIn() → Supabase Auth → Set Cookies → Dashboard -
Protected Routes
User Request → _authenticated.tsx → Check Auth → Allow/Redirect -
Session Management
Server Request → Read Cookies → Verify JWT → Get User → RLS Policies
-
Upload
User → File Input → Client Validation → Supabase Storage → uploadResume() -
Text Extraction (Stubbed)
Resume ID → extractResumeText() → PDF/DOCX Parser → Save to DB -
AI Analysis (Stubbed)
Resume Text → analyzeResume() → OpenAI API → Parse Results → Save to DB -
Display
Resume ID → getResumeAnalyses() → Format Data → React Components
-
Checkout
User → Select Plan → createCheckoutSession() → Stripe Checkout → Success/Cancel -
Webhook Processing (Stubbed)
Stripe Event → handleStripeWebhook() → Verify Signature → Update DB -
Access Control
User Request → getUserSubscription() → Check Plan → Allow/Restrict Features
- Stores uploaded resume metadata and extracted text
- Fields: id, user_id, filename, file_path, file_type, file_size, extracted_text, created_at, updated_at
- RLS: Users can only access their own resumes
- Stores AI analysis results
- Fields: id, resume_id, user_id, ats_score, strengths, weaknesses, suggestions, keywords, raw_analysis, created_at
- RLS: Users can only access their own analyses
- Manages user subscriptions
- Fields: id, user_id, stripe_subscription_id, stripe_customer_id, status, plan_type, current_period_start, current_period_end, cancel_at_period_end, created_at, updated_at
- RLS: Users can only access their own subscription
- Records payment history
- Fields: id, user_id, subscription_id, stripe_payment_intent_id, amount, currency, status, created_at
- RLS: Users can only access their own payments
All tables have RLS enabled with policies that:
- Allow users to read their own data
- Allow users to insert their own data
- Allow users to update their own data
- Restrict access to other users' data
- Use
auth.uid()for user identification
- Bucket:
resumes - Path Structure:
{user_id}/{timestamp}-{filename} - Policies: Users can only access files in their own folder
signUp({ email, password })- Create new user accountsignIn({ email, password })- Authenticate usersignOut()- End user sessiongetCurrentUser()- Get authenticated user
uploadResume({ filename, fileType, fileSize, filePath })- Save resume metadataextractResumeText({ resumeId })- Extract text from uploaded file (stubbed)analyzeResume({ resumeId })- Analyze resume with AI (stubbed)getUserResumes()- Get all user's resumesgetResumeAnalyses({ resumeId })- Get analyses for a resume
createCheckoutSession({ planType })- Create Stripe checkout (stubbed)handleStripeWebhook()- Process Stripe webhooks (stubbed)getUserSubscription()- Get user's current subscriptioncancelSubscription()- Cancel user's subscription
- JWT tokens stored in HTTP-only cookies
- Server-side validation on every request
- Protected routes check authentication before rendering
- Row Level Security enforces data access
- Server functions verify user ownership
- Service role key used only for admin operations
- Client-side file type validation
- Server-side file size limits
- Files scoped to user directories
- Storage policies enforce access control
- Server-only environment variables
- Never exposed to client
- Separate keys for development/production
- Stripe webhook signature verification (stubbed)
- Payment events processed server-side only
- No sensitive payment data stored
Location: app/utils/resume.server.ts - analyzeResume()
// Replace stubbed implementation with:
import OpenAI from 'openai'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: [
{
role: "system",
content: "You are a resume analysis expert..."
},
{
role: "user",
content: resume.extracted_text
}
]
})
// Parse and save resultsLocation: app/utils/resume.server.ts - extractResumeText()
// For PDF files:
import pdf from 'pdf-parse'
const { data: file } = await supabase.storage
.from('resumes')
.download(resume.file_path)
const buffer = await file.arrayBuffer()
const data = await pdf(Buffer.from(buffer))
const extractedText = data.text
// For DOCX files:
import mammoth from 'mammoth'
const result = await mammoth.extractRawText({ buffer })
const extractedText = result.valueLocation: app/utils/stripe.server.ts - handleStripeWebhook()
Uncomment the production implementation and handle:
checkout.session.completed- Create subscriptioncustomer.subscription.updated- Update subscriptioncustomer.subscription.deleted- Cancel subscriptionpayment_intent.succeeded- Record payment
- Database: Add migration in
supabase/migrations/ - Types: Update
app/types/supabase.ts - Server Function: Create in
app/utils/*.server.ts - Route: Add file in
app/routes/ - Component: Create in
app/components/
Required for all environments:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYSTRIPE_SECRET_KEYSTRIPE_PUBLISHABLE_KEYSTRIPE_WEBHOOK_SECRETOPENAI_API_KEYVITE_APP_URL
npm run buildThis creates:
- Client bundle (
.output/public) - Server bundle (
.output/server) - SSR configuration
The application is compatible with:
- Vercel: Native TanStack Start support
- Netlify: Edge functions support
- Railway: Node.js deployment
- Self-hosted: Node.js + reverse proxy
- Run Supabase migrations
- Configure Stripe webhook endpoint
- Set up custom domain
- Configure CDN for static assets
- Set up monitoring and logging
- Initial page load is server-rendered
- Faster first contentful paint
- Better SEO
- Route-based code splitting
- Lazy loading of components
- Reduced initial bundle size
- TanStack Query caches server data
- Automatic cache invalidation
- Optimistic updates
- Indexed columns for faster queries
- Connection pooling
- Prepared statements
- Test server functions independently
- Mock Supabase and Stripe clients
- Test business logic
- Test route handlers
- Test database operations
- Test authentication flow
- Test complete user flows
- Test file upload and analysis
- Test payment flow
- Server function errors
- Authentication events
- Payment events
- Response times
- Database query performance
- File upload times
- Client-side errors
- Server-side errors
- Failed webhook events
- Create new migration files
- Test locally with Supabase CLI
- Apply in staging, then production
- Regular security updates
- Breaking change reviews
- Test before deploying
- Daily database backups
- Storage bucket replication
- Configuration backups
- User Documentation: In-app help and tooltips
- API Documentation: OpenAPI spec (future)
- Developer Documentation: This file
- Contributing Guide: CONTRIBUTING.md (future)