Private beta — the @z0devs packages are available to design partners while we harden for general availability.Try the live demoRequest access

PluginRouteLoader

Props, config, boot behaviour and the adapter types for the host-side orchestrator.

PluginRouteLoader is the host's plugin orchestrator: it loads the manifest, filters it, mounts each plugin's init, gates render on a loader, evaluates onboarding steps and records lifecycle transitions.

import { PluginRouteLoader } from "@z0devs/mf-shell-kit";

Mount it as a route element inside your dynamic route tree — it renders an <Outlet/> (optionally behind <OnboardingHost/>) once every plugin is ready.

Props#

PropRequiredTypeNotes
shellDefinedSharedStoreThe defineSharedStore result. Supplies the Provider and the useInitProps hook.
authAuthAdapter{ user, isLoading, login(returnTo?), logout? } — adapt Auth0 / Clerk / Cognito / Keycloak / custom.
manifestManifestLoader() => Promise<MFEManifestEntry[]>. Cached through React Query.
loaderLoaderComponentYour branded loading component — any ComponentType<{ size?: number }>.
submodulesSubmoduleComponent[]Shell-local init components rendered once during startup, alongside remote inits.
onPluginError(record: PluginErrorRecord) => voidCentral error sink. Mounts a PluginErrorProvider for the subtree.
configPluginRouteLoaderConfigSee below.
<PluginRouteLoader
  shell={sharedStore}
  auth={{ user, isLoading, login, logout }}
  manifest={loadManifest}
  loader={AppLoader}
  submodules={[BpmInit, NotificationsInit]}
  onPluginError={reportToTelemetry}
  config={{ versionPolicy: "block" }}
/>

config#

FieldTypeDefaultNotes
manifestCacheKeyReadonlyArray<unknown>["mfes-config-json"]React Query cache key. Include the tenant id for per-tenant manifests.
buildEnvConfigUrl(entry) => string | null${origin}/env-config.js from service.remoteEntryReturn null to skip env-config for an entry.
loaderSizenumber300Forwarded to your loader as size.
settleMsnumber1000Debounce before declaring loading done after dynamic router changes settle.
versionPolicy"block" | "warn" | "off""block"Behaviour when shellApiVersion doesn't satisfy shell.apiVersion.
onboardingRegistryKeystring | null"onboardingSteps" when presentWhich shell.registries key holds onboarding steps. null disables the auto-mounted OnboardingHost.
loadPolicyRemoteLoadPolicy3 attempts / 500 ms·n / 60 sRetry + timeout, forwarded to every InitRemoteBaseRemote.

RemoteLoadPolicy#

type RemoteLoadPolicy = {
  maxAttempts?: number;                  // >= 1; 1 disables retries. Default 3
  backoffMs?: (attempt: number) => number; // default (n) => 500 * n
  isTransient?: (error: Error) => boolean; // default: ScriptNetworkError, timeouts, RUNTIME-008, ChunkLoadError
  scriptTimeoutMs?: number;              // default 60_000
};
<PluginRouteLoader
  shell={sharedStore}
  config={{
    loadPolicy: {
      maxAttempts: 5,
      backoffMs: (n) => Math.min(2000, 250 * 2 ** n),
      scriptTimeoutMs: 30_000,
    },
  }}
/>

What it does, in order#

Fetch

Calls manifest(), cached under manifestCacheKey.

Validate

validateManifestEntry on every record; failures are skipped and reported as manifest-invalid.

Filter

Drops enabled: false (→ disabled), then applies versionPolicy (→ version-blocked), then resolves inter-plugin dependencies (→ dependency-unmet).

Mount

One <InitRemote> per surviving entry, inside an InitPropsProvider wired to shell.useInitProps — integrity check, registerRemotes, loadRemote("${service.id}/init"), with loadPolicy retries. service.type is threaded through so ESM containers load correctly.

Gate

loader stays mounted until every plugin reports ready (plus settleMs), so the nav never renders half-built.

Render

<Outlet/> — wrapped in <OnboardingHost/> when an onboarding registry is present.

Adapter types#

type AuthAdapter<TUser = unknown> = {
  user: TUser | null;
  isLoading: boolean;
  login: (returnTo?: string) => void;
  logout?: () => void;
};
 
type ManifestLoader = () => Promise<MFEManifestEntry[]>;
type LoaderComponent = ComponentType<{ size?: number }>;
type SubmoduleComponent = ComponentType<Record<string, unknown>>;

AuthAdapter is intentionally minimal — it's the shape the loader needs to decide "wait, redirect to login, or proceed", not a general auth abstraction. Anything richer belongs in the store's values.

What it mounts for you#

  • shell.Provider — the shared-store context
  • InitPropsProvider — wired to shell.useInitProps(remoteName)
  • PluginErrorProvider — when onPluginError is set
  • OnboardingHost — when an onboarding registry key resolves

Which means you do not mount those yourself. You do still mount RouterContextProvider, MFEnvProvider and your query client at the app root — or let createShellApp do it.

createShellApp#

import { createShellApp } from "@z0devs/mf-shell-kit";
 
const App = createShellApp({
  initialRoutes,                                  // with <PluginRouteLoader/> wired inside
  defaultErrorElement: <ErrorPage />,
  mfEnv: { mfeEnvNamespace: "__MFE_ENV__", shellConfigLoader: () => getShellConfig() },
  AppShell: ({ children }) => <ThemeProvider>{children}</ThemeProvider>,
});

Mounts RouterContextProvider → MFEnvProvider → Router in one call. You still decide where PluginRouteLoader sits in the tree, because that position determines which routes are gated by it.

ExportPurpose
defineSharedStoreDeclare the host↔plugin contract
OnboardingHostEvaluate onboarding steps (auto-mounted, or mount manually for custom UI)
PluginDebugPanelTable of every plugin's status, timing and last error
useLifecycle(events, key, handler)Subscribe to a store event with cleanup
defineSubmoduleType helper for shell-local init components
SharedInitProps, PluginInitProps, DefinedSharedStore, StoreFeaturesContract types

Next#