Files
template-react/CLAUDE.md
T
pastilhasandClaude Opus 5 c08dab4243 Initial commit: Bun · Hono · React SPA · Postgres/Drizzle template
The starting point for an app with a React frontend. Same server, env,
database and API skeleton as template-htmx — the two differ only in what is
served at / — with a React 19 SPA, Tailwind v4 and shadcn/ui on top.

Carried from a working project rather than designed in the abstract, and
carrying the lessons that cost it time:

- The dev script states NODE_ENV=development rather than inheriting it. React
  resolves its runtime against NODE_ENV: Bun emits jsxDEV() in development
  and React's production jsx-dev-runtime defines jsxDEV as void 0. Inherit
  production while transforming for development and the app dies at first
  paint. Verified: the dev bundle resolves the development runtime and
  contains no void-0 stub.
- Asset URLs in components are relative and <base href="/"> is in the HTML,
  so a path-prefix proxy can rewrite one attribute and every asset follows. A
  test enforces it.
- Routing is in the hash, because the app may be mounted under a prefix it
  only learns at runtime and a hash means the same thing at either mount.
- index.html holds __PUBLIC_URL__ and gen-index.ts substitutes it, because
  Bun's HTML bundler fails the build on a root-relative favicon href.
- APP_ENV failing closed, env files before the ambient environment, the
  database enforced by name, one Postgres client per process — as in the
  htmx template.

The database has no tables. The App is a shell with one tab and no features,
named as a placeholder. Renaming `app` is the first step and the README lists
every place it appears.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:02:52 +00:00

2.3 KiB

app

A project started from template-react. Rename app first — README step 1.

Bun · Hono · React 19 · Tailwind v4 · shadcn/ui · Postgres with Drizzle.

Read these before writing code

CONVENTIONS.md React patterns — feature folders, manager hooks, colocated state — and layout
docs/DATABASE.md Drizzle structure, push not migrations, check() vs open text
docs/SURFACES.md The JSON API and the app: one process, one query layer
README.md Setup, scripts, configuration

Rules that are load-bearing

APP_ENV, never NODE_ENV. Must be production or development; throws when unset. Hosts export NODE_ENV=production into everything they spawn, so anything keyed on it treats development as production silently. NODE_ENV is still set explicitly by both scripts because React's runtime resolves against it — Bun emits jsxDEV() in development and React's production runtime defines it as void 0.

Env files are read with the ambient environment LAST: .env.production.local (production only) → .env.local → .env → process.env.

Asset URLs in components are relative — never start with /. <base href="/"> in index.html is what a path-prefix proxy rewrites; a leading slash bypasses it. asset-urls.test.ts enforces this. fetch('/api/...') is the exception.

Routing is in the hash. The app may be mounted under a prefix it only learns at runtime, and a hash means the same thing at either mount point. lib/routes.ts.

One feature folder per screen, one manager hook per feature. State lives in the hook, components receive the manager. CONVENTIONS.md.

Serializers live in src/servers/serialize.ts only. Its test walks the module's exports for camelCase keys and for serializers without a case.

Bun

  • bun <file> not node, bun test not jest/vitest, bun install, bunx
  • Bun.serve() — not express. Bun.file over node:fs. Bun.$ over execa
  • Bun loads .env automatically; do not add dotenv
  • bun dev and bun start set the env markers. Running src/server.ts directly throws, by design

Before you finish

  • bunx tsc --noEmit and bun test
  • Verify claims rather than asserting them — start the server and request the thing
  • Commit with a message that says why, not what the diff already shows