Skip to content

ADR-0006 Runtime & toolchain

Company standard — Node LTS runtime, pnpm package manager, Vite+ (vp) unified toolchain.

Decision
Node LTS + pnpm + Vite+Ship & run
Status
SupersededSuperseded by ADR-0035
Date
Scope
Company-wide default for all apps and tooling

Every app needs a JavaScript runtime to execute server code, a package manager to install dependencies, and a dev toolchain (dev server, build, test, lint, format). Most teams inherit a split toolchain: Node here, pnpm or npm there, plus separate Vite/Vitest/ESLint/Prettier configs that drift apart over time.

We want one coherent toolchain across dev, CI, and production — the same runtime and the same entry point locally, in the pipeline, and inside the production Docker container (ADR-0003).

The default.

Pros

  • The reference runtime; everything is tested against it first
  • npm ships with Node — zero setup

Cons

  • npm is the slowest, weakest package manager of the candidates (lockfile churn, audit noise)
  • No toolchain story — dev/build/test/lint remain separate tools with separate configs

Node LTS as the runtime, pnpm as the package manager, and Vite+ (vp, from VoidZero) as the unified toolchain: one entry point combining Vite (dev), Rolldown (build), Vitest (test), Oxlint + Oxfmt (lint/format), and a cached task runner — all configured from a single vite.config.ts. vp env pins the Node version and package manager per project.

Pros

  • Reference-runtime compatibility — Node LTS is what the ecosystem (TanStack Start, Vite, Hono, Drizzle) tests against first; zero compat babysitting
  • pnpm: fast, disk-efficient, strict resolution that catches phantom dependencies
  • Vite+ collapses the toolchain into one dependency and one config file — dev, build, test, lint, format, tasks
  • vp env pins Node LTS + pnpm per project → no “works on my machine” drift between dev and CI (the Docker image pins its own matching tag)
  • Eject path is cheap: Vite+ wraps tools we would use anyway (Vite, Vitest, pnpm), so dropping it later is a config change, not a rewrite

Cons

  • Vite+ is young (1.x, MIT-licensed) — we accept faster churn than the tools it wraps in exchange for the consolidation; mitigated by the cheap eject path
  • Three versioned components (Node, pnpm, vp) instead of one binary — mitigated by vp env managing them

All-in-one toolkit: runtime, package manager, bundler, test runner.

Pros

  • One binary for everything; very fast installs and startup
  • Node compatibility covers most of the npm ecosystem

Cons

  • Not the reference runtime: occasional Node-compat edge cases in exotic packages become our problem
  • Its all-in-one advantage is matched by Vite+ while keeping Node’s compatibility and pnpm’s strictness
  • Vite/TanStack tooling targets Node first

Secure-by-default runtime with web-standard APIs.

Pros

  • Excellent security model; built-in TS, formatter, linter

Cons

  • Node/npm compatibility is bumpier than advertised for our stack
  • Smallest ecosystem fit for Vite/TanStack tooling

Node LTS (runtime) + pnpm (package manager) + Vite+ (toolchain) is the company standard — in local dev, in CI, and inside the production Docker image (ADR-0003).

Concretely:

  • Node LTS — LTS lines only, never Current. New apps start on Active LTS: 24.x until 26.x enters Active LTS (2026-10-28), then 26.x. Upgrades scheduled before EOL. From Node 27, every major becomes LTS (one release per year), so “even-numbered” no longer means anything
  • pnpm for dependencies — pnpm-lock.yaml is the only lockfile; never mix npm/yarn/bun lockfiles in a repo
  • Vite+ (vp) as the single entry point: vp dev, vp build, vp check (lint + format + types), vp test — configured from one vite.config.ts
  • Versions pinned per project in .node-version and packageManager (vp env pin); vp env resolves them on laptops and CI. The Docker base image tag is pinned separately and must match
  • Lint/format (Oxlint/Oxfmt) is settled here as part of the toolchain; testing uses the bundled Vitest (ADR-0014)

The deciding factors: reference-runtime compatibility (zero babysitting), pnpm’s strictness and speed, and Vite+ giving us Bun-style toolchain consolidation on top of boring, dependable Node — with a cheap eject path if Vite+ itself disappoints.

Easier:

  • One config file governs dev, build, test, lint, and format — no more config drift between five tools
  • .node-version + vp env kills “works on my machine” between laptops and CI; the Dockerfile tag covers containers
  • Fast, strict CI installs with pnpm’s content-addressable store
  • Onboarding: install vp, run vp env, done

Harder:

  • We track a young tool (Vite+ 1.x) — expect upgrade churn; we pin versions and upgrade deliberately
  • Node upgrades are a scheduled, company-wide event (LTS cadence), not an afterthought

Given up: Bun’s single-binary simplicity and raw install speed. Accepted — Vite+ gets us the same consolidation on the reference runtime, with pnpm’s strictness as a bonus.