Skip to main content
A spa module is a Vite-built React single-page app that lives under modules/<name>/. Its <name>.yml declares type: "spa" and records the design module it consumes with a design: line; its package.json (named @module/<kebab>) carries dev, build, and preview Vite scripts on the module’s own free port (default 3030). The src/ tree is populated from the upstream skeleton-spa repo and follows a strict layering: routes stay thin, features own vertical slices, and shared/ holds the code two or more features have in common.

Directory layout

Module-level files

Two files at the module root describe the spa to the framework and to Vite. See Overview for how a spa fits into a workspace and pairs with a design module.

Top-level folders

The src/ tree is organized as four layers (entry wiring, routes, feature slices, and shared code) plus a public/ directory served as-is.

Inside bootstrap/

Inside routes/

Feature and shared sub-layers

Every features/<feature>/ folder and the shared/ folder share the same sub-layout. The layering keeps a spa maintainable: each sub-layer has one job, and the boundaries are enforced by convention.
The two rules to internalize: hooks are the only layer that talks to the backend, and services hold pure domain logic and never touch the backend. Keeping that split clean lets you test domain rules in isolation and swap data sources without rewriting your features. See Data fetching for how hooks call the backend.

The shared/ folder

shared/ mirrors a feature’s sub-layout exactly (assets, components, hooks, layouts, services, store, styles, types, utils) and is the one place features import common code from. On create, the spa ensures every shared/ sub-layer exists, each tracked with a .gitkeep so the empty directories survive in git. shared/hooks/ includes useLang, scaffolded alongside the first translation hook. It reads the ?lang= query param (defaulting to en) so every feature resolves the active locale the same way.
A feature must never reach into another feature’s internals. When two or more features need the same component, hook, type, or helper, promote it to the matching shared/ sub-layer. That boundary is what stops a spa from collapsing into a tangle of cross-imports.
The design tokens and UI primitives a spa consumes come from its paired design module. See Design system structure for that side of the layout, and Features for how to build out a feature slice.