A modern, full‑stack CV builder with live editing, strong validation, drag‑and‑drop section ordering, and SEO‑friendly public profiles at /@{username}. Built with Next.js 15, React 19, Tailwind CSS 4, and Convex for data + auth.
Note: Not related to the computer vision library “OpenCV”. This project’s short name is “Open CV”.
- Profile editor: name, title, location, bio, contact links, experience, education, skills, projects, certifications, volunteering, exhibitions, awards
- Live preview: real‑time preview that mirrors saved output
- Validation: comprehensive Zod schemas, cross‑field checks (date ranges), URL/email normalization, unique skills
- Drag & drop: reorder content sections with
@dnd-kitand persist order - Public profiles: toggle
Publicto publish at/@{username}with SSR, metadata, and JSON‑LD for SEO - Auth: email/password via
@convex-dev/auth - Typed data model: strict Convex schema with indexes and owner checks
- Light, Dark, and System (default is System)
- Implemented with
next-themesusingclasson<html> - Toggle is fixed top-right on all routes; preference persists
- Frontend: Next.js 15 (App Router), React 19, Tailwind CSS 4, shadcn‑style UI primitives, Framer Motion
- Forms/validation:
react-hook-form,zod - Drag & drop:
@dnd-kit/core,@dnd-kit/sortable - Backend: Convex (database, server functions, auth, HTTP router)
- Auth: Temporary
@convex-dev/auth(Password provider) - Tooling: TypeScript, ESLint, Prettier; package manager defaults to Bun but npm/pnpm work
/: Marketing landing (unauthenticated for all; authenticated users are redirected client-side to/editor)/editor: Authenticated workspace (sidebar visible)/@{username}or/u/{username}: Public profile view (no sidebar)
- Unauthenticated: marketing landing (hero, features, workflow, gallery, FAQ) with auth modal
- Authenticated:
- No profile →
ProfileSetup(username availability check) - Profile exists →
ProfileEditor(live preview + DnD reordering)
- No profile →
- Next.js SSR page:
app/u/[username]/page.tsxrenders public profiles;middleware.tsrewrites/@{username}→/u/{username}(revalidate: 300s) - Convex HTTP route:
convex/router.tsalso serves/@{username}directly with HTML + metadata (alternative backend‑hosted SSR) - Auth routes are added to the Convex HTTP router in
convex/http.ts
- Schema:
convex/schema.ts(profiles table + Convex Auth tables) - Queries/Mutations:
convex/profiles.tswith strict validators and owner checks - Auth:
convex/auth.tsconfigures Password auth and exposesauth.loggedInUser
Defined in convex/schema.ts with indexes for efficient lookups.
Table: profiles
- Ownership:
userId: Id<'users'> - Identity:
username: string(indexed, unique by logic) - Basics:
name,title?,location?,bio? - Contact:
email?,website?,github?,linkedin?,twitter? - Arrays:
experience[]:{ id, role, company, startDate, endDate?, current, description? }education[]:{ id, degree, school, startDate, endDate?, current, description? }skills[]: string[]projects[]:{ id, title, year, company?, link?, description? }certifications[]:{ id, name, issuer, year?, credentialId?, link?, description? }volunteering[]:{ id, role, organization, startDate, endDate?, current, description? }exhibitions[]:{ id, title, venue?, year, location?, link?, description? }awards[]:{ id, title, issuer, year, link?, description? }
- Presentation:
sectionsOrder?: string[],isPublic: boolean - Indexes:
by_user(userId),by_username(username)
Queries
auth.loggedInUser(): current user ornullprofiles.getMyProfile(): current user’s profile ornullprofiles.getProfileByUsername({ username }): public profile ornullprofiles.checkUsernameAvailable({ username }):trueif unused
Mutations
profiles.createProfile({...}): one profile per user; enforces uniqueusername; initializes arrays; setsisPublic=falseprofiles.updateProfile({...}): updates full profile; enforces ownership
HTTP
GET /@{username}(Convex HTTP router): server‑renders profile HTML with OG/Twitter/JSON‑LD; returns 404 when missing or private
- Cross‑field checks: start/end month ordering; end date omitted when
current=true - Normalization: trims values; coerces blank optionals to
undefinedbefore saving; deduplicates skills (case‑insensitive) - Section order:
sectionsOrderis persisted; unknown IDs are ignored; editor guards against duplicates - Public toggle:
isPubliccontrols visibility; public fetches never leak private profiles
Prereqs: Node.js 18+ (or Bun 1.1+)
Install
# with bun
bun install
# or with npm
npm installRun (Next + Convex in parallel)
# with bun
bun run dev
# or with npm
npm run devScripts
dev:next devandconvex devin parallelbuild:next buildstart:next startlint: typechecks Convex + app and lints
NEXT_PUBLIC_CONVEX_URL: Convex deployment URL; defaults tohttp://localhost:3210inapp/ConvexClientProvider.tsxCONVEX_SITE_URL: Used byconvex/auth.config.tsto configure auth application domainNEXT_PUBLIC_SITE_URL: Used inapp/u/[username]/page.tsxto generate canonical/OG/Twitter URLs
Create .env.local in the project root for Next variables, and configure Convex env via the Convex dashboard/CLI for production.
Frontend: Vercel (see vercel.json)
- Build command:
bun run build(ornpm run build)
Backend: Convex Cloud
- Deploy Convex functions/schema:
npx convex deploy - Set
NEXT_PUBLIC_CONVEX_URLin Vercel to your Convex prod URL - Set
CONVEX_SITE_URLto your public site domain - Set
NEXT_PUBLIC_SITE_URLto the same public URL (for metadata)
Public Profiles
- Vercel serves
/@{username}viamiddleware.ts→app/u/[username] - Alternatively serve directly from Convex HTTP
/@{username}
- Ownership checks: mutations derive
userIdfrom server context (getAuthUserId); only owners can write - Public access: public queries only return data when
isPublic=true - HTML escaping: Convex HTML renderer escapes user content in
convex/router.ts
app/: Next.js routes, layout, providerscomponents/: UI, editor sections, landing, public viewconvex/: schema, auth, queries/mutations, HTTP routerlib/: shared types/utilities
Issues and PRs are welcome. Please include a clear rationale and concise changeset. Keep changes minimal and consistent with existing patterns. Type first, validate at boundaries, and avoid unnecessary abstractions.
- Change auth from beta convex to a solved 3rd party auth provider
- PDF export / print‑ready layout
- Themes and shareable presets
- Custom domains for public profiles
- Media support (avatar, images via Convex storage for projects, exhibitions, awards)
- Unlisted links / passcode‑protected profiles
MIT.