Files
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

85 lines
3.5 KiB
Markdown

# template-react
A starting point for an app. **Bun · Hono · React 19 SPA · Tailwind v4 · shadcn/ui · Postgres with
Drizzle.** A JSON API at `/api` and a React app at `/`, over one query layer.
## 1. Make it yours
Everything project-specific is called `app`. Rename it once, before the first commit:
| Find | Replace with | Where |
|---|---|---|
| `APP_ENV` | `MYPROJECT_ENV` | `src/env-file.ts`, `package.json` scripts, `test-setup.ts`, `src/databases/app_db/src/env.ts` |
| `appEnv` | `myprojectEnv` | `src/env-file.ts` and its importers |
| `appdb` / `app_db` | `myprojectdb` / `myproject_db` | `src/databases/`, `tsconfig.json` paths, `package.json` scripts |
| `app` / `app_dev` (database names) | `myproject` / `myproject_dev` | `src/databases/app_db/src/env.ts`, `.env.example` |
| `"name": "app"` | your package name | `package.json` |
| `App` | your brand | `src/apps/frontend/App.tsx`, `index.html` `<title>` |
| `3000` | a port of your own | `.env.example`, `src/server.ts` |
A case-sensitive search for `app`, `App` and `APP` finds all of it. Thirty seconds now, a chore later.
## 2. Setup
```bash
bun install
cp .env.example .env # BUN_PORT, PUBLIC_URL, POSTGRES_URL_DEV
createdb app && createdb app_dev # or however you make databases
bun run db:push # applies the schema — no tables yet, by design
bun dev
```
## Scripts
```bash
bun dev # development on app_dev, with HMR
bun start # production on app
bun test
bun run build # bundle the SPA to dist/
bun run gen:index # index.gen.html from index.html — also run by predev/prestart/prebuild
bun run db:push # schema -> app_dev; APP_ENV=production bun run db:push for production
```
## What is served
| Path | |
|---|---|
| `/` and every unmatched path | the SPA — `src/apps/frontend` |
| `/api/*` | the JSON API — `src/servers/api` |
| everything in `public/` | from the root |
## Configuration, and the rules behind it
**`APP_ENV`, never `NODE_ENV`.** It must be `production` or `development` and throws when unset.
Some hosts export `NODE_ENV=production` into every process they spawn, so anything keyed on it
silently treats development as production.
**`NODE_ENV` is still set by both scripts, and must stay explicit.** React resolves its runtime
against it: Bun's bundler emits `jsxDEV()` calls in development, and React's *production*
`jsx-dev-runtime` defines `jsxDEV` as `void 0`. Inherit production while transforming for development
and the app dies with "jsxDEV is not a function".
**Env files are read with the ambient environment last:** `.env.production.local` (production only)
→ `.env.local` → `.env` → `process.env`.
**The database follows from `APP_ENV`** and is enforced before a connection opens; tests cannot reach
production data.
**`PUBLIC_URL`** is substituted into `index.html` by `scripts/gen-index.ts` because Bun's HTML bundler
fails the build on a root-relative favicon `href`. It must be the public origin in production.
## The placeholder
`src/apps/frontend/App.tsx` is a shell with one tab and no features. Add the first screen as a feature
folder — `CONVENTIONS.md` describes the shape. The database has **no tables**; `docs/DATABASE.md`
describes adding the first.
## Documentation
| | |
|---|---|
| `CLAUDE.md` | The rules an agent needs before touching anything |
| `CONVENTIONS.md` | React patterns — feature folders, manager hooks — and the project layout |
| `docs/DATABASE.md` | Drizzle structure, `push` not migrations, `check()` vs open `text` |
| `docs/SURFACES.md` | The API and the app over one query layer |