Local setup
Every command in the order it is needed, what a fresh copy of the folder does, and the three gates that must stay clean.
This page is for the person who would rather type than click. Everything the installer does is available as a command, and running the app in development is four commands from a fresh copy.
From nothing to a running app
node --version # 22.6 or newer, please
pnpm install # the packages
pnpm configure # the same walk the installer does
pnpm dev # http://localhost:3000pnpm configure needs no packages: it is a script that Node runs directly, so it works on a copy that has
never been installed. It writes .env.local, keeps a dated backup of the previous one, and prints a masked
hint for each value rather than the value itself.
The commands that exist
| Command | What it does |
|---|---|
pnpm install | Installs the packages |
pnpm configure | The guided walk over the nineteen names |
pnpm configure --dry-run | Says what it would write, writes nothing |
pnpm configure --only=deployment | Only the twelve names the deployment reads |
pnpm dev | Development server on port 3000, with hot reload |
pnpm build | A production build |
pnpm start | Serves the production build |
pnpm test | The test suite |
pnpm typecheck | TypeScript, no emit |
pnpm lint | ESLint |
npx convex dev | Waits on the schema and functions of a development deployment and pushes changes |
npx convex dev --once | Pushes once and exits, for a script |
npx convex deploy | Pushes to the production deployment |
npx convex env set NAME value | Sets one deployment variable |
npx convex env list | Lists the names the deployment has (not their values) |
npx convex run admin:bootstrap | Creates the first administrator |
`pnpm start` serves what was built, not what is in the editor
If the app's behaviour disagrees with the code you just changed, you are
looking at the previous build. Run pnpm build again, then pnpm start.
A fresh copy of the folder, step by step
What actually happens the first time, in order, with the points where people get stuck:
Node is checked, by version rather than by command name
The scripts are TypeScript files that Node runs by stripping the types. That works from 22.6 and refuses
earlier versions. Two Node installations on one machine is the usual cause of a complaint, and
node --version in the same terminal tells you which one is being used.
Convex is created or connected
npx convex devThis signs in, creates a development deployment, and prints the address. That address is
NEXT_PUBLIC_CONVEX_URL, and without it the app serves nothing at all.
The nineteen names
pnpm configure walks them. Seven go into .env.local, which the Next.js process reads. Twelve are set on
the deployment with npx convex env set, because that is where the functions that read them run.
The first administrator
npx convex env set ADMIN_EMAIL "you@your-domain.com"
npx convex env set ADMIN_PASSWORD "a long one"
npx convex run admin:bootstrapThe app
pnpm devA blank page for a few seconds is normal: Next.js serves nothing until the first compile finishes.
Two environments, side by side
This is the single most common source of confusion, so it is worth saying twice. Development and production are two deployments with two environments, and neither one reads the other's file.
# development: the one pnpm dev talks to
npx convex dev # pushes and watches
npx convex env set NAME value # sets a name there
# production: the one the deployed app talks to
npx convex deploy
npx convex env set NAME value --prodThe app's own half is a file, so it is per machine: .env.local on your laptop and a different set of values
on the server, or the same ones if that is what you want. Everything in .env.local is written with
restrictive permissions and ignored by version control.
The three gates
The project is shipped with three checks and all three are clean. They are the ones to run before you change anything of your own, because a change that breaks one of them is a change you will have to find later:
pnpm typecheck # TypeScript
pnpm lint # ESLint
pnpm test # Vitest`allowImportingTsExtensions` is deliberate
The configuration scripts in scripts/ and the installer server in
installer/ import their helpers with a .ts extension, because Node's type
stripping does not guess extensions the way a bundler does. Both the app's
tsconfig.json and the one under convex/ allow it on purpose. Removing the
flag breaks the installer, not the app.
Where to look when you want to change something
| Layer | Folder | What lives there |
|---|---|---|
| Routes | app/ | Marketing, product, admin, auth, API routes, and content/ for the docs |
| Interface | components/ | One folder per area, plus ui/ for the primitives |
| Rules | lib/ | Pure functions with tests beside them: plans, credits, limits, flags |
| Backend | convex/ | Schema, queries, mutations, actions, migrations, schedules |
| Documentation | content/docs/ | The manual you are reading, in MDX |
The panel is the answer to most "where do I change" questions, and the last section of the README is a table of them. Code is the answer for everything else.

