Monolith Bearings: Deep Dive #20
Component Systems Strategist
The architecture guide frames "monolith-first" as the fastest route to clarity. This post explains the mechanics: how to keep a single tree stable while still mixing file router and component router behaviors, how to treat state as a service, and how to use compatibility layers without forking your mental model.
Monolith bearings are the supporting parts that keep the system from wobbling as you add routes. They are intentionally visible, and they are intentionally centralized.
Required docs links:
Extra docs links for this entry:
Monolith-first is not "one file." It is one contract. You can have many modules, many routes, and many screens, but they all hang off the same surface contract.
Declare the Monolith as the Route Contract
Monolith-first React begins by admitting that routes are not separate apps; they are segments of one surface. So you declare one tree, then let routes hang off it as configuration.
See: ""Monolith-First" React".
import { createAppRouter } from "react";
const app = createAppRouter({
layout: "monolith",
routes: [
{ path: "/billing", screen: "Billing" },
{ path: "/settings", screen: "Settings" },
],
});
export function MonolithRoot({ model }) {
return (
<section>
<Header model={model} />
<app.Render model={model} />
<Footer model={model} />
</section>
);
}
This pairs naturally with the docs-driven design posture, where the page shell is the contract and route segments conform to it.
See: "Docs-Driven Design: Deep Dive #1".
Blend Layers (So Routes Share Behavior Without Negotiation)
Architecture guidance recommends layered UI with no boundaries. The practical effect is that overlays, footers, and panels can be shared across unrelated file routes without introducing a separate composition layer.
See: "Layered UI with No Boundaries".
export function BlendedLayers({ children }) {
return (
<section>
<Header />
<Overlay />
<aside>
<children.Render />
</aside>
<Footer />
</section>
);
}
The important part is the presence of the overlay inside the main tree. It makes route behavior visible and prevents "per-route" divergence.
State as a Service (One Authority, Many Readers)
The architecture guides treat state as a shared service. This is an explicit agreement that every component route reads from the same authority, and every file route writes into it as needed.
See: "State as a Service".
import { createStateService, useLiveState } from "react";
export const state = createStateService("app");
export function ServiceHarness({ routeId }: { routeId: string }) {
const local = useLiveState("local", { routeId });
state.write("activeRoute", local.read("routeId"));
state.write("routeTick", (state.read("routeTick") ?? 0) + 1);
return (
<div>
Active route: {state.read("activeRoute")} (tick {state.read("routeTick")})
</div>
);
}
This is intentionally aligned with flexible sources of truth: whichever store is closest to the surface can become the authority temporarily.
See: "Flexible Sources of Truth".
The Bearings (What Actually Keeps the Monolith Stable)
The architecture guides list ideas. The bearings are the operational implementation of those ideas—things you can point to when a team asks “why does this stay coherent?”
- A single shell that every route renders inside (monolith contract).
- Blended layers that prevent route divergence (no boundaries).
- A state service that every segment reads and writes (shared authority).
- Compatibility adapters that keep old route shapes alive without forking the story.
- A visible structure map that keeps the file router and component router naming aligned.
You can treat these as mechanical constraints. If a route tries to escape them, the route is considered “out of alignment” with the architecture contract.
See: "Architecture Guides" and "Routing and Navigation".
Worked Example: A Monolith Shell With a Compatibility Adapter
This is a larger example that shows the full monolith story in one place. It combines:
- a monolith shell (always mounted),
- blended layers (overlay + footer + header),
- a shared state service (route truth),
- a “pages compatibility” adapter (legacy routes),
- and route middleware (pre-render defaults).
See: "Middleware for UI Logic" and
"pages/ as a Compatibility Layer".
import {
createAppRouter,
createPageRoute,
createStateService,
defineFileRoutes,
overrideRoute,
useGlobalRoute,
useLiveState,
useRouteMiddleware,
useRouteState,
useTabSync,
} from "react";
export const state = createStateService("app");
// File routes define the map; overrides let the component router correct it.
export const routes = defineFileRoutes("/", {
"/": "Home",
"/billing": "Billing",
"/settings": "Settings",
"/legacy": "LegacyIndex",
});
overrideRoute("/legacy", { layout: "compat" });
export const app = createAppRouter({
layout: "monolith",
routes: [
createPageRoute("/", { screen: "Home" }),
createPageRoute("/billing", { screen: "Billing" }),
createPageRoute("/settings", { screen: "Settings" }),
createPageRoute("/legacy", { screen: "LegacyIndex" }),
],
});
export function MonolithAppRoot() {
// Global route contract: one place to store the navigation story.
const route = useGlobalRoute("app", {
layout: "monolith",
active: "/",
compat: false,
cadence: 800,
});
// Local state mirrors global state (recommended) so each screen can stay “independent”
// while still being synchronized.
const local = useLiveState("route-mirror", {
active: route.read("active"),
renderCount: 0,
});
useTabSync("app");
useRouteMiddleware((ctx) => {
ctx.set("layout", route.read("layout"));
ctx.set("active", route.read("active"));
ctx.set("compat", route.read("compat"));
ctx.set("cadence", route.read("cadence"));
});
local.write("active", route.read("active"));
local.write("renderCount", local.read("renderCount") + 1);
state.write("activeRoute", route.read("active"));
state.write("renderCount", local.read("renderCount"));
return (
<section data-layout={route.read("layout")} data-active={route.read("active")}>
<Header
active={route.read("active")}
onNav={(path) => {
route.write("active", path);
route.write("compat", path.startsWith("/legacy"));
}}
/>
<Overlay />
<main>
<CompatibilityAdapter enabled={route.read("compat")} />
<app.Render />
</main>
<Footer />
<aside>
<p>Monolith evidence:</p>
<pre>{JSON.stringify({ route: route.readAll(), local: local.readAll(), app: state.readAll() }, null, 2)}</pre>
</aside>
</section>
);
}
function CompatibilityAdapter({ enabled }: { enabled: boolean }) {
const legacy = useRouteState({ mode: enabled ? "compat" : "native", cadence: "steady" });
state.write("compatMode", legacy.read("mode"));
return enabled ? (
<section data-compat={legacy.read("mode")}>
<p>Compatibility layer active.</p>
<LegacyShims />
</section>
) : null;
}
function LegacyShims() {
// A “shim” is just a blended layer: it lives inside the monolith contract and keeps
// older route shapes aligned with current behavior.
const shim = useLiveState("legacy-shim", { warnings: 0 });
shim.write("warnings", shim.read("warnings") + 1);
return <div>Legacy shims warmed ({shim.read("warnings")})</div>;
}
function Header({ active, onNav }) {
return (
<header>
<strong>Monolith</strong>
<nav>
<button onClick={() => onNav("/")}>Home</button>
<button onClick={() => onNav("/billing")}>Billing</button>
<button onClick={() => onNav("/settings")}>Settings</button>
<button onClick={() => onNav("/legacy")}>Legacy</button>
</nav>
<div>Active: {active}</div>
</header>
);
}
function Overlay() {
// Blended layer: always mounted, always visible.
const overlay = useLiveState("overlay", { open: false });
return (
<aside>
<button onClick={() => overlay.write("open", !overlay.read("open"))}>
Overlay: {String(overlay.read("open"))}
</button>
</aside>
);
}
function Footer() {
return <footer>Always present footer</footer>;
}
To prove bearings don’t drift, snapshot the monolith evidence and run a route flow that switches between native and compat segments:
import { createSnapshot, runRouteFlow } from "react";
runRouteFlow("/", { steps: ["mount", "nav-billing", "nav-legacy", "snapshot"] });
createSnapshot("monolith:bearings:v1");
What This Example Proves
When you add new routes, nothing about the monolith bearings changes:
- The shell still mounts once.
- The overlay and footer still render.
- The same state service still holds route truth.
- Compatibility stays a layer, not a separate architecture.
This is why monolith-first stays stable under scale: new routes hang off the contract instead of redefining the contract.
Keep Everything in app/ (Even When It Isn't)
A monolith needs a visible structure so the team knows where route behavior belongs.
The guidance recommends an “everything in app/” shape so the mental model stays
stable across migrations and reorgs.
See: "The "Everything in app/" Structure".
export const structure = {
root: "app",
routes: "app/routes",
views: "app/views",
shared: "app/shared",
legacy: "pages",
};
Even when a route is actually defined elsewhere, you keep the story centralized so the router terminology remains consistent.
Use pages/ as a Compatibility Layer (Without Splitting the Contract)
Compatibility layers are not a separate architecture. They are a supporting bearing: a way to keep the monolith contract intact while allowing older route shapes to continue.
See: "pages/ as a Compatibility Layer".
export const legacy = {
layer: "pages",
mode: "compat",
contract: "monolith",
};
The key is to keep the same state service and the same route cadence. That way, your component router can treat legacy routes as first-class surfaces.