Examples
One CRM whose features are plugins, built three times. The SPA is the
runtime channel on its own; the two Next.js shells share
examples/crm-core — the same manifest rendered by two different servers.
npm run dev:spa # http://localhost:5173 — client-rendered, createSlot only
npm run dev:next-pages # http://localhost:3000 — Next.js pages router, SSR
npm run dev:next-app # http://localhost:3001 — app router: RSC tier 2 + streamingSPA — the runtime channel alone
No manifest, no Resolution and no provider: a plugin is a component that
contributes through the createSlot() façade, and installing it is mounting it
as a child of the shell. Live toggles are &&. Per-row hosts hand the same
fill different props.
It is also where the runtime channel's cost is visible — no error boundary wraps a fill, so the crash-test feature has to bring its own.
Pages router — the registry, server-rendered
The same features declared up front, resolved once per request, in the HTML the
server sends. Run it and view source at http://localhost:3000/deals?view=stale:
- the plugins' nav items, deal actions and panels are already in the markup;
- the saved view has already been applied by the application's resolved table;
- the pipeline card carries the target its
preloadfetched.
Two things are missing from that HTML on purpose: the status bar shows its
placeholder, because it is a façade fill, and the command list is empty,
because setup runs in an effect. Both appear a moment after hydration.
That page is the difference between the two channels, stated in markup instead of prose.
App router — RSC and streaming
The same HTML again, under React Server Components — tier 2 of
the RSC story. The manifests
follow the two-module discipline, so a server layout calls resolvePlugins
from create-slot/core and hands the whole Resolution across the client
boundary as a prop. The sidebar carries a host with no client half at all —
entriesOf plus ContributionBoundary in app/server-nav.tsx — and one slow
dashboard card streams in on its own, after the rest of the page.
Shared core
The features both Next.js shells mount, and where the patterns the library deliberately leaves to the application actually live:
resolveViews— exclusive claims, resolved into one table before render, reporting the plugin it refuseddescribeCatalog— an inventory, which is a.map- redux slices combined from the catalog, with
preloadfor server-side initial state, and a mobx store per application instance — see Plugin state crm-core/server— the server seam: plugin ids and per-request loaders in a module the server graph can import, separate from the manifests that point at it