Skip to content

About

Your resume, reimagined πŸ“

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

1,024 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 TanStack Start


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 TanStack Start (React 19, Vite, file routes + server functions)
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 image optimizer: Use <img> with CSS instead
  • No DB in the Worker gate: The Worker entry only strips/forwards requests; pages and APIs authenticate themselves
  • 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
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

Set the public values in wrangler.jsonc β†’ vars, not as secrets:

  • CLERK_PUBLISHABLE_KEY = your Clerk publishable key (pk_…)
  • UMAMI_WEBSITE_ID = your Umami website id (public; the tracker script prints it)
  • APP_URL = https://your-domain.com

Step 8: Deploy

DATABASE_URL="postgres://…" pnpm run deploy

Deploy builds, migrates, backfills NULL content_indexable flags, applies R2 lifecycle rules, and deploys the Worker. It then backfills rows updated since deploy start minus one hour and sends a best-effort IndexNow ping.

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

Important: After the domain is connected, update wrangler.jsonc β†’ vars 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

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

    DATABASE_URL="postgres://…" 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 (TanStack Start + Cloudflare plugin)
pnpm run preview          # Local Cloudflare preview
pnpm run deploy           # build β†’ migrate β†’ NULL backfill β†’ R2 lifecycle β†’ deploy β†’ since backfill β†’ 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 coverage (including worker/) and enforces the measured floors in vite.config.ts (test.coverage.thresholds).

Dependency Updates

Dependabot checks weekly (Monday 09:00 UTC) with a 7-day cooldown, so a just-published release waits a week 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 minor/patch PR because they only move in lockstep. Other patches arrive as one grouped PR; minors and majors get one PR per package. GitHub Actions updates are grouped.

Nothing merges automatically: the owner reads each PR's changelog, migrates code where needed and merges by hand.

Project Structure

app/
β”œβ”€β”€ routes/              # TanStack Router file routes (routeTree.gen.ts is generated)
β”‚   β”œβ”€β”€ api/             # Server routes (webhooks/clerk, upload, resume, etc.)
β”‚   β”œβ”€β”€ admin/           # Admin dashboard (route.tsx gates; users, resumes, analytics)
β”‚   β”œβ”€β”€ _protected/      # Auth-gated pages: dashboard, edit, settings, themes, waiting, wizard
β”‚   β”œβ”€β”€ @{$handle}.tsx   # Public resume viewer /@handle
β”‚   β”œβ”€β”€ for/             # Landing pages by profession
β”‚   β”œβ”€β”€ blog/            # Blog posts & content marketing
β”‚   β”œβ”€β”€ preview/         # Template preview (demo data)
β”‚   β”œβ”€β”€ index.tsx        # Homepage
β”‚   └── __root.tsx       # Root document (ClerkProvider, head, scripts)
β”œβ”€β”€ router.tsx           # Router factory (error/not-found defaults)
β”œβ”€β”€ client.tsx           # Client entry (hydration + browser telemetry)
└── 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, 15-minute stranded-resume sweep
β”œβ”€β”€ 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             # Worker entry (TanStack Start handler + Workflows + Cron + Queue + WebSocket auth)
└── cached-pages.ts      # CachedPages entrypoint: Workers Cache for public pages + purges

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

tests/
β”œβ”€β”€ integration/         # Integration tests
β”œβ”€β”€ security/            # Security tests (IDOR, rate limits)
└── helpers/             # Shared fixtures, including PGlite; tests use native Node WebCrypto

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)
*/15 * * * * Every 15 minutes Fails resumes stuck in pending_claim/queued (over 15 min) or processing (over 90 min); the user can retry them from the dashboard

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


Resume Templates

Built-in templates in components/templates/ (metadata in THEME_METADATA, lib/templates/theme-ids.ts):

Template Category Description
Academic CV Professional A scholarly CV with education first, dated appointments and numbered research outputs
Bento Grid Modern Flat colour tiles for your current role, a standout result and your full career
Blueprint Creative A cyanotype drawing sheet with grid paper, section callouts and a title block
Boardroom Professional A dark executive ledger with a pinned identity column and brass accents
Bold Corporate Professional Annual-report layout with a navy sidebar and condensed headings
Broadsheet Professional A newspaper front page: ruled columns, a masthead name and each role set as a story
Case File Professional A typed dossier in a manila folder, with each section filed as a lettered exhibit
Classic ATS Professional A printable one-column resume sheet that applicant tracking systems parse cleanly
Contact Sheet Creative Film-frame tiles on near-black with grease-pencil orange, built for photographers
DesignFolio Creative Swiss grid on cool grey with a cobalt name block. Project images lead the page.
DevTerminal Developer GitHub-style profile with pinned projects and a commit timeline
Gallery Wall Creative An exhibition wall with hung works on labels and a printed artist CV below
Glass Morphic Modern Frosted glass panes lit by one window of light, with a reeded-glass insert
Lesson Plan Creative A ruled planner page with binder holes, coloured subject tabs and a sticky note
Liner Notes Creative An album sleeve with the record sliding out and your jobs listed as Side A tracks
Link Stack Modern A link-in-bio page of full-width colour bars, ultramarine to peach, each one a link to tap or a role to open
Manuscript Professional A book spread with a title page, a table of contents and bylines as a bibliography
Media Kit Modern A creator's brand one-sheet: a fuchsia cover, a wordmark wall of who you've worked with and your real results
Midnight Modern Midnight blue night sky, Garamond headings and a gold star timeline
Minimalist Editorial (default) Professional Quiet single serif column with dates set in the margin
Neo Brutalist Creative Loud yellow poster with a giant name, hard shadows and a skills ticker
Open House Creative A real-estate listing sheet: your name on a hanging yard sign, a property-details table and roles filed as listings
Personal Best Modern A track meet: your name painted across the lanes, real marks on an LED board and roles as a results sheet
Pitch Deck Modern Your resume as a 16:9 founder deck: title slide, one-liner, traction and the ask
Quota Professional A sales resume with a closed-won board of the real figures from your highlights and a dated track record
Reel Creative A film-editing timeline with clip markers, timecode dates and a red playhead
Retro OS Creative A late-90s desktop where every section opens in its own bevelled window
Rounds Professional A nurse's ID badge on a lanyard, with licenses first and experience as a shift log
Scrapbook Creative Craft-paper desk with taped index cards, sticky-note skills and a polaroid portrait
Spotlight Creative A dark stage with a hard-edged followspot on your name and a theatre programme of credits
The Brief Professional A legal brief: a boxed caption header, roman-numeral sections and double-spaced serif paragraphs
The Menu Creative A bistro prix-fixe card on a gingham tablecloth, with roles priced in years along dotted leaders
Workspace Modern A clean document page with a cover, a properties table and collapsible sections

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 the quality gate and repo traps, and CODING_STANDARDS.md for what review checks.

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 / 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