apps/web is Ship’s front-end: a TanStack Start application running in SPA mode. It’s the same base whether you scaffolded full-stack or web-only — what changes is where the data comes from.

The stack
| Concern | Tool |
|---|---|
| App framework | TanStack Start (SPA) on Vite |
| Routing | TanStack Router — file-based, in src/routes/** |
| Server state | TanStack Query |
| UI | shadcn/ui + Tailwind v4 |
| Forms | React Hook Form + Zod |
| Icons | lucide-react · @tabler/icons-react |
| Language | TypeScript |
vite.config.ts with the tanstackStart plugin in spa mode, plus @tailwindcss/vite and vite-tsconfig-paths (the @/... alias):
vite.config.ts
SPA mode does not mean “no server.” TanStack Start still runs a server — it just doesn’t server-render your routes. That server is where server functions execute, which is how the web-only shape does its backend work.
The root
Everything hangs offsrc/routes/__root.tsx. It defines the document shell, the head (meta, fonts, favicon), and the providers every route shares — TanStack Query, theming, tooltips, and the toaster:
src/routes/__root.tsx
Two ways to get data
The baseapps/web ships a landing page and nothing else to fetch. How you add data depends on the shape you scaffolded.
Full-stack
Talk to
apps/api through a fully typed oRPC client. The client and the useApiQuery / useApiMutation / useApiForm hooks arrive with the Auth plugin, which also adds the sign-in/up pages and the authenticated app shell.Web-only
No separate API. Backend logic runs as server functions (
createServerFn) on the Start server, called straight from a route loader.Full-stack: the typed oRPC client
Add the Auth plugin and your routes get anapiClient whose methods mirror the API contract one-to-one — every input and return type is inferred from the endpoint’s .input() / .output() schemas. Wrap a call in TanStack Query with the plugin’s hooks:
.d.ts (pnpm --filter api build:types) and the web app imports them through its "api": "workspace:*" dependency. Change an endpoint’s output and the client’s types change on the next build. See How Ship works.
Web-only: server functions
With noapps/api, your backend is a createServerFn handler that runs on the Start server and is consumed by a route loader:
Styling
Tailwind v4 is wired through@tailwindcss/vite; tokens live in src/globals.css. UI primitives are shadcn/ui components copied into src/components/ui/** — your code, edit freely. Conditional classes use the cn() helper. See Styling.
Run it
Where things live
Next
- Routing — file-based routes, params, guards and loaders.
- Server functions — backend logic for the web-only shape.
- Styling — Tailwind v4 and shadcn/ui.
