Turn your PDF resume into a hosted web portfolio in under 60 seconds.
Upload a PDF. AI parses it. Get a shareable link.
- Instant PDF Parsing - AI extracts your information automatically
- Clean Public URLs - Get
yoursite.com/yournameimmediately - Privacy Controls - Show/hide phone numbers and addresses
- Multiple Templates - Professional, modern designs
- Mobile Responsive - Looks great on all devices
- SEO Optimized - Proper metadata, Open Graph tags
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Runtime | Cloudflare Workers |
| Database | Cloudflare D1 (SQLite) + Drizzle ORM |
| Auth | Better Auth (Google OAuth) |
| Storage | Cloudflare R2 (S3-compatible) |
| AI Parsing | OpenAI GPT via OpenRouter |
| Styling | shadcn/ui + Tailwind CSS 4 |
We chose Cloudflare Workers over traditional hosting for several reasons:
- Edge Computing: Code runs in 300+ data centers worldwide, closest to your users
- Cold Start: ~0ms cold starts vs. 200-500ms on traditional serverless
- Latency: Sub-50ms response times globally
- Free Tier: 100,000 requests/day free
- D1 Database: 5GB free, built-in SQLite
- R2 Storage: 10GB free, no egress fees
- Total: A production app can run free for most use cases
- No Container Management: Just deploy code
- Automatic Scaling: From 0 to millions of requests
- Integrated Stack: D1, R2, and Workers work seamlessly together
- No
fsModule: Must use R2 for file operations - No Native Next.js Image: Use
<img>with CSS instead - Edge Middleware Limits: No D1 access in middleware
- Bundle Size: Keep dependencies minimal
- Bun v1.0+ (package manager)
- Cloudflare Account with R2 and D1 enabled
- Google Cloud Console project for OAuth
- OpenRouter account for AI parsing
# Clone the repository
git clone https://github.com/divkix/clickfolio.me.git
cd clickfolio.me
# Install dependencies
bun install
# Copy environment template
cp .env.example .env.local
# Set up local database
bun run db:migrate
# Start development server
bun run devIf you are not technical, follow this exact checklist. You only need a terminal and browser.
What you need
- A Cloudflare account (free is fine)
- A Google Cloud account (for Google Sign-In)
- An OpenRouter account (for AI parsing)
- Bun installed (copy/paste this in Terminal):
curl -fsSL https://bun.sh/install | bash
Step 0: Get the code
- Download the repo ZIP from GitHub and unzip it, or use:
git clone https://github.com/divkix/clickfolio.me.git cd clickfolio.me - Install dependencies:
bun install
Step 1: Create Cloudflare D1 database
- In Terminal:
bunx wrangler d1 create clickfolio-db
- Copy the
database_idprinted in the terminal. - Open
wrangler.jsoncand replace thedatabase_idvalue.
Step 2: Create Cloudflare R2 bucket
- Go to Cloudflare Dashboard β R2 β Create bucket.
- Name it
clickfolio-bucket. - The bucket is accessed via binding in wrangler.jsonc - no API tokens needed.
Step 3: Configure R2 CORS In Cloudflare R2 bucket settings β CORS, paste:
[
{
"AllowedOrigins": ["http://localhost:3000", "https://your-domain.com"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]Step 4: Set up Google OAuth
- Go to Google Cloud Console.
- Create project β APIs & Services β Credentials.
- Create OAuth Client ID (Web app).
- Add redirect URIs:
http://localhost:3000/api/auth/callback/googlehttps://your-domain.com/api/auth/callback/google
- Copy Client ID and Client Secret.
Step 5: Set up OpenRouter
- Create OpenRouter account β API Keys.
- Copy your API key.
Step 6: Add secrets to Cloudflare (production) Run each command and paste the value when prompted:
bunx wrangler secret put BETTER_AUTH_SECRET
bunx wrangler secret put BETTER_AUTH_URL # Also used as app URL
bunx wrangler secret put GOOGLE_CLIENT_ID
bunx wrangler secret put GOOGLE_CLIENT_SECRET
bunx wrangler secret put CF_AI_GATEWAY_ACCOUNT_ID
bunx wrangler secret put CF_AI_GATEWAY_ID
bunx wrangler secret put CF_AIG_AUTH_TOKENStep 7: Deploy
bun run db:migrate:prod
bun run deployStep 8: Add your domain Cloudflare Dashboard β Workers & Pages β your worker β Settings β Domains & Routes.
Important: After domain is connected, update this secret:
BETTER_AUTH_URL=https://your-domain.com
Then redeploy:
bun run deployIf you followed the steps above, the site should be live at your domain.
-
Create a Cloudflare account at cloudflare.com
-
Create D1 Database
bunx wrangler d1 create clickfolio-db
Copy the
database_idtowrangler.jsonc -
Create R2 Bucket
- Go to Cloudflare Dashboard > R2
- Create bucket named
clickfolio-bucket - The bucket is accessed via binding in
wrangler.jsonc- no API tokens needed
-
Configure R2 CORS Add CORS policy in R2 bucket settings:
[ { "AllowedOrigins": ["http://localhost:3000", "https://your-domain.com"], "AllowedMethods": ["GET", "PUT", "POST"], "AllowedHeaders": ["*"], "MaxAgeSeconds": 3000 } ]
- Go to Google Cloud Console
- Create a new project (or select existing)
- Enable Google+ API and People API
- Go to APIs & Services > Credentials
- Create OAuth 2.0 Client ID (Web application type)
- Add authorized redirect URIs:
- Development:
http://localhost:3000/api/auth/callback/google - Production:
https://your-domain.com/api/auth/callback/google
- Development:
- Copy Client ID and Client Secret
- Create account at openrouter.ai
- Go to API Keys
- Create new API key and copy it
- Get your OpenRouter HTTP Referer and App Title from the dashboard
Cloudflare AI Gateway This project uses Cloudflare AI Gateway for AI calls.
- Go to Cloudflare Dashboard > AI > AI Gateway
- Create a gateway
- Store your OpenRouter token in Cloudflare Secrets Store
- You will use
CF_AI_GATEWAY_*environment variables
Create .env.local for development:
# Generate a secure secret
openssl rand -base64 32
# Copy to .env.local
BETTER_AUTH_SECRET=your-generated-secret
BETTER_AUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
# Cloudflare AI Gateway (BYOK - OpenRouter key stored in CF Secrets Store)
CF_AI_GATEWAY_ACCOUNT_ID=your-account-id
CF_AI_GATEWAY_ID=your-gateway-id
CF_AIG_AUTH_TOKEN=your-gateway-auth-token
See .env.example for complete template with all options.
-
Apply database migrations
bun run db:migrate:prod
-
Set production secrets
bunx wrangler secret put BETTER_AUTH_SECRET bunx wrangler secret put BETTER_AUTH_URL # Also used as app URL bunx wrangler secret put GOOGLE_CLIENT_ID bunx wrangler secret put GOOGLE_CLIENT_SECRET bunx wrangler secret put CF_AI_GATEWAY_ACCOUNT_ID bunx wrangler secret put CF_AI_GATEWAY_ID bunx wrangler secret put CF_AIG_AUTH_TOKEN -
Deploy
bun run deploy
-
Configure custom domain (optional)
- In Cloudflare Dashboard > Workers & Pages > Your Worker
- Add custom domain in Settings > Domains & Routes
# Development
bun run dev # Start dev server at localhost:3000
bun run lint # Biome linting
bun run fix # Biome auto-fix
bun run type-check # TypeScript check
# Build & Deploy
bun run build # Next.js production build
bun run build:worker # OpenNext Cloudflare bundle
bun run preview # Local Cloudflare preview
bun run deploy # Build and deploy to Cloudflare Workers
# Database (D1 + Drizzle)
bun run db:generate # Generate migrations from schema
bun run db:migrate # Apply migrations locally
bun run db:migrate:prod # Apply migrations to production
bun run db:studio # Drizzle Studio UI (port 4984)
bun run db:reset # Wipe local D1 and re-migrate
# Quality
bun run ci # type-check + lint + buildapp/
βββ (auth)/ # /api/auth/* - Better Auth handlers
βββ (public)/ # / and /[handle] - no auth required
β βββ page.tsx # Homepage with upload dropzone
β βββ [handle]/ # Public resume viewer (SSR)
βββ (protected)/ # Auth required pages
βββ dashboard/ # User dashboard
βββ edit/ # Content editor with auto-save
βββ settings/ # Privacy toggles, theme selection
βββ waiting/ # AI parsing status polling
components/
βββ templates/ # Resume templates (MinimalistEditorial, etc.)
βββ ui/ # Reusable UI components (shadcn/ui)
lib/
βββ auth/ # Better Auth configuration
βββ db/ # Drizzle schema and client
βββ schemas/ # Zod validation schemas
βββ utils/ # Utility functions
Allows anonymous users to upload before authenticating:
1. POST /api/upload β Upload file directly to Worker
2. Worker stores in R2 β Store temp key in localStorage
3. User authenticates β Google OAuth
4. POST /api/resume/claim β Link upload to user, trigger parsing
5. Poll /api/resume/status β Wait for AI parsing (~30-40s)
Before rendering public profiles:
- Phone numbers: Hidden by default
- Addresses: City/State only (full address hidden)
- Email: Public (for contact)
- User controls visibility in settings
Four built-in templates in components/templates/:
| Template | Description |
|---|---|
| MinimalistEditorial | Serif fonts, editorial aesthetic (default) |
| NeoBrutalist | Bold borders, high contrast |
| GlassMorphic | Blur effects, dark background |
| BentoGrid | Mosaic grid layout |
All templates receive content (ResumeContent) and user props, respect privacy settings, and are mobile-responsive.
- Application-Level Authorization: All data access controlled in code
- Rate Limiting: 5 uploads/day, 10 updates/hour per user
- Input Validation: Zod schemas on all endpoints
- XSS Protection: React's default sanitization
- Encrypted Secrets: All secrets encrypted in Cloudflare
See SECURITY.md for security policy and vulnerability reporting.
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
- Fork the repository
- Create a feature branch (
git checkout -b feat/amazing-feature) - Use conventional commits (
feat:,fix:,docs:) - Run quality checks (
bun run ci) - Submit a pull request
bun run type-check # See all errors
bun run build # Fix errors and rebuild- Verify
BETTER_AUTH_URLincludeshttps://for production - Check redirect URIs match in Google Cloud Console
- Clear browser cookies
- Check R2 CORS includes your domain
- Verify R2 bucket binding is configured in
wrangler.jsonc - Confirm bucket name in binding matches actual bucket
- Verify CF AI Gateway config and OpenRouter BYOK setup
- Check PDF isn't corrupted
- Use retry button (max 2 retries)
You're on Cloudflare Workers. Use R2 bindings for file operations.
MIT License - see LICENSE for details.
- Next.js - React framework
- Better Auth - Authentication
- Drizzle ORM - Type-safe database
- Cloudflare - Edge infrastructure
- OpenRouter - AI API gateway
- OpenAI - AI inference
- shadcn/ui - UI components (built on Radix UI + Tailwind CSS)
Built with TypeScript. Deployed on the edge. Designed for speed.