Skip to content

About

Your resume, reimagined πŸ“

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

882 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

clickfolio.me

Turn your PDF resume into a hosted web portfolio in under 60 seconds.

Upload a PDF. AI parses it. Get a shareable link.

License: MIT Cloudflare Workers vinext


Features

  • Instant PDF Parsing - AI extracts your information automatically
  • Clean Public URLs - Get yoursite.com/yourname immediately
  • 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

Tech Stack

Layer Technology
Framework vinext (Vite-based Next.js)
Runtime Cloudflare Workers
Database PlanetScale Postgres via Cloudflare Hyperdrive + Drizzle ORM (node-postgres pg >= 8.16.3)
Auth Clerk (Google OAuth + credentials; prebuilt <SignIn>/<SignUp> UI, JWKS-verified session JWTs)
Storage Cloudflare R2 (S3-compatible)
AI Parsing OpenRouter via Cloudflare AI Gateway (openai/gpt-oss models)
Styling shadcn/ui + Tailwind CSS 4

Why Cloudflare Workers?

We chose Cloudflare Workers over traditional hosting for several reasons:

Performance

  • 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

Cost Efficiency

  • Free Tier: 100,000 requests/day free
  • Hyperdrive: Free connection pooling and query caching for Postgres at the edge
  • R2 Storage: 10GB free, no egress fees
  • Total: A production app can run free for most use cases (plus PlanetScale/Clerk free tiers)

Developer Experience

  • No Container Management: Just deploy code
  • Automatic Scaling: From 0 to millions of requests
  • Integrated Stack: Hyperdrive, R2, Workflows, and Durable Objects work seamlessly together

Trade-offs

  • No fs Module: Must use R2 for file operations
  • No Next.js <Image /> Component: Use <img> with CSS instead
  • No DB in Middleware: The edge proxy only checks cookie presence
  • Bundle Size: Keep dependencies minimal

Quick Start

Prerequisites

Installation

# Clone the repository
git clone https://github.com/divkix/clickfolio.me.git
cd clickfolio.me

# Install dependencies
pnpm install

# Copy environment template and fill in your values
cp .env.example .dev.vars

# Apply database migrations (needs DATABASE_URL from PlanetScale)
DATABASE_URL="postgres://…" pnpm run db:migrate

# Start development server
pnpm run dev

Open http://localhost:3000


Self-Hosting Guide

Beginner-Friendly Deployment (copy/paste)

If 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 PlanetScale account (Postgres database, free tier is fine)
  • A Clerk account (authentication, free tier is fine)
  • An OpenRouter account (for AI parsing)
  • pnpm installed (copy/paste this in Terminal):
    npm install -g pnpm

Step 0: Get the code

  1. Download the repo ZIP from GitHub and unzip it, or use:
    git clone https://github.com/divkix/clickfolio.me.git
    cd clickfolio.me
  2. Install dependencies:
    pnpm install

Step 1: Create the PlanetScale Postgres database

  1. Create a Postgres database in the PlanetScale console (e.g. clickfolio).
  2. Copy the direct connection string (PlanetScale console β†’ your database β†’ Connect β†’ Postgres URL).
  3. You will use it as DATABASE_URL below. Note: drizzle-kit uses this DIRECT URL; the deployed Worker connects through Cloudflare Hyperdrive instead.

Step 2: Create the Cloudflare Hyperdrive binding

  1. In Terminal:
    pnpm exec wrangler hyperdrive create clickfolio-pg --connection-string="postgres://user:password@host/db"
  2. Copy the printed Hyperdrive id.
  3. Open wrangler.jsonc and put that id under hyperdrive[0].id.

Step 3: Create Cloudflare R2 bucket

  1. Go to Cloudflare Dashboard β†’ R2 β†’ Create bucket.
  2. Name it clickfolio-bucket.
  3. The bucket is accessed via binding in wrangler.jsonc - no API tokens needed.

