react-monorepo architecture
How the apps start
Section titled “How the apps start”Each app’s src/app/providers.tsx configures the shared packages at module
scope, before the first request:
configureApi({ baseUrl: VITE_APP_API_URL, onUnauthorized: redirectToLogin });configureAuth({ authApiBaseUrl: <API>/auth, authAppUrl, trustedOrigins, defaultRedirect }), whereauthAppUrlis the auth app’s origin (VITE_APP_AUTH_HOST) andtrustedOriginslists every sibling app.
Then the same provider tree everywhere: ThemeProvider → MetaProvider →
Redux Provider → AuthProvider.
One session across apps
Section titled “One session across apps”- The client app asks the API for the session (
/auth/get-session) with the cookie. While that round trip is in flight,ProtectedRouteshows a skeleton. - With no session,
redirectToLogin()sends the browser to the auth app:<auth>/login?redirect=<where you were>. - The auth app signs in against the API. better-auth sets the session cookie on
the parent domain (
BETTER_AUTH_COOKIE_DOMAIN, e.g..example.com), so every subdomain sends it. - The auth app sends you back to
redirect, but only if its origin is one of the trusted sibling apps; otherwise to the client app. - Back on the client, the session check now succeeds.
Any 401 from the API, in any app, triggers the same redirect. Sign-out clears
the cookie and tells other tabs of the same app through a BroadcastChannel
(other apps notice on their next request).
Data layer
Section titled “Data layer”One RTK Query baseApi in @scope/api, which every feature extends with
injectEndpoints in features/<name>/api.ts of its app. All requests include
credentials; every 401 goes through one chokepoint. Tag types and endpoint names
share one global namespace across every app and package: collisions are silent,
so declare tags in base-api.ts and keep them specific.
@scope/types holds TypeScript types and Zod schemas generated from the API’s
OpenAPI document. Every app compiles against them, so regenerate and commit them
when the API changes. See Development.
apps/admin renders only for the admin and superuser roles (RoleRoute);
signed-out visitors go to the auth app, and other users see Forbidden. That’s
the UI; the API must enforce the same roles on every endpoint the console calls.
Landing
Section titled “Landing”apps/landing builds robots.txt, sitemap.xml and security.txt at build
time, and marks every build that isn’t for the production origin as noindex,
so a preview can’t outrank production. Set PRODUCTION_ORIGIN in
src/features/seo/routes.ts to your real origin. Pages are rendered in the
browser: crawlers that don’t run JavaScript see an empty shell unless you add a
prerender step.
Theming and telemetry
Section titled “Theming and telemetry”As in react-app: light, dark or system
per origin, applied before first paint, and OpenObserve RUM in every app (each
with its own service name) with traceparent on API calls.