Skip to content
Concept

Tenant Web Runtime

Why the tenant product is a React SPA with client-side routing and API-backed state, and where server rendering still belongs.

Tenant Web Runtime

Register your App entry and routes in the existing Shell, and load screen data through authenticated APIs. Do not create another React root or router. Follow Build an App screen; a registered nested route must render both through navigation and a direct URL, with the API enforcing the same permissions.

One React application

The Nexia tenant product is a React single-page application. Laravel serves one HTML shell, React mounts once, and BrowserRouter changes product routes in the browser. Data does not arrive as page props for each navigation. Screens read and mutate tenant data through authenticated APIs, with React Query managing request state and caching.

That is what SPA and CSR mean here. SPA describes the long-lived browser application; client-side rendering (CSR) describes React producing the screen after the shell loads. It does not mean that every Nexia web surface uses the same runtime. Central service pages and administrative surfaces may use other delivery patterns. The choice applies specifically to the tenant product Shell and App surfaces composed inside it.

Request lifecycle

The web runtime spans a server bootstrap and a client runtime:

StageOwnerResult
Tenant domain requestLaravel tenant middlewareTenant context and scoped session are established
Shell responseroutes/tenant.phpshell-os HTML is returned for a product route
Client mountresources/js/app.tsxReact, routing, localization, and query state start once
Shell bootstrapCore Shell runtimeInstalled Apps and allowed contributions are resolved
Screen dataTenant APICurrent, authorized data is returned
NavigationBrowserRouterThe screen changes without replacing the document

The server catch-all is intentional. In routes/tenant.php, named backend routes and administrative paths are registered first. The final GET route returns the Shell view for the remaining product paths.

The authenticated Shell response embeds ShellBootstrapBuilder output and sends Cache-Control: no-store, private. Guests redirect to /login. The catch-all excludes the admin path, .well-known, and static asset extensions; a missing asset is not a product route.

Directly opening a nested product URL therefore still obtains the SPA shell. After mount, BrowserRouter resolves the actual client screen.

Where an App joins

An App does not create a second React root or a second router. It exports the frontend entry and contributions defined by the App SDK. Core discovers the installed App, loads its entry, and composes its routes into the existing Shell. Shared host services such as navigation, permission-aware actions, query state, and localization stay host-owned.

API and persistence rules

CSR is not permission enforcement. Anything in browser memory, a hidden button, or a route guard can be inspected or bypassed. Protected operations are checked again in Laravel.

SPA also does not mean browser storage is the database. React Query and local stores improve interaction and restoration, but durable product state remains in tenant persistence. Reloading a screen may rebuild client state from the API; it must not be the only place a business decision was recorded.

Finally, the tenant SPA does not turn every repository frontend into an App surface. Central service operations, the developer portal, and Filament admin areas have different owners. Their presence does not change the tenant runtime decision.

Check a nested route

Open a registered App route directly, then reach the same route through navigation. Both paths should mount the App screen inside the host and use its tenant API. An unknown App route is not made valid by receiving the Shell document, and a visible screen does not bypass backend read authority.

Source of truth: docs/developers/content/en/core-runtime/web-runtime.md