Skip to main content
A spa module holds a single-page application: the React app your users load in the browser. Unlike the other modules in your project, it carries no backend logic, no controllers, services, or repositories. It is a self-contained front end built with Vite, TanStack Router for file-based routing, and TanStack Query for data fetching, written in React and TypeScript. It lives under modules/<kebab-name>/ like any other module, and its <name>.yml declares type: "spa".

What sets a spa module apart

A spa module is the browser application, the UI users see and interact with. It does not register controllers or run on the server alongside your API. Each spa is a standalone Vite app with its own dev, build, and preview scripts and its own dev port, so you run and ship it independently. Routing is file-based, with routes as folders and files under src/routes/, and server state is fetched and cached with TanStack Query, so there is no manual route table or ad-hoc fetch glue. A spa builds its UI on top of a design module, the shared design system; the chosen design is recorded in the spa’s yml, so components and tokens come from one place. Code is organized as vertical slices under src/features/, with cross-cutting code in src/shared/, so a domain’s routes, components, and hooks live together.

How it differs from a backend module

A regular module (or a microservice) is server-side: it registers into AppModule or SharedModule, exposes path aliases in the root tsconfig, and runs as part of the API. A spa module does none of that. It is a browser app that the API serves to the client.

How it works

talos spa:create scaffolds a base module, marks it type: "spa", and clones the upstream skeleton-spa repository into the module’s src/. It then installs the spa dependencies and assigns a free dev port (3030 by default, or the next free port if that is taken) by adding dev, build, and preview Vite scripts to the module’s package.json. During creation you also pick the design module the spa composes (an existing type: "design" module or a new one), recorded as design: "<kebab>" in the spa’s yml. A spa module’s tree therefore looks like this:

Working with a spa

Once created, you build the app out with the generators:
  • Add a feature slice with talos spa:feature:create — see Features.
  • Add a translation hook with talos translation:create — see Internationalization.
  • Remove a spa with talos spa:remove — see Remove.

Next steps