Request Contracts and Data Ownership
A request contract is the route record in taujs.config.ts that describes how τjs should produce
an application response. Fastify owns HTTP dispatch. Once Fastify selects a τjs page route, τjs
applies that route’s policy, data and rendering declarations before invoking the selected renderer.
The contract moves initial-response coordination out of the component tree without banning component-owned fetching:
Fastify route -> τjs request contract -> policy and response-owned data -> React, Vue or Solid renderer -> HTML response and optional hydrationYou can introduce this boundary one route at a time.
A current request contract
Section titled “A current request contract”The route record can describe rendering, hydration, policy, head data, critical data and deferred
data together. This example assumes serviceData was created for the application’s typed service
registry:
export const productRoute = { path: "/products/:id", attr: { render: "streaming", hydrate: true, meta: { title: "Product" }, middleware: { auth: {} }, head: { data: serviceData("catalogue", "getProductHead", ({ id }) => ({ id })), optional: true, }, data: serviceData("catalogue", "getProduct", ({ id }) => ({ id })), deferred: { reviews: serviceData("reviews", "forProduct", ({ id }) => ({ id })), }, },} as const;For a request to /products/42:
- Fastify selects the concrete route and decodes
id. - τjs applies the declared auth and CSP policy.
- Head and route-data work starts at the request boundary.
- The renderer receives the critical initial-data channel and the already-started named deferred registry.
- In development, the response episode records what actually happened.
There is no second τjs route matcher and the renderer does not rediscover the route contract from the component tree.
Four data ownership choices
Section titled “Four data ownership choices”The route fields describe different timing and ownership, not four spellings for the same work.
| Work | Owner | Timing and purpose |
|---|---|---|
attr.head | Request | Resolves before rendering so dynamic head values are available before the shell. |
attr.data | Request | Supplies the required initial snapshot. SSR resolves it before rendering; streaming can project it through the renderer’s native streaming path. |
attr.deferred | Request | Starts named work before rendering without awaiting it first. Available on streaming routes only. |
| Component or client fetch | Application | Starts from the UI or after hydration and remains outside the τjs response contract. |
attr.deferred is declarative response-owned work. Each entry starts once, shares the request
cancellation lifecycle, appears in the request graph, records a complete, failed or aborted
outcome on the development request episode, and reaches the renderer through its native Suspense
primitive. A value consumed during rendering reaches hydration through a private seed without
re-running the loader or issuing a client refetch.
Deferred entries are not HTTP-status-bearing. Their result may arrive after the status and headers
have committed. Work that must prevent the response, redirect it or determine its status belongs in
a pre-commit request phase, not in attr.deferred.
Deferral is a property of the declaration, never of the tree. A component cannot promote async work it starts into the deferred registry. That work is still valid, but it remains UI-local and τjs does not present it as a declared dependency, cancel it as deferred work or record a deferred outcome for it.
See Data Loading for the complete lifecycle and the renderer guides for the React, Vue and Solid accessors.
Declared shape and runtime evidence
Section titled “Declared shape and runtime evidence”A request contract is a declarative, statically enumerable configuration record. Its handlers are functions that execute at request time, so not every fact is knowable statically.
The distinction is intentional:
- Fastify page paths, rendering, hydration, policy, head declarations and deferred keys are visible from configuration.
serviceData()carries branded service identity, allowing the request graph to record a service edge without executing the loader.- An arbitrary handler or dynamic
ctx.call()remains valid, but the concrete call is runtime evidence rather than a statically declared edge. - Development request episodes record the route selected, service calls made, deferred outcomes and response terminal that occurred for one real request.
The generated request graph and live development episodes therefore answer different questions. The graph says what the system declares and can do. A episode says what one request did. The MCP server reads both forms of evidence rather than inferring them from component source.
Undeclared URLs and client routing
Section titled “Undeclared URLs and client routing”Not every client-routed screen needs its own τjs route declaration. A document-like URL that has no matching page contract can still receive the SPA fallback document, load the client bundle and let the client router take over. The fallback owner depends on who created Fastify:
- τjs-created Fastify: the implicit SPA fallback document serves unmatched document-like URLs with status 200. The child URL can remain client-routed without a separate τjs contract.
- Caller-owned Fastify: unmatched URLs belong to the caller. To keep them in the
application, declare an explicit terminal
/*page route. Without it, the caller’s not-found response wins.
routes: [ { path: "/", attr: { render: "ssr", hydrate: true } }, { path: "/*", attr: { render: "ssr", hydrate: true } },];The wildcard is the server route; individual child URLs can still be owned by the client router
inside the application. A wildcard-enabled caller @fastify/static mount also claims GET /* and will
collide at boot, so configure that mount with wildcard: false or use non-overlapping patterns.
See Host Ownership for the owner split. For the separate single-application composition pattern using shared browser-side chrome, routing and state, see App Shell Architecture.
Client fetching remains part of the model
Section titled “Client fetching remains part of the model”Request contracts govern the initial application response. They do not replace the client data layer. Components can still:
- fetch from explicit API endpoints;
- refresh or poll after hydration;
- issue mutations in response to user interaction;
- subscribe to real-time updates;
- keep screen-local async work inside the UI.
A common migration moves only the initial read into attr.data, while the existing query library
continues to own refreshes and mutations. The same domain or service implementation can sit behind
both paths.
The distinction is where authority starts, not what application code is allowed to do.
Scope across applications and MFEs
Section titled “Scope across applications and MFEs”One document response selects one τjs application and one renderer root. Its critical snapshot, deferred registry, ordering and hydration seed belong to that response and that root. They do not coordinate with a second application.
Navigation across τjs micro-frontend boundaries remains a full document navigation. The destination application creates its own request contract and data scope. Within one application, the client router can own subsequent navigation and UI-local fetching. See Micro-Frontends for where to place those boundaries.
When the structure pays for itself
Section titled “When the structure pays for itself”Request contracts are most useful when initial rendering depends on several services, policy must be applied before rendering, rendering strategy varies by route, or teams need a graph and episode of how a response was assembled.
For a small interactive screen whose data is entirely post-hydration, component-owned fetching may remain the clearer choice. τjs does not require every URL or every data read to become a request contract.
The useful boundary is the initial response: declare the work that the server must coordinate and leave application-local work in the application.
Read next
Section titled “Read next”- Data Loading for critical and deferred data behaviour
- Services for mediated backend access
- Head Management for pre-render head data
- τjs Configuration for the complete route schema
- Incremental Migration for adopting the model route by route