Skip to content

Local Development

  • mise — provisions and pins the toolchain (Node 26.5.0, Bun 1.3.14, Caddy 2, Railway CLI 5) and runs the dev tasks. Bun is still the package manager and runtime; mise just pins it.
  • A GITHUB_TOKEN with read:packages — required to install the private @huddlesurety/api SDK (see below).
  • Railway accessmise run env pulls env vars from Railway.
  • A running API (locally or a deployed environment) for the app to talk to.
  1. Install the toolchain + dependencies:

    Terminal window
    mise install # provisions Node/Bun/Caddy/Railway; run `mise run deps` for bun install

    Installs authenticate the @huddlesurety scope against GitHub Packages via bunfig.toml, which reads your GITHUB_TOKEN:

    [install.scopes]
    "@huddlesurety" = { token = "$GITHUB_TOKEN", url = "https://npm.pkg.github.com" }
  2. Pull environment variables:

    Terminal window
    mise run env # railway variables … --json > .env.json (env from ENVIRONMENT, default "development")
  3. Run the dev server:

    Terminal window
    mise run run # bun run dev → next dev --turbopack (auto-runs deps + env first)

    Optionally, front it with the HTTP/2 dev proxy so the browser speaks h2 to the app (see tRPC over relative origins):

    Terminal window
    mise run proxy # caddy reverse-proxy --from localhost:3001 --to localhost:3000

Run with mise run <task> (mise.toml):

Task Command Notes
run bun run dev (next dev --turbopack) Local dev server (deps + env first)
proxy caddy reverse-proxy --from :3001 --to :3000 HTTP/2 dev proxy in front of Next
check fmt + lint + tsc The full pre-push check
tsc / tsc:watch bunx tsc --noEmit Typecheck with the native TS7 compiler
lint bunx eslint Runs through the TS6 alias (see below)
fmt bunx prettier --write . Format
env railway variables … --json > .env.json Pull env from Railway
deps bun install Install dependencies
log / clean railway logs / rm -rf .env.json .next

The remaining package.json scripts are just dev, build (next build, which also typechecks via useTypeScriptCli), and start (next start).

Pulled from Railway into .env.json (gitignored); .env.local holds dev overrides, and ENVIRONMENT (default development) selects the Railway environment. Key variables:

  • Server: API_URL (Go backend base URL — http://localhost:8080 locally), JWT_SECRET (HS256 verify), GITHUB_TOKEN (install-time), PORT (the SSR self-call port, default 3000), OTEL_EXPORTER_OTLP_ENDPOINT.
  • Public: NEXT_PUBLIC_URL, NEXT_PUBLIC_BRAND_URL, NEXT_PUBLIC_SERVICE_NAME, NEXT_PUBLIC_DEPLOYMENT_ENVIRONMENT, NEXT_PUBLIC_FARO_ENDPOINT.

The tRPC client URL is now decoupled from the serving origin (#135-era change): in the browser it’s the relative "/api/trpc" (same-origin under any host, including the h2 proxy on :3001), and during SSR it self-calls http://localhost:${PORT ?? 3000}/api/trpc. NEXT_PUBLIC_URL is no longer used for tRPC — it now only builds invite links. next.config.ts lists allowedDevOrigins: ["localhost:3001"] so the proxy origin can load HMR/assets. The SDK fetcher still targets API_URL.

A couple of non-obvious things about the build setup — both are intentional:

  • TypeScript 7 (tsgo) for typecheck/build, TypeScript 6 for ESLint. TS7’s native binary ships no JS compiler API, which typescript-eslint needs. So the repo installs a second, aliased typescript-lint (TS6) and eslint.config.mjs monkey-patches module resolution so only ESLint uses it. next build typechecks via experimental.useTypeScriptCli (TS7 ships no lib/typescript.js, so Next must invoke the project-local compiler).
  • Cache Components + React Compiler are on (next.config.ts). See Architecture › Cache Components for the prerender-safety patterns this requires.