Step 4: 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 5: Set up Clerk

  1. Create an application at clerk.com β†’ copy the Publishable key (pk_…) and Secret key (sk_…).
  2. Enable the Google social connection in Clerk's dashboard (Clerk hosts the OAuth app β€” no separate Google Cloud project needed).
  3. Create a webhook in Clerk β†’ Webhooks pointing to https://your-domain.com/api/webhooks/clerk, subscribing to user.created, user.updated, user.deleted. Copy the signing secret (whsec_…).

Step 6: Set up OpenRouter

  1. Create OpenRouter account β†’ API Keys.
  2. Copy your API key.

Step 7: Add secrets to Cloudflare (production) Run each command and paste the value when prompted:

pnpm exec wrangler secret put CLERK_SECRET_KEY                 # sk_… from Clerk
pnpm exec wrangler secret put CLERK_WEBHOOK_SECRET             # whsec_… from Clerk webhook
pnpm exec wrangler secret put NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY # pk_… from Clerk
pnpm exec wrangler secret put APP_URL                          # https://your-domain.com
openssl rand -base64 32                                        # then:
pnpm exec wrangler secret put PENDING_UPLOAD_SECRET            # random value from openssl above
pnpm exec wrangler secret put CF_AI_GATEWAY_ACCOUNT_ID
pnpm exec wrangler secret put CF_AI_GATEWAY_ID
pnpm exec wrangler secret put CF_AIG_AUTH_TOKEN

Step 8: Deploy

DATABASE_URL="postgres://…" pnpm run deploy   # build β†’ db:migrate β†’ R2 lifecycle β†’ wrangler deploy

Step 9: Add your domain Cloudflare Dashboard β†’ Workers & Pages β†’ your worker β†’ Settings β†’ Domains & Routes.

Important: After domain is connected, update these secrets and redeploy:

  • APP_URL = https://your-domain.com
  • Point the Clerk webhook endpoint URL at https://your-domain.com/api/webhooks/clerk

Email notifications: Permanent resume parse failures and unresolved cache waits send a dashboard link to the owner's verified Clerk account email. Email delivery is best effort and never blocks the resume status update. Successful parses do not email. Use a Workers Paid plan and onboard your sending domain under Cloudflare Dashboard β†’ Compute β†’ Email Service β†’ Email Sending β†’ Onboard Domain. Set EMAIL_FROM and the EMAIL binding's allowed_sender_addresses in wrangler.jsonc to your sender (default: notifications@clickfolio.me). No email API key is needed. See Cloudflare's sending setup.

Local email sends are simulated. Adding remote: true to the EMAIL binding sends real mail during development; only enable it for deliberate delivery testing. Cloudflare Email Sending is currently beta. Email runs in its own journaled workflow step with automatic retries disabled: normal replay reuses the completed step, but a crash after provider acceptance and before persistence can still duplicate a notification. Delivery errors are logged, without recipient addresses, and are not retried.

Then redeploy:

pnpm run deploy

If you followed the steps above, the site should be live at your domain.

Step 1: Cloudflare Setup

  1. Create a Cloudflare account at cloudflare.com

  2. Create the Hyperdrive binding over your PlanetScale Postgres:

    pnpm exec wrangler hyperdrive create clickfolio-pg --connection-string="postgres://…"

    Copy the id to wrangler.jsonc

  3. 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
  4. 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
      }
    ]

Step 2: Clerk Setup

  1. Create an application at clerk.com
  2. Copy the Publishable key (pk_…) and Secret key (sk_…)
  3. Enable Google sign-in in Clerk's dashboard (Clerk manages the OAuth app)
  4. Add a webhook endpoint https://your-domain.com/api/webhooks/clerk subscribed to user.created, user.updated, user.deleted; copy its signing secret (whsec_…)

Step 3: OpenRouter + Cloudflare AI Gateway (required)

  1. Create account at openrouter.ai
  2. Go to API Keys
  3. Create new API key and copy it
  4. Get your OpenRouter HTTP Referer and App Title from the dashboard

