App shell
The app shell is the host application — the only thing end users download directly. It provides the stage that all pilets perform on.
The golden rule
The app shell should contain only things that are genuinely universal. Feature logic belongs in pilets.
Every change to the app shell is a full deploy that affects all users and requires pilet teams to eventually update their emulators. Keep it thin, stable, and focused on infrastructure.
What the app shell owns
Creating an instance
Layout components
Override these in state.components:
Layout — The outermost wrapper. Everything visible on every page. The children prop is where pilet pages render.
Router — Provides a React Router context. Defaults to BrowserRouter.
RouteSwitch — Renders the page component matching the current URL. Wrap with Suspense here to handle lazy-loaded pilet pages gracefully.
Error components — Provide fallback UIs for each error type: not_found, unknown, loading, page.
Centrally shared dependencies
Declare packages that all pilets receive as externals in the app shell's package.json:
These are pure externals — pilets never bundle them. The shell provides the running instance at runtime.
This central importmap is for packages that all pilets need. For domain-specific packages, pilets can declare their own importmap entries and share them with each other without involving the app shell. See Pilets — sharing dependencies.
Plugins
Plugins extend the Pilet API. Install them as packages and add to createInstance:
These are ordinary npm packages added to the app shell — pilet developers never install them directly. They surface in pilets automatically through the typed PiletApi.
Each plugin adds typed methods to PiletApi. After adding piral-notifications, pilets get api.showNotification(...). TypeScript types update automatically.
Building
The build produces:
dist/release/— Static files ready to deploy to a CDN or static hostdist/emulator/my-app-shell-x.y.z.tgz— The emulator package to publish to npm
Publish the emulator so pilet teams can develop locally:
Common mistakes to avoid
Don't put feature components in the app shell. If it's specific to one team's domain, it should be a pilet.
Don't hardcode pilet lists. That's the feed service's job. The app shell should only know the feed URL.
Don't define routes in the app shell. Pilets register their own routes.
Don't version-lock the shell to specific pilets. The whole point is they evolve independently.