Capabilities
Single-page apps
Most of a site is best built as server-rendered pages — they're fast, indexable, and translated automatically. But for app-like surfaces — a dashboard, a client portal, an interactive tool — you can mount a single-page app (React, Vue, Svelte, anything that builds to static assets) under a path on the site, while the rest stays server-rendered. This path is CLI-only.
The model
You choose a path prefix — say /app or /portal — and configure it so that any URL under that prefix serves your app's shell. Your client-side router then takes over in the browser and renders the right view. Outside that prefix, the site behaves normally: server-rendered, translated, SEO-friendly pages.
Your app's built files live in the site's /assets/ folder and load by their own paths, so code-splitting and lazy chunks work as your bundler emits them.
Your toolchain, your repo
You build the app however you like — vite build, or whatever your framework uses — with the output directed into the site's /assets/ folder. Then you publish the site with the CLI. Only the built bundle is published; your framework source stays in your own repository. A typical loop:
# in your app's repo
npm run build # output into the pulled site's /assets/
# in the pulled site
acira publish -m "Update the portal app"
If your bundler also emits the site's stylesheet, turn the platform's CSS build off in the site's root configuration — otherwise the platform regenerates the stylesheet at publish from the Tailwind classes in your server-rendered templates, replacing your bundler's output. The switch is site-wide, and the platform's build reads only your server-rendered templates and never your bundle, so a hybrid site has to pick a side: leave the build on and let your bundler emit a second stylesheet for the app half, or turn it off and have your bundler produce the whole thing.
Your bundler's public/ folder is the site's public folder: files in it serve at their own path locally and once published, identically. Because your output directory is the site's /assets/, your bundler will also copy public/ in there — a redundant second copy you can suppress (publicDir: false in Vite) or ignore.
Talking to the backend
Your app doesn't need a separate API. It calls the site's own page handlers as a same-origin JSON API — the handlers return JSON, your app fetches them — and it reuses the site's sessions, auth, database, and email unchanged. So a logged-in visitor in your SPA is the same session the rest of the site sees.
Tradeoffs to know
- No auto-translation or SEO inside the SPA. Views rendered client-side aren't server-rendered, so their content isn't translated automatically and isn't indexed. Use SPA areas for app/portal surfaces; keep marketing and content as server-rendered pages.
- Locale prefixes still reach inside the SPA. On a multilingual site every path under your prefix is also served under each language code —
/de/app,/de/app/settings, and your JSON API at/de/app/api/…— and no setting excludes a folder from that. A router that hard-codes/appas its base misses every one of those URLs, so derive the base path from the URL at runtime; a hash router is unaffected. - Assets are public by filename. Anything sensitive must be a database attachment served through access control, never a file in
/assets/. - CLI-only. The AI coding agent authors server-rendered pages, not framework builds — SPA areas are built and published with the CLI.