The routing engine/Layout adaptation

Layout adaptation

Mapping framework layouts onto React Router without an Outlet.

The mismatch

React Router renders nested routes using the <Outlet /> component. A layout component must call <Outlet /> where its children should appear. But BionicJS developers write standard React layouts that accept children:

What the developer writes
1export default function Layout({ children }) {
2  return (
3    <div className="layout">
4      <nav>...</nav>
5      {children}
6    </div>
7  );
8}

This is the idiomatic React pattern - every component library, every design system, every documentation page teaches layouts this way. But React Router expects <Outlet />, not children. If BionicJS required developers to use <Outlet />, it would break the mental model that layouts are just regular React components.

BionicJSLayoutAdapter

To bridge this gap, the BionicJS generator wraps every layout in a higher-order component called BionicJSLayoutAdapter. This adapter takes the user's layout and automatically injects an <Outlet /> as its child, completely hiding the React Router implementation detail:

The adapter
1export function BionicJSLayoutAdapter({ Layout }) {
2  return (
3    <Layout>
4      <Outlet />
5    </Layout>
6  );
7}

When the generator encounters a layout file, it wraps the import in this adapter. The developer's layout component receives <Outlet /> as the children prop, so it renders nested routes exactly where it placed {children}.

Generated route with adapter
1lazy: async () => {
2  const mod = await import("/app/dashboard/layout.tsx");
3  return {
4    Component: () => React.createElement(BionicJSLayoutAdapter, { Layout: mod.default })
5  };
6}

Why not require Outlet?

Requiring developers to import <Outlet /> from react-router would leak the routing implementation into every layout. The adapter eliminates this coupling entirely.

This is a core principle of the BionicJS design: React Router is an internal detail. The developer writes <BionicJSRouter /> at the root and standard {children} layouts everywhere else. The framework adapts between the two conventions so the developer never has to think about it.

ⓘ
The adapter is lightweight - it renders the layout with <Outlet /> as its only child. There is no extra re-rendering, no context providers, no performance overhead. It exists purely to translate between two conventions.