The entire Squilla core admin SPA is about 4,000 lines of React. That is the auth flow, the sidebar, the dashboard, and the extension loader. That is it. Nothing else lives in there.
Every feature page you actually use in day-to-day editing, the media library, email rules, forms, SEO config, sitemap settings, is a separate Vite build owned by an extension. The shell loads each one at runtime by reading an import map and pulling shared dependencies off window.__SQUILLA_SHARED__. The core admin never imports them. It does not even know what they render.
People assume this is over-engineering for a CMS. I want to walk through why I went this direction, because every reason maps to a real pain I have hit on other platforms.
1. Core admin does not accrete features
The default failure mode of any CMS admin is feature creep. Someone adds a media tab. Then SEO settings. Then a forms inbox. A year later the core admin is 80,000 lines of React and nobody can rip anything out without breaking three other screens.
With a shell, that pressure has nowhere to go. If you want a media library, you build a media extension. The shell stays at 4,000 lines forever, because there is nothing to add to it. The only way to grow the shell is to expand what shell-level concerns exist, and those are basically frozen: log in, navigate, see a dashboard, load an extension.
2. Extensions ship their own React UIs
Each extension is a full Vite project. It picks its own component patterns, its own state management, its own routing inside its mounted subtree. The shell hands it a DOM node and a context, and gets out of the way.
This sounds chaotic until you realise the alternative is worse. In a monolithic admin, every plugin author has to learn your component library, your form abstractions, your table primitives, your toast system. They will get it wrong. Their code will look foreign next to yours. Worse, they will work around your abstractions in ways that make upgrades terrifying.
When extensions own their own builds, they are free to be themselves. I publish a shared @squilla/ui package for the parts that should look consistent, buttons, inputs, the sidebar layout, but anyone is free to ignore it.
3. Update one extension without redeploying core
This is the operational win. The media extension ships a bug fix. I rebuild the media extension Vite bundle. I deploy it. The core admin SPA does not get rebuilt, does not get re-served, does not invalidate anyone's session. Existing tabs in users' browsers keep working with whatever version they loaded.
On a monolithic admin, every change to any feature triggers a full SPA rebuild and redeploy. That is a lot of risk for fixing a typo in a settings form.
4. Third-party developers never touch core
This is the one I care about most. If you are building a Squilla extension, you do not need to clone the core repo. You do not need to learn how the sidebar works. You do not need to find the right slot to register your menu item in some giant routing config. You ship an extension.json that declares your admin routes and menu entries, and you ship a Vite bundle that exports a React component. The shell picks it up.
That is the only path I want for third-party authors. The core repo should be irrelevant to them.
The honest cost
This is not free. There are two real costs I have absorbed.
First, the import map setup is fiddly. The shell publishes shared deps (React, the UI kit, the API client, the icon set) on window.__SQUILLA_SHARED__ and registers an import map shim so extension bundles resolve react to the shell's copy instead of bundling their own. Getting that handshake right took a few iterations, and it is the kind of thing that fails loudly when someone misconfigures their Vite externals.
Second, shared dependency versions need coordination. If the shell is on React 18 and an extension is on React 19, things break in subtle ways. So there is a contract: extensions target the shell's major versions. I publish those versions in the docs and bump them deliberately.
Both of those costs are real, and both feel worth it every time I update one extension without touching the others.
How other CMSes do it
WordPress puts every plugin's admin UI inside WP-Admin chrome. The plugin author writes PHP that emits HTML into pages WordPress controls. It works, and it means plugin admins all look the same, but it also means a plugin cannot ship a modern React experience without fighting the host. And the host's UI is locked to whatever WordPress decided years ago.
Strapi is the opposite extreme. The admin is one big monolithic React app. Plugins extend it by registering into the same bundle, which means a plugin's UI changes require a full admin rebuild, and plugin authors have to fit into Strapi's design system whether it suits their feature or not.
Squilla's bet is that the shell should do the minimum that lets every extension do its own thing. The core admin handles auth, navigation, and loading. Everything else belongs to whoever owns the feature.
Four thousand lines of React, and the rest of the CMS is somebody else's problem. That is the whole point.