Introduction
A quick look at our journey from classic templating engines to a dynamically extensible UI with Inertia.js.
Over the past five years, we’ve been searching for a convenient way to build the interface for our admin panel—something easy to update, expand, and scale. We started with Jade, then moved to EJS. Everything seemed fine. Later, we began removing jQuery from the project and switched to Vue. Vue had many strengths, but it turned out to be suboptimal for extensibility and scalability: each new component had to be bundled, and support for third-party libraries was often lacking.
For our admin framework (Adminizer), one key requirement is extensibility without restarting the Node.js process—in other words, realtime extensibility. We need to allow developers to create custom controls for model fields via an internal API. Although Adminizer provides all the essential UI controls out of the box, sometimes more is needed.
For example, in earlier versions of the admin panel, you could inject any component (e.g., JsonEditor
) using a <head><script>
tag and mount it via EJS to any database field requiring it. But how can we replicate this behavior in a SPA—without rebuilding or restarting?
How We Came to the Idea of Dynamic Bundling
Why we abandoned full rebundling and turned to dynamic component loading.
One of our developers raised a fundamental question: why bundle everything upfront? A SPA doesn’t need everything in one file, and modern tooling allows for dynamic component loading. So why not load the components we need—on demand, as independent modules?
That idea clicked. We envisioned an admin panel where each module is a separate UI component that gets injected into the system on the fly, without any recompilation—just via HTTP requests.
This led us to the concept of a modular SPA, and the technology that enabled it was Inertia.js. While this approach is widely used in Laravel/PHP, we had to make some adjustments for it to work well with our Node.js-based framework.
Why Inertia.js Was the Perfect Fit
Explaining how Inertia.js stands out from other frameworks.
Inertia is designed to work with components. When it needs to render a page, it gets the component name from the server response and uses a manifest to locate the appropriate file. All we had to do was write a wrapper component that dynamically imports the path passed through props.
Inertia acts as a bridge between the server and the SPA: on the backend, we specify which component to render, and Inertia takes care of loading and displaying it. The best part? That component doesn’t even need to exist at boot time—it can be created later and used immediately, without any need to rebuild the frontend.
The result? We can display anything we want on the page. Since React is globally available via the window context, the entire admin system becomes controllable as a live SPA. In theory, it could even be wrapped in Cordova to deliver a mobile app version (though we haven’t tested this yet—that’s for another article).
How the New Admin Architecture Works
Describing the practical implementation of dynamic extensibility.
Our new admin panel behaves like a modular SPA framework. Vite
loads React
globally via window
. When initializing a controller, we specify which component to load and from where—and if it exists, it gets rendered immediately. Most components live in individual modules, often inside node_modules
, and they require no rebundling.
Inertia handles routing and delivery. It “understands” what needs to be rendered, even for routes we didn’t explicitly configure. It parses HTML and embedded JSON metadata, loads only what’s necessary, and even keeps a request log for caching and rollback.
Why This Matters: Real-World Use Cases
Why we needed this flexibility—and what it enables.
Modules can now not only extend the UI but also inject custom fields, interactive controls, or even entire standalone pages. For example, if a module needs a special editing interface, it simply defines it—and it just works. All of this happens without touching the main admin codebase and without triggering a rebuild.
We believe this architecture is close to ideal for an admin panel, where modularity and flexibility are critical.
Conclusion: Where We Are Now
What’s done and what comes next.
We now have a fully working version 4 of our admin panel, built with this modular Inertia.js approach. It’s already running in production. We’re currently polishing the last parts and preparing documentation to share this solution with the community.
Hopefully, it’ll help others looking to build a flexible, scalable, and truly extensible admin panel—without the pain of constant rebuilds or server restarts.
Source code: https://github.com/adminization/adminizer
Top comments (0)