Before you start
You need:- Node.js 24. The version is pinned in
.nvmrcand the rootenginesfield, sonvm useorfnm usepicks it up. - pnpm 12. The root
packageManagerfield pins the exact version (currently[email protected]), and pnpm switches to it automatically when you run it inside the repository. - Docker with Docker Compose for PostgreSQL, Redis, and S3-compatible storage. Start the Docker daemon first.
- Git.
Set up your checkout
Run every command from the repository root unless a step says otherwise.1
Clone the repository
2
Install dependencies
3
Start the infrastructure services
Wait until
ps shows the services as healthy. PDF generation runs on the Forme engine in WebAssembly, so you don’t need a Chromium or Browserless container.4
Create your environment file
Copy the template only if you don’t have a The template uses container hostnames. Because the app runs on your machine, change these values in Then generate the secrets:
.env.local yet:.env.local to localhost:AUTH_SECRET is required. ENCRYPTION_SECRET (at least 32 characters) is needed only for saved AI providers and the assistant, but it’s easiest to set it now. Redis is optional; with it, assistant replies survive a page reload and rate limits are shared between server processes. Every variable is described in Environment variables.5
Start the app
6
Create a local account
Select Sign up and create an account. Without SMTP settings, the app doesn’t send email: verification and password-reset links are printed in the terminal running
pnpm dev. Copy the link from there into your browser.What pnpm dev runs
pnpm dev loads .env.local through dotenvx and starts three processes with Turborepo:
Use
pnpm dev:web to start only Vite. API calls still need a server running.
Everyday commands
Prefer package-scoped commands while you work. Package names come from each
package.json: the apps are web and server, and shared packages are @reactive-resume/<name>.
Work with the database
All three load
.env.local before calling Drizzle Kit, which doesn’t read .env files by itself.
To change the schema:
- Edit the tables in
packages/db/src/schema/*. - Run
pnpm db:generate. The migration is written to the rootmigrations/folder. - Read the generated SQL, then apply it with
pnpm db:migrateor by restartingpnpm dev.
Run tests
Unit and integration tests
Tests use Vitest and sit next to the code they cover as*.test.ts(x) or *.spec.ts(x). Most packages run in Node; packages/ui uses happy-dom.
PDF export text tests require Poppler’s pdftotext to read logical Unicode text, including /ActualText spans for shaped scripts and emoji. Install it with brew install poppler on macOS or sudo apt-get install poppler-utils on Debian/Ubuntu. CI installs it before running the unit suites.
test. An extra -- stops Vitest from filtering the run. Most test scripts use --passWithNoTests, so a green run with zero tests proves nothing about your change.
Database integration suites need COVER_LETTER_TEST_DATABASE_URL, INTEGRATIONS_TEST_DATABASE_URL, and OAUTH_TEST_DATABASE_URL. The migration suite uses COVER_LETTER_TEST_DATABASE_URL as an administrative connection and creates and deletes its own disposable databases, so its user needs CREATE DATABASE permission. Give the OAuth suite its own database, because it writes signing keys. Never point test variables at a database with real data. .github/workflows/e2e.yml shows the full setup.
Browser tests
End-to-end tests use Playwright and live intests/e2e/specs, with fixtures in tests/e2e/fixtures. They cover sign-up and sign-in, section editing and autosave, JSON export and import, public sharing with statistics and passwords, OAuth consent for MCP clients, and the assistant against a scripted AI provider.
Playwright starts the built server (node apps/server/dist/index.mjs) in production mode and waits for /api/health, so build first. Use a disposable database, and export the variables yourself; these scripts don’t load .env.local.
FLAG_ALLOW_UNSAFE_AI_BASE_URL=true, the assistant spec skips itself. Playwright runs Chromium with no retries. Locally it reuses a server that’s already running on PORT. See tests/e2e/README.md for the full recipe.
Keep the unsafe AI and OAuth redirect flags for isolated test installations. They relax SSRF and redirect protections.
Add translatable text
The web app uses Lingui. Wrap every user-facing string in a macro:pnpm lingui:extract. It updates the catalogs in apps/web/locales/*.po and regenerates the PDF section-title catalog. You only add English strings; translators handle the rest on Crowdin. See Contributing translations.
Code style
- TypeScript is strict, including
exactOptionalPropertyTypesandnoUncheckedIndexedAccess. Packages type-check withtsgo --noEmit. - Oxlint checks code, including native React Compiler and accessibility rules. Oxfmt formats with tabs, double quotes, and 120-column lines. It sorts imports into type, Node, test, external, workspace, and local groups, and sorts Tailwind classes in
clsx,cva, andcn. Side-effect imports retain their order. Install the recommended Oxc editor extension for fixes and formatting on save. - Configure lint rules in
.oxlintrc.jsonand formatting in.oxfmtrc.json. Generated files and byte-sensitive CSS fixtures stay excluded. Async test doubles are exempt fromrequire-await; source functions still require it. The Playwright fixture adapter is exempt fromrules-of-hooksbecause itsusecallback belongs to Playwright. CSS declarations are formatted, but Oxlint does not lint them. @shadcn/lintis registered with Oxlint. Design-system rules are opt-in; add your chosen rules in.oxlintrc.json, scoped toapps/webandpackages/ui. Existingcomponents.jsonfiles identify the shared component exports and Tailwind theme. See its rules and configuration examples.- Coding agents should run
pnpm exec oxlint --fix <changed paths>, thenpnpm exec oxfmt <changed paths>, and finish withpnpm lint:agentandpnpm format:check. Follow the Oxc coding-agent workflow; fix findings and give any necessary inline suppression a rule-specific explanation. - React components with explicit props use a named props type, such as
type FooProps = {...}withfunction Foo(props: FooProps).
Commits and pull requests
The Git hooks run automatically:- Before each commit, Lefthook checks staged files for merge conflict markers, applies safe Oxlint fixes, runs Oxfmt, then checks remaining lint findings with warnings denied and stages the fixes.
- On each commit message, commitlint enforces Conventional Commits, such as
fix(pdf): keep the timeline dot roundordocs: update the development guide.
- Run the type checks and tests for the packages you changed, plus
pnpm lintandpnpm format:check. - Run
pnpm exec turbo boundariesif you changed imports, exports, or shared contracts. - Run
pnpm buildif you changed runtime or bundling behavior. - Keep the pull request focused. Describe the problem, the new behavior, and the checks you ran, and link the related GitHub issue.
Keep credentials and personal resume data out of code, logs, test fixtures, issues, and pull requests.
Troubleshooting
Port 3000 or 3001 is already in use
Port 3000 or 3001 is already in use
Stop the other process, or set different values for
PORT and SERVER_PORT in .env.local and change APP_URL to match. Keep port 3002 free for the email preview.The database connection is refused
The database connection is refused
Check that the containers are healthy with
docker compose -f compose.dev.yml ps. Code on your machine connects to localhost; code inside a container uses the service name, such as postgres.Uploads fail with S3 errors
Uploads fail with S3 errors
Read the storage logs:Check that
S3_ENDPOINT is http://localhost:8333 and the bucket exists. If you don’t need S3, set STORAGE_BACKEND=local.Type errors about routes after pulling or adding a route
Type errors about routes after pulling or adding a route
apps/web/src/routeTree.gen.ts is generated. Start pnpm dev (or run pnpm build) to regenerate it, and never edit it by hand.A server dependency fails to load on Vercel
A server dependency fails to load on Vercel
CommonJS server dependencies must be bundled. Add the package and its dependencies to
bundledInteropPackages in apps/server/tsdown.config.ts. See Deployment checks.Next steps
Project architecture
Learn where each part of the code lives and where new code belongs.
GitHub repository
Browse the source, open issues, and send pull requests.