Dev.to has regular "What was your win this week?" discussion. For me this time it was definitely "I finished and released new version of my open-source Nuxt project". Allow me to introduce it now.
What is it and why should you care?
I fell in love with Nuxt. It's the best framework for building modern websites I know and working with it is so satisfying I refuse to spend time looking for alternatives. So yea, you may argue with me, advocating for your favorite solutions, but it is unlikely, I will listen. If you want to know it better, I have an ongoing tutorial series.
There is one issue that had troubled me though. The Nuxt core is (deliberately) small and many common tasks require external tools and libraries. Nuxt authors made it easy to integrate stuff together, but you still have to do it. Now imagine having multiple projects, each with dozen or more dependencies. And your dependabots keep screaming about updates. And you keep having to manage them. Not once, but multiple times. And overnight there's a new vulnerability and tomorrow here we go again...
When I get bothered by this, I started to think about an optimization.
What if you can depend on just one npm package and have access to everything you need? Database connector, UI library, forms, validation, you name it...
But I didn't want to create an opinionated solution that will lock everyone into one way of doing things. Instead, Nuxt Ignis is meant to be optionated (is that even a word?). It should provide reasonable defaults based on known best practices and trends, but it shouldn't hold users back from tailoring their own combinations.
Now how to do it?
How I started and how I nearly failed
Most re-usable integrations with Nuxt are made via a feature called modules. Nuxt modules are unified API bridges to augment bare Nuxt app with 3rd party tools. And you basically just need to tell in the configuration heart, the nuxt.config.ts file which modules you want to turn on.
So if the list of modules can be dynamic and configuration-based, the application can become a dynamic wrapper around 0-n integrations. However, there is a limit. The contents on nuxt.config.ts must be static at build time. Vite builds the Nuxt app whatever you like it, but it cannot re-build it afterwards. D'oh!
After some experimenting, I figured out a workaround. I proclaimed Nuxt Ignis to be a layer, which is another cool Nuxt feature giving you an inheritance-like behavior. Your project will extend from nuxt-ignis, re-use what is declared there, and the build happens on your machine (or in your production) when you say so. The only question was how you can affect nuxt.config.ts without having to write everything by hand yourself?
I solved that as well. The defineNuxtConfig helper inside nuxt.config.ts accepts an object as its argument. Typically, you inline it and all the values are purely static. But there is no actual limit holding you back from passing a result of a function. And this function will get executed automatically when Nuxt build is executing defineNuxtConfig to process its argument. And because we are in Node.js (or other JS runtime), we have access to process.env. And so we can read provided environment variables and build the static config object dynamically enough.
I created a function called simply setFeatures. This is how the last version looked like: Nuxt Ignis' features.ts (v0.5.3)
It does its job perfectly. When I wanted Nuxt UI, it gave me Nuxt UI. When I wanted Supabase, I got it. When I wanted nothing, I got (nearly) bare Nuxt. When I wanted everything...
Well, that soon started to be a problem. The solution that looked so promising started to choke on itself pretty soon. The number of dependencies created a mess, the bundle size grew, build times prolong and waiting for dev server to spin up soon started to be problem. And it was "only" like 25 integrations yet. I definitely planned more.
Back to the drawing board
I had six websites that run on Nuxt Ignis v0.5.3 without apparent issues. You can check three of them here to get the idea. But I wasn't satisfied with the performance. When the DevEx was hurting me, it would surely hurt any of the potential users. For some time, I didn't know what I should do. In fact, I was considering abandoning the project as non-viable solution. A nice thought experiment, but a practical dead end.
Then I met a guy during last year's PragVue conference (btw. 2026 issue is in the making and you're very much welcome to join us there). He pulled the right string in the back of my mind by suggesting using Nuxt Modules for more convenient configuration.
It took couple of weeks before I turned the crude idea into some believable plan. Then I started experimenting and implementing, shattered everything in pieces and slowly re-build it back.
Side note - AI (Copilot) helped me a lot during this phase. I knew where I wanted to go, but it was Copilot using Claude Opus who dug through most of the docs and source codes and did the heavy lifting. I honored The AI Manifesto and made sure I understand everything what was happening in my codebase. Sometimes we moved fast, other days we moved slower. Sometimes we got strayed. Sometimes I had to step in and ask for serious refactors AI didn't see itself. I might write a separate article about the process one day. For now, all you need to know is we got there eventually. It was inspiring and fun. And the most important thing - it worked!
New modular solution
The key structural change of the "new" Nuxt Ignis is breaking the one tangled package into separate modules divided by logical domains. The core nuxt-ignis package is still a Nuxt layer, but instead of having everything packed inside itself, it leverages the composition power of Nuxt modules.
So far, I have identified 7 distinguishable areas (though it might not be the final number). Every module is now a separate npm package prefixed @nuxt-ignis/* and it is wrapped as a literal Nuxt Module. It could have been done, because Nuxt modules can depend on another modules. They can declare modules they need inside moduleDependencies within defineNuxtModule function. It is more than a shopping list of module names; it also allows passing configuration into them. This allows Nuxt Ignis to provide defaults. And it also allows future users to overwrite them if they need. And because it is an executable function, it can contain dynamic JS logic and return different lists of modules based on user config values. When Nuxt resolves its modules, it recursively resolves all the declared module dependencies and activates those that were requested. Either none or some or all.
This is how the assembling got delegated from "core" nuxt-ignis package into separate submodules. The former setFeatures custom function fully vanished. Nuxt is pretty effective in loading modules, and it definitely runs faster than before while maintaining the same level of flexibility. The core layer and the seven internal modules are now all rather small wrappers around transitive dependencies. Your package manager will still download everything into your node_modules (I don't see a way to avoid this), but dev and production builds only work with what is requested. Builds are much faster and bundles smaller.
Target users still only depend solely on nuxt-ignis. And they got one interesting benefit - now ignis config key can be used inside nuxt.config.ts and you have intelli-sensed and type-safe way of configuring the outcome. You just need to run nuxt dev command once so the .d.ts files get created. It is much more convenient than having to rely on environment variables that were easier to misspell and harder to debug. But if you prefer them or if you need different configs in different environments without having to rebuild, env variables still work (and have higher precedence).
I have tested the new version within all six websites running on Nuxt Ignis so far. Everything works as intended.
Therefore, I officially proclaim nuxt-ignis@0.6.0 released and ready for use.
I would be glad for any feedback, comments or complaints.
What's next?
I already have a number of ideas that should be done. Especially more integrations with other useful Nuxt modules and 3rd party libraries. This will either prove the new modular architecture works well or bury the idea again.
Then there is one big and important question I am indifferent to yet.
Should Nuxt Ignis really try to glue together like "every" possible variant?
I.e. some use Nuxt UI, some would prefer PrimeVue, others something else. There is Vueform, Formkit, Formish, Formwerk and who knows what. You can validate with Zod, Valibot, VeeValidate...
So? Dozens of (more or less) equivalent integrations? Or should Nuxt Ignis actually become opinionated more and pick one solution for each domain and "only" allow users to decide whether to use it or not?
Honestly, I don't know. Both approaches have pros and cons.
What I'd really need right now is the feedback from real users (providing there are some and it is not just my personal fancy toy). So I hope this will reach to someone who finds it useful and will want to share its opinion. I am looking forward to hearing from you.
Top comments (1)
The question at the end is probably the most important one.Every framework starts flexible. The successful ones eventually develop opinions.
Too much flexibility creates configuration burden. Too many opinions create lock-in. Finding the balance between those two is usually harder than the technical implementation itself.