Cloudflare AI Gateway This project uses Cloudflare AI Gateway for AI calls.

  1. Go to Cloudflare Dashboard > AI > AI Gateway
  2. Create a gateway
  3. Store your OpenRouter token in Cloudflare Secrets Store
  4. You will use CF_AI_GATEWAY_* environment variables

Step 4: Environment Variables

Create .dev.vars for development:

# Generate a secure secret with: openssl rand -base64 32

APP_URL=http://localhost:3000
PENDING_UPLOAD_SECRET=your-generated-secret

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_…
CLERK_SECRET_KEY=sk_test_…
CLERK_WEBHOOK_SECRET=whsec_…

# Direct PlanetScale URL β€” used ONLY by drizzle-kit locally (db:migrate/push/studio).
# The dev Worker itself connects through the local Hyperdrive simulation.
DATABASE_URL=postgres://user:password@host/clickfolio

# 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.

Step 5: Deploy to Cloudflare

  1. Apply database migrations

    DATABASE_URL="postgres://…" pnpm run db:migrate
  2. Set production secrets (see Step 7 of the beginner guide above)

  3. Deploy

    pnpm run deploy
  4. Configure custom domain (optional)

    • In Cloudflare Dashboard > Workers & Pages > Your Worker
    • Add custom domain in Settings > Domains & Routes

Development

Available Scripts

# Development
pnpm run dev              # Start dev server at localhost:3000
pnpm run lint             # Oxlint linting (via vp lint)
pnpm run fix              # Oxlint + Oxfmt auto-fix (via vp check --fix)
pnpm run type-check       # TypeScript check

# Build & Deploy
pnpm run build            # Vite production build (vinext)
pnpm run preview          # Local Cloudflare preview
pnpm run deploy           # scripts/deploy.ts: build, db:migrate, R2 lifecycle, wrangler deploy, IndexNow (needs DATABASE_URL)

# Database (PlanetScale Postgres via drizzle-kit; needs DATABASE_URL except generate)
pnpm run db:generate      # Generate migration files into migrations_pg/
pnpm run db:migrate       # Apply migrations_pg/ to DATABASE_URL
pnpm run db:push          # Sync schema without migration files (prototyping only)
pnpm run db:studio        # Drizzle Studio UI (port 4984)

# Testing
pnpm run test             # Integration and security tests
pnpm run test:integration # Integration tests
pnpm run test:security    # Security tests
pnpm run test:coverage    # All tests + coverage
pnpm run test:ci          # CI mode (JSON reporter)
pnpm run test:ui          # Interactive UI mode

# Quality
pnpm run verify           # check + type-check + knip
pnpm run format           # Format with Oxfmt
pnpm run seo:lastmod      # Update SEO last-modified timestamps
pnpm run ci               # type-check + lint + test + build

Tests cover integration and security flows. CI collects whole-app coverage and enforces the remaining-suite baseline: 24% statements/lines, 16% branches, and 14% functions.

Dependency Updates

