Skip to main content

Isomorphic Execution

Archibald code is isomorphic: the same components and hooks run on the server (SSR), in the browser, and — for app projects — in React Native. What differs per environment is not your import, but which build of a package that import resolves to.

One import, three entry points

Every @archibald/* package declares conditional exports in its package.json, so import { useFetch } from '@archibald/client' resolves to a different bundle per environment:

ConditionResolves toRuns in
browserlib/client.*the browser
node (and default)lib/node.*the SSR server
react-nativelib/expo.* / lib/native.*the native app

The node build typically re-exports everything the client build has plus server-only modules — which is why getSession from @archibald/auth exists on the server and throws in the browser, and why server-only code (Hapi routes, controllers, providers) never bloats the browser bundle. Application code follows the same split via the src/<platform>/client and src/<platform>/server trees.

SSR, then hydration

A request renders in two phases:

  1. Server render — components run in Node. Data hooks like useFetch execute their actions on the server; results are serialized into the HTML as transferred state.
  2. Hydration — the browser re-runs the same components. Data hooks find the transferred state in the cache and do not refetch; interactive islands attach their event handlers.

Both phases execute your component code, so it must not assume a browser. Branch with useSSR where the environments genuinely differ:

import { useSSR } from '@archibald/core';

function Widget() {
const { isServer } = useSSR();
if (isServer) {
return <StaticFallback />;
}
return <BrowserOnlyWidget />;
}

See useSSR and the hydration architecture for how islands, state transfer, and event replay work in detail.

Why hooks live in @archibald/core vs @archibald/client

The split follows the execution boundary, not topic:

  • @archibald/core holds the isomorphic foundation — the router hooks (useLocation, useNavigate, useParams, useSearchParams), useSSR, the DataClient class itself. Everything here is safe in any environment, including during the server render.
  • @archibald/client holds the React data/UI layer on top — useFetch, useMutation, useDataClient, lifecycle hooks. It ships distinct browser, node, and native builds because rendering and hydration behave differently in each.

So a feature package like @archibald/search imports useFetch from @archibald/client and FetchKey from @archibald/core — same runtime, two layers. When you write your own isomorphic module, mirror this: shared logic and interfaces in a common entry, environment-specific wiring behind client/node/native entry points.