Providers and request context
CAUTION
Experimental — not for production. Managed Data Mode SSR is available for evaluation and testing. Do not use it in production yet; the API and behaviour may change. In the meantime, continue using Webpack SSR.
Pass request-scoped values into React with typed hooks. Mount isomorphic providers (Braid, Vocab, Apollo) and shared UI in your root layout route.
sku mounts a SkuProvider outside the router:
Document
└── SkuProvider ← site, clientContext, reactContext
└── Router
└── root layout route ← Vocab, Apollo, shared UI
└── pagesTyped hooks
Create hooks bound to your entry objects:
// src/skuContext.ts
import { createSkuContexts } from 'sku/runtime';
import type client from './client';
import type server from './server';
export const { useSite, useClientContext, useReactContext } = createSkuContexts<
typeof server,
typeof client
>();useSite()— active site nameuseClientContext()— serialisable content fromgetClientContext(shared with the browser)useReactContext()— env-differing values fromgetReactContext(may differ on server vs client)
To type sites on routes from the same getSite union, see Strictly typed sites in route objects.
Pass values into React
Serialisable content (theme, user id) — set getClientContext on the server entry and read with useClientContext():
// src/server.tsx
import { defineServerEntry } from 'sku/runtime';
const server = defineServerEntry({
getClientContext({ req }) {
return { userId: req.user?.id ?? null };
},
});
export default server;Env-differing values (API clients, server-only links) — set getReactContext on both entries and read with useReactContext():
import { defineServerEntry } from 'sku/runtime';
const server = defineServerEntry({
getReactContext() {
return {
// Server-only client factory (API base URL, server link, …)
makeClient: () => createServerClient(),
};
},
});
export default server;import { defineClientEntry } from 'sku/runtime';
import type server from './server';
const client = defineClientEntry<typeof server>()({
getReactContext() {
return {
makeClient: () => createBrowserClient(),
};
},
});
export default client;clientContext and reactContext are set for the page load and do not change across client navigations. Anything that must track navigation (for example locale from the URL) belongs in the route tree.
For loader/action/route-middleware dependency injection, see Data loading → Router context.
Root layout for providers
Wrapping that needs React Router hooks belongs in your own root layout in routes.tsx. You can add a pathless layout to wrap child routes without adding a URL segment. The same root layout can be used for shared UI such as a header or footer.
import 'braid-design-system/reset';
import { BraidProvider } from 'braid-design-system';
import seekJobs from 'braid-design-system/themes/seekJobs';
import { Outlet, useLocation } from 'react-router';
export const RootLayout = () => {
const language = useLanguage();
return (
<BraidProvider theme={seekJobs}>
<Header />
<Outlet />
<Footer />
</BraidProvider>
);
};import type { SkuRouteObject } from 'sku/runtime';
import { RootLayout } from './RootLayout';
export const routes: SkuRouteObject[] = [
{
Component: RootLayout,
children: [
{ index: true, lazy: () => import('./pages/home/home') },
{ path: 'about', lazy: () => import('./pages/about/about') },
],
},
];Env-specific values (API clients, etc.) come from dual-entry getReactContext. Isomorphic provider components mount in the root layout and read those values with hooks — for example Vocab keyed on the URL, or Apollo via useReactContext(). See Multi-language and Apollo streaming hydration.
Braid reset
Import braid-design-system/reset before any module that touches Braid on the server graph (for example at the top of the root layout). On sku start, Vite’s SSR evaluation order can differ from production. sku does not auto-inject Braid reset.
Browser-only libraries
Libraries that touch window (for example analytics SDKs) throw during Document SSR. Construct them in client getReactContext and return a stub (or omit the field) on the server. Consume from a small useEffect wrapper via useReactContext():
import { defineClientEntry } from 'sku/runtime';
import { createAnalytics } from './analytics';
import type server from './server';
const client = defineClientEntry<typeof server>()({
getReactContext() {
return { analytics: createAnalytics() };
},
});
export default client;import { defineServerEntry } from 'sku/runtime';
const server = defineServerEntry({
getReactContext() {
return { analytics: null };
},
});
export default server;import { useEffect } from 'react';
import { useReactContext } from './skuContext';
export const Analytics = () => {
const { analytics } = useReactContext();
useEffect(() => {
analytics?.trackPageView();
}, [analytics]);
return null;
};Mount <Analytics /> in the root layout.
See also
- Request entries — getters and entry shapes
- Routing — pathless root layout and pages
- Data loading — render-time fetch and router context
- Multi-language — Vocab in the root layout
- Error pages → Errors above the router — route boundaries do not cover
SkuProvider - Runtime API —
createSkuContextsand related helpers
