Modular Architecture with Nuxt Layers in Vue Projects
Table of contents
- The Invisible Challenge Every Frontend Dev Faces
- The Inspiration: Modular Backend Monoliths
- The Fundamental Principle
- The Traditional Organization Problem in Vue/Nuxt
- Nuxt Layers: The Silent Revolution
- How it works in practice
- The Transformative Benefits
- 1. Natural Domain Cohesion
- 2. Truly Parallel Development
- 3. Simplified Onboarding
- 4. Natural Boundaries and Encapsulation
- 5. Preparing for the Future
- Scenarios Where It Shines (and Where It Doesn’t)
- Perfect Scenarios ✅
- Where Maybe It Doesn’t Make Sense ❌
- Comparison with Other Approaches
- Monorepo with Workspaces
- Micro-frontends
- Implementing it in Practice: Simple Example
- Gradual Migration: The Secret to Success
- Phase 1: Identify Domains
- Phase 2: Create Base Layer
- Phase 3: Migrate a Pilot Domain
- Phase 4: Gradually Expand
- The Future is Modular
- Conclusion: Ready for the Next Step?
- Resources to Dig Deeper
The Invisible Challenge Every Frontend Dev Faces
You know this story: started a small Vue project, organized everything neatly - components here, pages there, stores in its place. Three months later, you have 147 components, and finding that ProductCardVariantB.vue has become an archaeological expedition.
Even worse: you need to move the shopping cart, but the files are spread across 7 different folders. A component here, a store there, the pages somewhere else… and that feeling that the organization that made sense in the beginning is now getting in the way of your work.
This is the silent problem of frontend scalability. And the solution comes from an unexpected place: backend architecture concepts applied to the browser world.
The Inspiration: Modular Backend Monoliths
In the backend world, there is an eternal debate: monolith or microservices? But architects like Sam Newman and Simon Brown proposed a third way: the modular monolith.
As Martin Fowler explains, a modular monolith is “a single application that maintains a highly modularized internal structure.” You have the benefits of modularization (separation of responsibilities, independent development, clear boundaries) without the operational complexity of microservices (multiple deployments, network communication, orchestration).
The Fundamental Principle
The genius idea is to organize the code by business domain instead of technical responsibility. On the backend, this means that everything related to “Payments” stays together - controllers, services, repositories, models. Everything about “Users” in another module. And so on.
But… what about the frontend?
The Traditional Organization Problem in Vue/Nuxt
Let’s be honest about how 99% of Vue projects are organized today:
my-ecommerce/
├── components/
│ ├── cart/
│ │ ├── CartItem.vue
│ │ ├── CartSummary.vue
│ │ └── CartDrawer.vue
│ ├── product/
│ │ ├── ProductCard.vue
│ │ ├── ProductGallery.vue
│ │ └── ProductReviews.vue
│ └── shared/
│ ├── Button.vue
│ └── Modal.vue
├── pages/
│ ├── products/
│ │ └── [id].vue
│ ├── cart.vue
│ └── index.vue
├── stores/
│ ├── cart.js
│ ├── product.js
│ └── user.js
└── composables/
├── useCart.js
└── useProduct.js
Looks organized, right? But let’s think about a real scenario:
“I need to implement a new feature in the cart”
You will need:
- Navigate to
components/cart/for components - Jump to
pages/cart.vuefor page - Go to
stores/cart.jsfor status logic - Check
composables/useCart.jsfor helper functions - Maybe even
api/cart.jsif you separated
There are 5 different directories to work on a single feature. Multiply this by each developer on the team, each feature, each day… and you understand why large projects become difficult to maintain.
Nuxt Layers: The Silent Revolution
With Nuxt 3 came a feature that went somewhat unnoticed but is revolutionary: Nuxt Layers. As Dave Stewart explains in his article about modular architecture, Layers allow you to completely reorganize the structure of the project.
Instead of organizing by file type, we organize by domain:
meu-ecommerce/
├── layers/
│ ├── cart/ # Everything about cart
│ │ ├── components/
│ │ │ ├── CartItem.vue
│ │ │ ├── CartSummary.vue
│ │ │ └── CartDrawer.vue
│ │ ├── pages/
│ │ │ └── cart.vue
│ │ ├── stores/
│ │ │ └── cart.js
│ │ ├── composables/
│ │ │ └── useCart.js
│ │ └── nuxt.config.ts # Cart-specific config
│ │
│ ├── product/ # Everything about products
│ │ ├── components/
│ │ ├── pages/
│ │ ├── stores/
│ │ └── nuxt.config.ts
│ │
│ └── catalog/ # Everything about catalog
│ ├── components/
│ ├── pages/
│ ├── stores/
│ └── nuxt.config.ts
└── nuxt.config.ts # Main config
How it works in practice
Each layer is like a Vue “mini-application”. It has its own structure, its own settings, its own modules. Nuxt then “sews” everything together at build time.
// main nuxt.config.ts
export default defineNuxtConfig({
extends: [
'./layers/cart',
'./layers/product',
'./layers/catalog'
]
})
And each layer can have its own configuration:
// layers/cart/nuxt.config.ts
export default defineNuxtConfig({
// Cart-specific modules
modules: ['@vueuse/nuxt'],
// Cart components
components: {
dirs: [{
path: './components',
prefix: 'Cart' // CartItem, CartSummary, etc.
}]
}
})
The Transformative Benefits
1. Natural Domain Cohesion
When you need to work on the cart, everything is in layers/cart. No need to jump between folders. It’s like having a mini-project dedicated just to that feature.
# Working on the cart? That's all:
cd layers/cart
# All related files are here
2. Truly Parallel Development
With isolated domains, different teams can work without stepping on each other’s toes:
- Team A working on
layers/checkout - Team B refactoring
layers/product - Team C creating new feature in
layers/recommendations
Less conflicts in Git, less “who moved my component?”, less stress.
3. Simplified Onboarding
New dev on the team? Instead of explaining the entire architecture:
“You will take care of the catalog. It’s all in layers/catalog. It’s like a separate Vue application, but integrated with the rest.”
Ready. In 5 minutes the person is already productive.
4. Natural Boundaries and Encapsulation
Each layer exposes only what needs to be public. The rest is encapsulated:
// layers/cart/index.ts - Public interface
export { useCart } from './composables/useCart'
export { CartButton } from './components/CartButton'
// CartDrawer, CartLogic, etc. stay private
5. Preparing for the Future
Today you have a modular monolith. Tomorrow, if you need, you can:
- Extract a layer as an NPM package
- Transform into micro-frontend
- Move to a separate repo
- Create a standalone application
The architecture is already ready for evolution.
Scenarios Where It Shines (and Where It Doesn’t)
Perfect Scenarios ✅
1. Medium E-commerce/Grande
- Clear domains: Catalog, Product, Cart, Checkout, User
- Complex but independent features
- Multiple teams or developers
2. B2B SaaS Applications
- Dashboard, Reports, Settings, Integrations
- Each module with its own rules and complexities
- Need for independent evolution
3. Portals and Marketplaces
- Seller area, buyer area, admin
- Different experiences and needs
- Specialized teams by area
4. Multi-tenant applications
- Shared Core
- Customizations per client in separate layers
- Facilitates white-labeling
Where Maybe It Doesn’t Make Sense ❌
1. Landing Pages and Institutional Websites
- Few components
- No clear business domains
- Unnecessary complexity
2. MVPs and Prototypes
- Speed > Structure
- Constant scope changes
- Very small team (1-2 devs)
3. Hypersimple Applications
- Basic CRUD
- Less than 20 components
- No growth perspective
Comparison with Other Approaches
Monorepo with Workspaces
monorepo/
├── packages/
│ ├── cart/
│ ├── product/
│ └── shared/
└── apps/
└── main-app/
Pros of Monorepo:
- Total physical separation
- Independent versioning
- You can publish on NPM
Pros of Nuxt Layers:
- Less configuration complexity
- Unique and optimized build
- Better DX (developer experience)
- HMR working perfectly
Micro-frontends
Micro-frontends are the extreme of modularization - completely independent applications that come together in the browser.
When Micro-frontends make sense:
- Completely independent teams
- Different technologies (Vue + React + Angular)
- Independent deployment is critical
- Massive scale (100+ devs)
When Nuxt Layers is better:
- Single team or few teams
- Standardized Stack Vue/Nuxt
- Want to avoid orchestration complexity
- Performance is priority
Implementing it in Practice: Simple Example
Let’s see a minimalist example of how to structure a blog + store:
// nuxt.config.ts
export default defineNuxtConfig({
extends: [
'./layers/base', // Shared
'./layers/blog', // Blog with Nuxt Content
'./layers/shop' // Simple shop
]
})
// layers/blog/nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
content: {
sources: {
blog: {
prefix: '/blog',
base: './layers/blog/content'
}
}
}
})
<!-- layers/shop/pages/shop.vue -->
<template>
<div>
<h1>Our Store</h1>
<ProductGrid :products="products" />
<!-- ProductGrid is in layers/shop/components/ -->
</div>
</template>
<script setup>
// Everything local to the shop domain
import { useShopStore } from '../stores/shop'
const shop = useShopStore()
const products = await shop.loadProducts()
</script>
Gradual Migration: The Secret to Success
The beauty of this architecture is that you don’t need to rewrite everything. You can migrate gradually:
Phase 1: Identify Domains
List the obvious domains of your application. These are usually the “big areas” that users see.
Phase 2: Create Base Layer
Move shared components, utils, generic composables.
Phase 3: Migrate a Pilot Domain
Choose the most isolated domain (usually something like “About” or “Blog”) and migrate as a test.
Phase 4: Gradually Expand
One domain at a time, as the team has time. It’s not a big bang, it’s evolution.
The Future is Modular
Modular architecture is not just a fad or a different way of organizing folders. It’s a fundamental shift in how we think about the structure of frontend applications.
As Eric Evans explains in Domain-Driven Design, software should reflect the business domain. With Nuxt Layers, we finally have an elegant way to do this on the frontend.
Conclusion: Ready for the Next Step?
If you’ve made it this far, you’re probably thinking, “Okay, the theory is beautiful, but how do I actually implement it?”
Great question! In the next article, we will move away from theory and build a complete e-commerce from scratch using Nuxt Layers. Let’s implement:
- Catalog system with filters and search
- Product pages with gallery and reviews
- Shopping cart with persistence
- Real integration between layers
- Testing, deployment and optimizations
With real, working code that you can copy and adapt.
Resources to Dig Deeper
- Official Nuxt Layers Documentation - The definitive source
- Nuxt Layers Unwrapped - In-depth article by Dave Stewart
- Domain-Driven Design - Eric Evans on domain modeling
- Building Evolutionary Architectures - About architectures that evolve