---
title: Web app
description: The @africaos/web app - its routes, guards, shell components, and server functions, and how they compose into the product surface.
---

`@africaos/web` is the main product surface: the unified shell where users sign in, manage their organizations, and use application modules. This page documents its structure and the code that makes it work.

### The route tree

```txt
src/routes/
├── __root.tsx                       root layout - providers, theme, router outlet
├── index.tsx                        landing/redirect when unauthenticated
├── login.tsx                        sign-in
├── onboarding.tsx                   organization onboarding
├── _authenticated.tsx               guard: requires a session
├── _authenticated/index.tsx         the launcher (application cards)
├── _authenticated/$organization.tsx organization shell (sidebar + header)
└── _authenticated/$organization/$application.tsx  mounts an application
```

### The auth guard

`_authenticated.tsx` enforces authentication before any protected page renders:

```ts
export const Route = createFileRoute("/_authenticated")({
  beforeLoad: async () => {
    const session = await getSessionFn();
    if (!session) {
      throw redirect({ to: "/login" });
    }
  },
});
```

`getSessionFn` is a server function backed by `AuthService.getSession`. Unauthenticated visitors are redirected to `/login`.

### The organization shell

`routes/_authenticated/$organization.tsx` renders the shell for one tenant:

```tsx
export const Route = createFileRoute("/_authenticated/$organization")({
  beforeLoad: async ({ params }) => {
    const organization = await resolveOrganizationFn({
      data: { organizationSlug: params.organization },
    });
    if (!organization) {
      throw redirect({ to: "/" }); // not an active membership → first org
    }
  },
  component: OrganizationLayout,
});

function OrganizationLayout() {
  const { organization } = useParams({ from: Route.id });
  return (
    <SidebarProvider>
      <AppSidebar organizationSlug={organization} />
      <SidebarInset>
        <Header />
        <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
          <Outlet />
        </div>
      </SidebarInset>
    </SidebarProvider>
  );
}
```

Two things matter:

- **`beforeLoad` verifies the slug server-side.** `resolveOrganizationFn` runs the request scope, which requires the organization to exist and be an active membership. The shell never trusts the URL segment alone.
- **The layout composes the sidebar and header** from `@africaos/ui`'s `Sidebar` set.

### The launcher

`routes/_authenticated/index.tsx` is the dashboard. It calls a server function that lists the enabled applications for the active organization (`RegistryService.listForOrganization`) and renders a card per application from registry metadata - icon, color, name, description, route. It is fully data-driven.

### The application route

`routes/_authenticated/$organization/$application.tsx` mounts an application:

```tsx
const Mount = resolveApplicationMount(applicationSlug);
return Mount ? <Mount organizationSlug={organizationSlug} /> : <NotAvailableState />;
```

`resolveApplicationMount` looks the slug up in `applicationMounts` (see [Application registry](/architecture/application-registry)). Applications with no mount render the "not available yet" state.

### Server functions

The web app keeps its server functions under `src/features/<name>/services/`:

- `features/auth/services/auth.functions.ts` - session, sign-in, sign-up, sign-out.
- `features/organizations/services/org.functions.ts` - resolve organization, list organizations, create.
- `features/applications/...` - launcher data.

Every function runs through the request runtime (`runRequest`/`runRequestFolded`), so each call authenticates, resolves the tenant, and carries permissions.

### The app sidebar and header

- **`AppSidebar`** (`src/components/app-sidebar.tsx`) - brand, organization switcher, navigation. Links point into the current tenant (`/${organizationSlug}/...`).
- **`Header`** (`src/components/header.tsx`) - page chrome and actions.

Both are composed from `@africaos/ui` primitives and stay thin.

### The composition rule

The web app is the **composition root**. It wires the request context, auth, organizations, the application registry, and the application mounts together. Application modules never import the web app - they are mounted by it.

### Next steps

- [Unified shell](/architecture/unified-shell) - the architecture of this app.
- [Data fetching](/frontend/data-fetching) - how its server functions and hooks work.