Skip to content
Africa OS Knowledgebase
Esc
navigateopen⌘Jpreview
On this page

TanStack Start

The app framework conventions - file routing, server functions, server-first rendering, and the web app's route tree.

Africa OS apps are built on TanStack Start (not Next.js). This page covers the conventions the repository follows: file-based routing, server functions, server-first rendering, and the shared app patterns.

File-based routing

TanStack Start uses file-based routing, like TanStack Router. Files under src/routes/ become routes:

routes/
├── __root.tsx                 root layout (html, providers)
├── _authenticated.tsx         layout group with an auth guard
├── _authenticated/index.tsx   the launcher at "/"
├── _authenticated/$organization.tsx  layout for "/:organization"
└── _authenticated/$organization/$application.tsx  mounts an app

Conventions used in this repo:

  • Layout groups are folders or files with a pathless prefix (_authenticated) - the segment is not part of the URL but its layout applies to nested routes.
  • Dynamic segments use $ ($organization, $application).
  • Guards live in beforeLoad - the route’s data-loading hook. See Web app.

The route tree is code-generated with tsr generate (pnpm generate-routes in each app), and kept in sync by the dev server.

Server functions

Server functions are the RPC layer - the TanStack Start replacement for a REST API. They run server-side and are callable from the client with full types:

import { createServerFn } from "@tanstack/react-start";
import { getRequestHeaders } from "@tanstack/react-start/server";

export const listProductsFn = createServerFn({ method: "GET" })
  .validator((data: unknown) => schema.parse(data))
  .handler(async ({ data }) => {
    const headers = getRequestHeaders();
    // ... run an Effect program through the request runtime
  });

The rules every server function follows:

  1. Validate input with a Zod validator before anything else.
  2. Read headers via getRequestHeaders() and pass them to the request runtime.
  3. Run the Effect program through runRequest/runRequestFolded - never hand-assemble session, org, or database.
  4. Fold typed failures into serializable results - client code never sees thrown errors.
  5. Scope to the organization via the request scope (URL slug) whenever the call targets tenant data.

Server-first rendering

The repository prefers server components and server functions first. Data fetching happens server-side; client components are added only when explicitly needed (forms, sheets, live updates). This keeps business logic and tenancy enforcement server-side and the client bundle lean.

The shared app pattern

Every TanStack Start app in the repo follows the same skeleton:

src/
├── router.tsx                    createRouter + route tree import
├── routes/                       file-based routes
├── components/                   app-level components
└── features/                     domain features (auth, applications, ...)

App-specific features live under src/features/<name>/ with services/ for server functions, hooks/ for Query hooks, and components/ for UI - the same feature structure the application packages use.

The web app’s guard

Authentication is enforced in the _authenticated group’s beforeLoad:

export const Route = createFileRoute("/_authenticated")({
  beforeLoad: async () => {
    // session check via a server function; redirect to /login when missing
  },
});

Route loaders that need data call server functions, which run through the request runtime and therefore carry the full request context.

Next steps

  • Data fetching - how server functions and Query compose.
  • Web app - the full route tree in practice.

Last updated on August 18, 2026