Dependabot checks daily with a 1-day cooldown, so a just-published release waits a day before it is proposed (security updates skip the cooldown). Vite+ toolchain packages (vite-plus, the vite catalog alias, vitest, @vitest/*, @voidzero-dev/*) are grouped into one PR because they only move in lockstep.

Kind What happens
minor / patch (grouped per group) Auto-merged once every required check is green
major Left open for a human; review is requested from @Divkix

.github/workflows/dependabot-automerge.yml performs the merge (gh pr merge --squash --match-head-commit <event SHA>) only after it has observed, on the PR's head commit: Code Quality, Tests, Build, CI Success, GitGuardian Security Checks and Workers Builds: clickfolio-me present and exactly successful, every other reported check/commit status exactly successful too (a skipped or neutral check is not green), and its own 45-minute budget not exhausted. Its own check runs are recognised as this workflow's runs on the gated commit (check-suite ids), not by name, so a foreign check sharing that name still has to pass while a cancelled predecessor run cannot deadlock the gate. Author, same-repo head, base branch and head SHA are re-verified on every poll and again immediately before the merge. Missing checks, failures, cancellations, timeouts, a moved head, a retarget, a draft flip, conflicts, fork heads and non-Dependabot authors all fail closed β€” the PR stays open, the run log says why, and a human merges it. The branch is deleted by the repository setting delete_branch_on_merge, not by a merge flag.

The workflow is metadata-only: it runs on pull_request_target, never checks out, installs or executes pull-request code, and receives every event value through env (nothing is interpolated into the script). Repository Actions rule 7006 allows the pull_request_target event for exactly this file and the dependabot[bot] actor, which keeps the November 2026 public-repo pull_request_target restriction from blocking it without widening the event for other workflows. A pull_request_target workflow only runs from the default branch, so this automation starts working once the workflow is on main; a manual re-run of an update may also need a Dependabot-side event to be eligible.

Project Structure

app/
β”œβ”€β”€ api/                 # API routes (webhooks/clerk, upload, resume, etc.)
β”œβ”€β”€ (admin)/             # Admin dashboard pages
β”‚   β”œβ”€β”€ users/       # User management
β”‚   β”œβ”€β”€ resumes/     # Resume management
β”‚   └── analytics/   # Site analytics
β”œβ”€β”€ (protected)/         # Auth-gated pages
β”‚   β”œβ”€β”€ dashboard/       # User dashboard with analytics
β”‚   β”œβ”€β”€ edit/            # Resume content editor
β”‚   β”œβ”€β”€ settings/        # Privacy & theme settings
β”‚   β”œβ”€β”€ themes/          # Theme gallery
β”‚   β”œβ”€β”€ waiting/         # AI parsing status (WebSocket)
β”‚   └── wizard/          # Onboarding wizard
β”œβ”€β”€ [handle]/            # Public resume viewer /@handle
β”œβ”€β”€ for/                 # Landing pages by profession
β”‚   β”œβ”€β”€ student/
β”‚   β”œβ”€β”€ software-engineer/
β”‚   β”œβ”€β”€ designer/
β”‚   β”œβ”€β”€ product-manager/
β”‚   β”œβ”€β”€ marketer/
β”‚   └── consultant/
β”œβ”€β”€ blog/                # Blog posts & content marketing
β”œβ”€β”€ preview/[id]/        # Template preview (before claiming)
β”œβ”€β”€ page.tsx             # Homepage
β”œβ”€β”€ layout.tsx           # Root layout (ClerkProvider wrapper)
└── globals.css          # Global styles

components/
β”œβ”€β”€ templates/           # Resume template components
β”œβ”€β”€ ui/                  # shadcn/ui components
β”œβ”€β”€ auth/                # LoginButton using Clerk's native sign-in modal
β”œβ”€β”€ dashboard/           # Dashboard-specific components
β”œβ”€β”€ icons/               # Custom icon components
β”œβ”€β”€ analytics/           # Analytics components
└── *.tsx                # Shared components (Footer, Logo, etc.)

lib/
β”œβ”€β”€ auth/                # Clerk integration (server JWKS verification, session, client seam)
β”œβ”€β”€ ai/                  # AI parsing (OpenRouter via CF AI Gateway)
β”œβ”€β”€ cron/                # Daily DB cleanup cron
β”œβ”€β”€ db/                  # Drizzle PG schema + getDb(env.HYPERDRIVE)
β”œβ”€β”€ durable-objects/     # WebSocket Durable Object
β”œβ”€β”€ parse/               # Parse step bodies, error classification, alerts, DO notify
β”œβ”€β”€ workflows/           # ResumeParseWorkflow + R2DeleteWorkflow and their triggers
β”œβ”€β”€ schemas/             # Zod validation schemas
β”œβ”€β”€ templates/           # Theme registry & metadata
β”œβ”€β”€ types/               # TypeScript type definitions
β”œβ”€β”€ utils/               # Utility functions
β”œβ”€β”€ blog/                # Blog post data
└── config/              # Site config, FAQ, retry policies

worker/
└── index.ts             # Custom worker entry (vinext + Workflows + Cron + WebSocket auth)

migrations_pg/
└── *.sql                # Postgres migrations (drizzle-kit)

tests/
β”œβ”€β”€ integration/         # Integration tests
β”œβ”€β”€ security/            # Security tests (IDOR, rate limits)
└── setup.ts             # Test configuration

Architecture

The Claim Check Pattern

Allows anonymous users to upload before authenticating:

1. POST /api/upload         β†’ Upload file directly to Worker
2. Worker stores in R2      β†’ Signed pending_upload cookie (HMAC'd with PENDING_UPLOAD_SECRET)
3. User authenticates       β†’ Clerk (Google OAuth or credentials)
4. POST /api/resume/claim   β†’ Link upload to user, trigger parsing
5. Poll /api/resume/status  β†’ Wait for AI parsing (~30-40s)

Privacy Filtering

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

Real-time Updates (WebSocket)

Live status updates during AI parsing:

  • Endpoint: wss://your-domain.com/ws/resume-status?resume_id={id}
  • Technology: Cloudflare Durable Objects (ClickfolioStatusDO)
  • Flow: WebSocket connection β†’ DO tracks parsing progress β†’ Real-time status pushed to client
  • Authentication: Clerk session JWT verified against JWKS before upgrade
  • Use case: Waiting room shows live parsing progress instead of polling

Parse Pipeline (Cloudflare Workflows)

Durable resume parsing with per-step retries:

  • Workflow: ResumeParseWorkflow (lib/workflows/resume-parse-workflow.ts) runs claim β†’ parse β†’ complete
  • Trigger: /api/resume/claim and /api/resume/retry start an instance keyed by the resume id, so a repeated start is a no-op
  • Retries: the parse step retries transient failures 3 times with exponential backoff; permanent errors throw NonRetryableError
  • Failure: once retries are spent the workflow marks the row failed, notifies the Durable Object, and sends an alert
  • Duplicate uploads: an await-cache instance sleeps 10 minutes, then expires a waiting_for_cache row that never resolved
  • Alerting: Cloudflare Logpush by default, optional Slack/Discord webhook on permanent failures
  • User email: a separate best-effort EMAIL step sends a dashboard link after terminal failure or cache expiry, only to verified Clerk account owners; no automatic email retries

R2 Cleanup

  • Anonymous uploads: an R2 lifecycle rule (r2-lifecycle.json, applied by pnpm run deploy) deletes temp/ objects after 1 day
  • Deletions: R2DeleteWorkflow retries failed deletes (account deletion, admin dismiss) with backoff

Scheduled Tasks (Cron)

Cron Time (UTC) Task
0 3 * * * 3:00 AM Database cleanup (expired rate limits, handle history)

Runs via worker/index.ts without self-fetch (avoids double billing).

Referral Program

Removed β€” all 14 templates are now free for every user. No referral gating.


Resume Templates

Built-in templates in components/templates/:

Template Category Description Unlock Requirement
Minimalist Editorial Professional Clean magazine-style layout with serif typography Free (default)
Neo Brutalist Creative Bold design with thick borders and loud colors Free
Glass Morphic Modern Dark theme with frosted glass effects Free
Bento Grid Modern Modern mosaic layout with colorful cards Free
Classic ATS Professional Legal brief typography, ATS-optimized single-column layout Free
DevTerminal Developer GitHub-inspired dark terminal aesthetic for developers Free
DesignFolio Creative Digital brutalism meets Swiss typography with acid lime accents Free
Spotlight Creative Warm creative portfolio with animated sections Free
Midnight Modern Dark minimal with serif headings and gold accents Free
Boardroom Professional Dark executive ledger with a pinned identity column, brass accents Free
Bold Corporate Professional Executive typography with bold numbered sections Free
Broadsheet Professional Newspaper front page: masthead name, ruled columns, roles as stories Free
Case File Professional Typed dossier in a manila folder, sections filed as exhibits Free
Retro OS Creative Late-90s desktop, every section in its own bevelled window Free
Academic CV Professional Scholarly CV: education first, dated appointments, numbered research Free
Contact Sheet Creative Film-frame tiles on near-black with grease-pencil orange for photographers Free
Workspace Modern Clean document page: cover band, properties table, collapsible sections, tag labels Free
Blueprint Creative Cyanotype drawing sheet: grid paper, lettered sections, dimension-line dates, title block Free
Scrapbook Creative Craft-paper desk: taped index cards, sticky-note skills, polaroid portrait Free
Media Kit Modern Bold colour blocks, a results wall pulled from your metrics, a by-the-numbers strip Free
Liner Notes Creative Album sleeve with the record sliding out, jobs as Side A tracks, recordings as Side B Free
Rounds Professional Nurse ID badge on a lanyard, licenses first, career set as a shift log Free
Lesson Plan Creative Ruled planner page with binder holes, coloured subject tabs, sticky note Free
Manuscript Professional Book spread: title page, table of contents, bylines as a bibliography Free
Gallery Wall Creative Exhibition wall: hung works with wall labels and a printed artist CV Free

All templates receive content (ResumeContent) and profile props, respect privacy settings, and are mobile-responsive.


Security

  • Application-Level Authorization: All data access controlled in code
  • Rate Limiting: 5 resume uploads/day per user, plus IP-based limits (10/hour, 50/day) for anonymous uploads
  • Input Validation: Zod schemas on all endpoints
  • XSS Protection: React's default sanitization
  • Encrypted Secrets: All secrets encrypted in Cloudflare; Clerk session JWTs verified against JWKS on every server request
  • Webhook Signatures: Clerk webhooks are Svix-signature verified before processing
  • Privacy Controls: Users control visibility of phone numbers and addresses
  • IP Privacy: IP addresses SHA-256 hashed before storage (GDPR-friendly)

To report a vulnerability, open a GitHub issue with the "security" label or contact the maintainers directly via the repository's GitHub page.


Contributing

Contributions welcome! See AGENTS.md for branch conventions, commit style, and the pnpm run ci quality gate.

Quick Contribution Guide

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/amazing-feature)
  3. Use conventional commits (feat:, fix:, docs:)
  4. Run quality checks (pnpm run ci)
  5. Submit a pull request

Troubleshooting

Build Fails with TypeScript Errors

pnpm run type-check  # See all errors
pnpm run build       # Fix errors and rebuild

Auth Redirect Issues / Users Missing After Sign-Up

  1. Verify CLERK_SECRET_KEY / NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY match the same Clerk application
  2. Confirm the Clerk webhook points at /api/webhooks/clerk and CLERK_WEBHOOK_SECRET matches β€” a signed-in user with no synced row gets 404s until the webhook lands
  3. Clear browser cookies

Database Connection Failures

  1. Verify the Hyperdrive binding id in wrangler.jsonc matches wrangler hyperdrive create output
  2. For db:* scripts, confirm DATABASE_URL is set to the DIRECT PlanetScale connection string
  3. Check PlanetScale console β†’ your database is awake and credentials are valid

R2 Upload Fails

  1. Check R2 CORS includes your domain
  2. Verify R2 bucket binding is configured in wrangler.jsonc
  3. Confirm bucket name in binding matches actual bucket

Parsing Stuck in "Processing"

  1. Verify CF AI Gateway config and OpenRouter BYOK setup
  2. Check PDF isn't corrupted
  3. Use retry button (max 2 retries)

"Cannot find module 'fs'"

You're on Cloudflare Workers. Use R2 bindings for file operations.


License

MIT License - see LICENSE for details.


Acknowledgments


Built with TypeScript. Deployed on the edge. Designed for speed.

About

Your resume, reimagined πŸ“

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages