tier:app member. It sits at apps/<name> and keeps all its code in src/.
Kinds
There is no Next.js kind: web apps are Vite + React Router (0126).
Ports are the
--port values in apps/*/package.json dev scripts; pick the next free one in the range. Vite uses --strictPort so a clash fails loudly. There is one docs app and it exists (apps/docs).
Rules
Apps are leaves
- Things depend on an app over HTTP, never through an import. If a second app needs code verbatim, it moves to a package and both consume it.
- An app may depend on
tier:libandtier:config. It never depends ontier:service: it talks to a service over the wire. A library never depends on an app. See Boundaries. - Apps hold definitions, packages hold factories: brand, locale list, routes and feature flags are defined in the app and handed to shared code (Packages).
Configuration is build-time and not secret
- An app reads configuration from build-time variables with the framework’s public prefix (
VITE_*,EXPO_PUBLIC_*). Anything under such a prefix ships to every user, so it is public by definition: API origin, environment name, a publishable key. - A secret never reaches an app. Server-side secrets belong to a service; see Secrets.
- Per-environment values for mobile live in EAS environment variables, not in
eas.json; each build profile names its environment. - A missing required value in a staging or production build fails loudly on a configuration screen; it never falls back to a development default.
- Feature flags come from one coded list in the app, forced on or off by a build-time variable, never from a fixture.
No fixtures in the build
An app talks to the real API in every environment. There is no demo or fixture mode in a shipped bundle. Test doubles live under__tests__, __fixtures__ or testing/ and never reach dist.
Budgets
A web app has a bundle budget measured on the built output: gzip size of the initial load (the entry script, its preloads and the stylesheetsindex.html references), a total tripwire, and the largest single chunk. Rules:
- The budget is a measured number plus a stated margin, written down next to the check.
- A number is raised only in a reviewed change that says why. Lazy route chunks raise only the total.
- Mobile and web performance targets are reviewed with the
performance-engineermethod: measure first, then name the top wins.
Per-kind notes
- vite and next: UI is HeroUI web (UI and accessibility).
nextignores.next/andnext-env.d.ts, and itsturbo.jsoncadds.next/**as build output. - expo: code-based React Navigation (no expo-router, no
app/folder); HeroUI Native with UniwindclassName; colours from the theme (useThemeColor), never literals.app.jsoncarries the bundle id and Android package; Android segments cannot contain-. Native versions must match the SDK (pnpm doctor), andreactstays on themobilecatalog. - docs: no TypeScript and no tests.
mint validateandmint broken-linksare its checks, anda11yaudits the built pages.
dev, start, preview, ios, android, doctor, a11y) are allowed on apps because they never terminate or need a device; they are not Turborepo tasks. See Tooling.