Skip to content

Composing systems

Your routes. Your renderer. One orchestrated response: data, policy and rendering - declared per route, not discovered.

τjs adds a declared request boundary to ordinary Fastify, Vite and framework conventions: each declared route states its data, policy and rendering.

You don’t need to convert an application. Start with what you have, then declare the routes whose initial responses benefit from server coordination.

  • Add critical or deferred data when the server should own the initial read
  • Mediate service access where validation, cancellation and observability matter
  • Choose SSR, streaming and hydration per declared page
  • Move applications between React, Vue and Solid without moving policy and services into components

The same declaration that coordinates the response also makes the architecture inspectable.

Adopt the model incrementally.

Your architecture is declared, not discovered

Section titled “Your architecture is declared, not discovered”

A τjs request contract can be inspected without running the component tree: routes, policy, rendering and declared service edges exist as configuration. Development traces add what real requests actually did.

Using the application’s typed serviceData helper, the declaration can describe a home page that server-renders and a product page that streams with its reviews declared alongside:

export default defineConfig({
apps: [
{
appId: "storefront",
entryPoint: "",
renderer: reactRenderer({ project: "./tsconfig.json" }),
routes: [
{ path: "/", attr: { render: "ssr", hydrate: true } },
{
path: "/products/:id",
attr: {
render: "streaming",
hydrate: true,
meta: { title: "Product" },
data: serviceData("catalogue", "product", (p) => ({ id: String(p.id) })),
deferred: {
reviews: serviceData("reviews", "forProduct", (p) => ({ id: String(p.id) })),
},
},
},
],
},
],
});

The product is critical: it resolves before the response commits, where a failure can still become an HTTP error. The reviews are deferred: they start with the request, never delay independent shell content, and cannot decide the status. Values consumed during rendering hydrate without a client refetch.

Each declared page is a real Fastify route, and a route can also declare head metadata, authentication and Content Security Policy. Everything after hydration - mutations, polling, subscriptions, UI-local fetching - stays in your application, where it belongs.

The contract is operational, not descriptive: data loading, rendering, policy, tracing and tooling all consume the same declaration.

Read about request contracts and data ownership.

What breaks if you change the catalogue service? In most stacks that means grepping and hoping. Here you ask, and get back the routes known to use it - declared when configuration states the call, and observed when a real request has made it.

The answers come from artefacts the dev server writes, not from reading source. The request graph records what taujs.config.ts declares; development traces record what each request actually did. Both are served over MCP, so tools and AI agents work from the same evidence - and when there is no live dev server to ask, the trace tools say so rather than answer from stale data.

Explore architecture-aware inspection through MCP.

One architecture, several application shapes

Section titled “One architecture, several application shapes”

The same Fastify host can serve, for example, a storefront, an account area and an operations console - each a separate application with its own renderer root, module graph and output bundle, and each free to choose its own renderer framework. Independently developed applications can share one server and one operational model without first standardising on a component framework.

Each URL belongs to exactly one application. Left undeclared within the application shell, it remains client-rendered.

Applications compose through routes and assembled build output rather than runtime module federation. They can be built selectively, while the deployed server artefact receives the bundles and manifests it needs to serve the complete system.

See how multi-app composition works.

τjs composes existing tools rather than replacing their native APIs:

  • Fastify owns HTTP routing, plugins, hooks, decorators and the host lifecycle
  • Vite owns the application module graph, development pipeline and production bundles
  • React, Vue and Solid keep their component models, streaming semantics and client runtimes

There is no second HTTP router or parallel plugin system to learn. Supply your own Fastify instance and τjs operates inside an encapsulated scope; omit it and τjs creates and operates the Fastify host.

The same request boundary holds across all three component models: policy, data orchestration and service access stay where they are when the renderer changes.