Server Functions and Middleware
Purpose and Scope
TanStack Start is the full-stack layer built around TanStack Router. In Start, a route remains the application contract: it defines URL shape, params, search validation, loaders, links, and rendering boundaries. Server functions and middleware add the server-only side of that contract. They are the place to run database access, authentication checks, environment-specific work, and request handling that should not be bundled into client components. The official Start positioning describes this as Router-first apps gaining full-document SSR, streaming, server functions, server routes, and deployable runtime output.
The source paths supplied for this page are Router API and file-based routing references, so the most important implementation lesson is how Start server work attaches to Router’s existing model rather than replacing it. File-based routing defines where route modules live and where the generated route tree is written. The Router API index then exposes the route creation functions, route data utilities, deferred rendering components, redirects, not-found helpers, and error boundaries that Start relies on when server work participates in route matching and rendering. Sources: docs/router/api/file-based-routing.md, docs/router/api/router.md
Relevant Source Files
docs/router/api/file-based-routing.md— Defines file-based routing configuration such asroutesDirectory,generatedRouteTree, virtual route config, route file tokens, ignore rules, code-splitting toggles, and generated route tree formatting. These settings determine how Start and Router discover route modules before server work runs.docs/router/api/router.md— Lists the public Router API surface, including route creation functions,createRouter,defer,redirect,notFound,<Await>,<CatchBoundary>, hooks, and exported types used by route modules and rendering boundaries.docs/router/api/router/ActiveLinkOptionsType.md— Documents active and inactive link props. This is client-facing, but it matters because server functions should preserve the same typed navigation contract that links use after hydration.docs/router/api/router/AsyncRouteComponentType.md— DefinesAsyncRouteComponent<TProps>as a route component with an optionalpreload()method, which connects code-split route modules to preloading and server-rendered navigation flows.docs/router/api/router/awaitComponent.md— Documents<Await>, the React 18 component for rendering promised loader data and deferred values by suspending while a promise is pending and throwing on rejection.docs/router/api/router/catchBoundaryComponent.md— Documents<CatchBoundary>, which catches thrown errors from children, renders an error component, supportsonCatch, and resets from a declarative key.
Core Primitives
A Start app should be read in layers. The route file is the public shape of the screen: it names a path, validates search, loads data, renders UI, and participates in the generated route tree. The server function is an explicit server boundary called from that application contract. The official Start example uses createServerFn({ method: 'GET' }).handler(...) to keep database work behind a validated server-only call, while a route loader can still use Router context and params to decide what data is required for the route.
Middleware sits conceptually around server functions and server routes: it is where cross-cutting request concerns belong. Authentication, session lookup, request metadata, and environment-specific behavior should be kept near the server boundary rather than embedded in link components or route UI. The supplied Router docs do not define Start middleware APIs directly, but they do show the Router-side primitives that middleware outcomes feed into: a loader may redirect, a route may throw a not-found error, a deferred value may be rendered with <Await>, and rendering failures can be isolated by <CatchBoundary>. Sources: docs/router/api/router.md, docs/router/api/router/awaitComponent.md, docs/router/api/router/catchBoundaryComponent.md
File-based routing is the build-time primitive that makes this arrangement predictable. routesDirectory declares the route source directory, and generatedRouteTree declares the generated file consumed by Router. Additional options such as virtualRouteConfig, routeFilePrefix, routeFileIgnorePrefix, routeFileIgnorePattern, routeToken, autoCodeSplitting, and disableTypes control what becomes part of the route tree and how much type information is emitted. Server functions are easier to reason about when route discovery is deterministic, because each server call is associated with a route contract that can be loaded, preloaded, rendered, and hydrated. Sources: docs/router/api/file-based-routing.md
Execution Flow
A typical Start request begins with URL matching. Router narrows the route tree to the matching route branch, including path params, search state, loader dependencies, and route context. Start can then run server work before or during rendering: loaders may run on the server, server functions may execute database or environment calls, and the result can be streamed into a full document response. This keeps the URL and route model stable while moving privileged operations to the server runtime chosen by the deployment adapter.
When a loader calls a server function, treat the server function as a boundary with an explicit HTTP method and a handler. The handler should own work that requires server authority: database reads, writes, auth token verification, private environment variables, or integration credentials. The route loader should own route-specific orchestration: reading params, deriving loader dependencies from validated search, deciding when to redirect, and returning the data shape the route component expects. That separation keeps Router’s type safety useful across both server and client navigation.
Rendering then uses the same Router components documented in the API reference. If a route returns a promise for deferred data, React 18 apps can render it with <Await promise={deferredPromise}>, whose child function receives the resolved value. If the promise rejects, <Await> throws the error so a surrounding boundary can handle it. For thrown rendering or data errors, <CatchBoundary> renders an error component and can reset when getResetKey() changes. This makes server failures visible through normal route-level UI boundaries instead of becoming hidden request-side exceptions. Sources: docs/router/api/router/awaitComponent.md, docs/router/api/router/catchBoundaryComponent.md
API Components and Contracts
The Router API index is the best compact map for the Start-side route contract exposed by the supplied sources. Route authors use createFileRoute, createRootRoute, createRootRouteWithContext, createRoute, and createRouter to construct the route tree and router instance. Data and control-flow utilities include defer, redirect, notFound, isRedirect, and isNotFound. Component APIs include <Await>, <CatchBoundary>, <CatchNotFound>, <ClientOnly>, <ErrorComponent>, <Link>, <MatchRoute>, <Navigate>, <NotFoundComponent>, and <Outlet>. Sources: docs/router/api/router.md
Use this contract to decide what belongs in server functions. A server function should not replace route creation, generated route trees, links, or Router hooks. Instead, it supplies server-only data and side effects to those APIs. For example, an authenticated route can run a server function that verifies the session, then the loader can return user data or throw redirect to a login route. A data-heavy route can call a server function that fetches records, return a deferred promise, and render it with <Await>. A failing server call can be displayed by route error UI or a local <CatchBoundary>.
// Conceptual Start pattern based on the documented Router contract
export const Route = createFileRoute('/_app/projects/$id')({
loader: async ({ params, context }) => {
const project = await getProject({ data: { id: params.id } })
return { project }
},
component: ProjectPage,
})
const getProject = createServerFn({ method: 'GET' }).handler(({ data }) => {
return db.project.find(data.id)
})The file-based routing options also influence the operational behavior of server functions. If disableTypes is enabled, the generated route tree is emitted as JavaScript instead of TypeScript, reducing the generated type surface available to route authors. If autoCodeSplitting is enabled, route components can participate in automatic splitting, and AsyncRouteComponent<TProps> documents the preloadable route component shape. A code-split route with a preload() method can be prepared before navigation, while the server boundary still remains explicit and isolated. Sources: docs/router/api/file-based-routing.md, docs/router/api/router/AsyncRouteComponentType.md
Implementation Guidance
Keep server functions small and named after the server capability they expose, not after the component that calls them. A good function boundary answers one server-side question: load a project, create an invoice, verify a user, or read a private integration. Route loaders and actions can compose those functions into navigation behavior. This mirrors the official Start request trace: match the route, run the loader, call a server function, stream the document, and ship output for the target runtime. The route remains the typed public contract, while the server function remains the private execution boundary.
Use middleware for repeated request policy rather than duplicating checks in every route. Authentication, request logging, tenant selection, headers, and environment setup are typical examples. After middleware establishes request context, route code can consume the resulting context through the Router-side APIs. The API index explicitly includes createRootRouteWithContext, useRouteContext, useRouter, and useRouterState, which are the Router-facing pieces of context-aware applications. This separation helps keep policy central while preserving route-local type narrowing and UI composition. Sources: docs/router/api/router.md
For user experience, keep navigation and active-state behavior on the Router side. ActiveLinkOptions extends link options with activeProps and inactiveProps, each allowing anchor props or a function returning anchor props. That means a link can remain declarative and style itself based on the current match, even when the data behind the destination is loaded by server functions. Server code should decide authorization and data access; link code should decide where navigation points and how active or inactive UI is presented. Sources: docs/router/api/router/ActiveLinkOptionsType.md
Testing Signals and Next Steps
When validating a Start feature that uses server functions, test the full route behavior rather than only the function body. Confirm that the route file is included by the configured routesDirectory, that the generated route tree is produced at generatedRouteTree, and that ignored prefixes or patterns do not accidentally exclude the route. Then test request outcomes: successful data, redirected unauthenticated requests, not-found records, rejected deferred promises, and UI reset behavior through catch boundaries. These are Router-observable signals for server-side behavior.
Next, read the Start routing and rendering pages alongside the Router API reference. Server functions become most useful when combined with typed params, search validation, loaders, streaming, and route-level boundaries. If you are designing an application architecture, start by drawing the route tree, mark which loaders need private server data, extract those operations into server functions, and centralize repeated policy in middleware. Then verify that links, preloading, deferred rendering, and error boundaries still behave as route features rather than ad hoc request handlers.