"What framework did you use?" I hear this quite often from other devs when I share my journey building the ERP system I completed this year.
1. Freedom
Every time I started building with Svelte, React or Next.js I felt... constrained. I don't want to criticise these technologies, they are great and each has its audience. Back in the day they solved real limitations of JavaScript around code organisation, modularity, component architecture and state management.
But today things are different. JavaScript itself has grown up. You can build reusable components, use ES module imports natively, write clean arrow functions and structure things in an object oriented way without a framework telling you how.
There is one catch though. JavaScript is not a framework. No enforced structure, no constraints, absolute freedom to build things exactly the way you want. Including doing them completely wrong. That is what most developers associate with the word "Vanilla" and honestly I get it.
Here is a snippet from the dashboard where I manage ERP users:
A few things worth noting:
1. Many imports. Helps with modularity. The cool thing is this runs directly in the browser without compilation, though I would not recommend skipping it in production. More on that below.
2. Classes. I use them mainly as entry points for each page of the ERP while keeping everything else in exportable modules outside. No deep reason for classes here really, I just find the readability better and the patterns I repeat across pages help me navigate the codebase faster.
3. Repeatable pattern. Every page class follows the same structure:
jsconstructor → init → html → render → listeners
Works for Users, Manufacturing Journal, Orders, everything.
4. Universal components like Locale, Session, Header and Footer shared across the whole system.
2. Aesthetics and Minimalism
This matters less now that AI generates so much code but still. The freedom JavaScript gives you to structure things however you want makes it easy to keep things clean.
Code should look good. Bad looking code belongs in bad projects.
Simple repeatable methods like init and html, shared classes like Header and Footer, make the codebase easy to read without any bloat or unnecessary dependencies weighing things down.
I skip types. Simple variables and objects passed through methods or event bus handlers. I know the argument, there is no reason not to use types, but I don't care as long as everything works and the code stays concise. For a solo built system where I know every data structure by heart this works fine. I would not necessarily recommend this to a larger team.
3. Full Stack JavaScript
If I broke down my programming time by language it would go: PHP, JavaScript, Java, Python. Of those JavaScript and Python appeal to me most. Python's conciseness is great but the indentation style is not for me.
Node.js is pure JavaScript on the backend. When I was working with Next.js I kept asking myself: wait, is this frontend or backend? I think beginners run into this too. At first I thought I was just inexperienced but I now think it is a structural issue. With a different backend language the boundary is obvious immediately.
The biggest practical advantage of JavaScript on both ends is sharing identical logic. In my ERP I calculate various VAT rates across different scenarios. Things get complicated fast in the EU depending on whether your company is VAT registered and whether goods fall under a special tax regime.
I created a packages folder at the root of the project that shares the exact same tax calculation code across PDF invoices, accounting reports and live frontend quotation previews:
One source of truth for something this critical is not optional.
4. Performance
React carries 100KB+ before anything else loads. JavaScript runs natively in the browser. Everything else compiles down to it first. JavaScript just runs, straight from the start.
With a single JS file I can fetch everything needed for full UI rendering and localisation in one shot. The moment the script loads it executes.
Here are the Lighthouse scores. Most pages hit 100% without a single optimisation pass:
Some pages drop to around 80% but that is mainly due to large datasets and servers sitting thousands of kilometres away, not the architecture.
For the inventory page above the entire thing is one JS file at 30KB. I do use Bootstrap which adds 81KB of JS on top, that is a deliberate trade for UI consistency and something I have not optimised away yet. But with 100% Lighthouse scores across most pages the question becomes whether there is anything left worth optimising.
5. No Dependency Hell
How many times have you been there. A package needs updating because it is outdated or worse vulnerable. You run the update, get lucky, and then something breaks somewhere completely unrelated.
Basic projects end up with hundreds of dependencies. Now imagine an ERP with 20+ journals, reports and manufacturing modules all tightly coupled. When something breaks you are stuck.
I am not saying I avoid dependencies entirely. Three.js is a good example of one I rely on. But overall the project is much cleaner for it. My node_modules mostly feeds the Node backend, not the frontend.
AI
Is there an article that does not mention AI today. So here we go.
Something I keep thinking about is how ERP software updates should actually work in the future. The traditional model is one shared codebase, customisations live in their own layer, new releases keep everyone on the same version. It works but it is rigid.
What if the model flipped. Shipping code is cheaper and faster than it has ever been. What if every client just gets their own codebase from day one. No modifying a core that affects everyone. No releases nobody asked for. Each installation just evolves alongside the company using it.
This alone deserves a separate article but the part that connects to vanilla JS is this. I want to build an environment where AI agents can navigate the codebase, understand it and make changes directly based on user feedback. Not in staging. In production.
I know that sounds alarming. But imagine a user complains about an awkward button. Two minutes later it is a toggle instead of a checkbox and everyone moves on. No ticket, no sprint, no deployment pipeline.
The decoupled way my ERP pages are structured, and the fact that almost every part can be overridden through an overrides folder, gives me a starting foundation where AI generated changes are contained, easy to test visually and easy to revert without recompiling anything or restarting a container.
No compilation means you drop the code and check it in the browser immediately. I genuinely want to know if there is a workflow in React or Next.js that gets close to this.
Things I Learned Along the Way
Document title timing
All my data fetching is packed into a single JS call after page load. This works fast but it means I set the document title after the data arrives since that lives in index.html. Minor but worth knowing going in.
Decoupled pages
I moved toward treating each section of the UI as its own isolated thing. No centralised routing, no injected scripts that may not be needed on every page. The product editing page is heavy, 3D declarations, image uploads, an .obj viewer with Three.js. Keeping it isolated means that complexity stays contained.
State management
This is the one thing I wish I had done better. I built helper functions like onClick and onChange because writing document.addEventListener everywhere felt unnecessarily verbose. They work well until the frontend grows. Then partial DOM updates cause some listeners to go missing while others fire twice.
This means I end up solving problems React and Svelte have already solved. React through the virtual DOM, Svelte by manipulating the DOM directly.
The fix that actually worked was declaring listeners directly in the HTML. It sounds old school but it holds up:
- That element always has the same reference no matter what happens to surrounding code. No separate initialisation needed.
- You can see exactly what is listening right where the element lives.
- No double executions, no mystery triggers.
Bundling
Running without compilation with many ES module imports causes browser caching issues that stick around even in incognito. A simple Rollup script fixed the live reloading and the caching disappeared. Loading 20+ individual imports is not ideal in production but the fact that something as complex as an ERP can run this way, no bundles, no framework, no frontend compilation step, is still something I find genuinely interesting.
Maybe it is the future. Who knows.



Top comments (0)