Skip to content

Quickstart

Markdown

Two routes to a running directory: the CLI scaffolds a project and asks you a few questions; cloning the repository gives you the starter exactly as it ships, demo data included. Both end at the same place — pnpm dev, and the demo site on your machine with no account, no key and no database of your own.

You need Check You should see
Node 22.18 or newer — Node 24 is what the starter is tested on and what .nvmrc pins node --version v24.x.x (or v22.18.0 and up)
pnpm — the repository pins the version in package.json, and corepack enable installs that one pnpm --version a 10.x version
git — to clone, and to pull updates later git --version any version

wrangler is a dependency of the project, not something you install: pnpm exec wrangler runs the pinned one.

  1. Scaffold.

    Terminal
    npm create zerodirs@latest my-directory -- --pro # npm needs the `--`
    pnpm create zerodirs my-directory --pro
    yarn create zerodirs my-directory --pro
    bun create zerodirs my-directory --pro

    Without --pro you get the free MIT edition, which is the same code with the paid half deleted — see Free vs Pro. With --pro the CLI clones the private starter repository using your own git credentials; it never handles a licence key and never signs you in to anything.

  2. Answer the questions.

    The site name, the domain, a sample dataset, a theme, and — on the paid template — whether you want public submissions and how you want to send email. --yes skips all of it and takes the conservative answer to every question: no submissions, no payments, console email, the free plan. Each alternative needs an account or a key a non-interactive run cannot have, so --yes refusing them is the point.

    create-zerodirs [directory] [options]
    --pro Use the paid starter template (default: the free MIT edition)
    --dataset <name> ai-tools | indie-tools | local-businesses
    --template-dir <path> Copy from a local directory instead of fetching a release
    (env: ZERODIRS_TEMPLATE_DIR)
    --name <name> Site name
    --domain <host> Production domain, e.g. dir.example.com
    --theme <preset> default | warm | mono
    --package-manager <pm> pnpm | npm | yarn | bun
    -y, --yes Accept every default; ask nothing
    --no-cloudflare Never call wrangler; create no Cloudflare resources
    --no-install Do not install dependencies
  3. Read SETUP.md.

    The CLI rewrites site.config.ts through its AST, so the file keeps its comments and keeps type-checking; the same for wrangler.jsonc. It generates .dev.vars with a fresh ADMIN_SECRET and TOKEN_SECRET, loads the chosen dataset into src/content/, and writes a SETUP.md listing everything it deliberately left to you. It does not sign you in, upload a secret, run a deploy, write to a remote database, run git init, install anything globally, send telemetry, or overwrite a directory that already has files in it.

    The site’s whole shape — the URL scheme, the noun for a listing, the structured-data type, the categories and tags — comes from the dataset’s own meta.json. A dataset declaring kind: "business" produces seo.jsonLd.listing: 'localBusiness', which is what makes the detail pages emit LocalBusiness structured data.

Terminal
pnpm dev

Before the dev server starts, pnpm dev does three things a fresh clone would otherwise discover one error at a time, and prints a line for each:

dev: wrote .env from .env.example — LISTINGS_SOURCE=files, the default for a local build
dev: wrote .dev.vars from .dev.vars.example — placeholder secrets for local development; pnpm cf:setup replaces them
dev: applied 2 migrations to the local D1 (.wrangler/state)

It never touches a Cloudflare account and never writes over a file that exists. On the next run the first two lines are gone and the third says dev: local D1 is up to date.

Open You should see
The site http://localhost:4321/ the demo directory: the AI-tools dataset, its categories and tags
Submissions http://localhost:4321/submit/ the first step of the form. It works locally against the Miniflare database that was just migrated
The admin http://localhost:4321/admin/login/ the login form. The password is ADMIN_SECRET in .dev.vars — a placeholder, and the configuration report on /admin/ says so