Gabriel Caiana

Modular Architecture with Nuxt Layers in Vue Projects


Table of contents
  1. The Invisible Challenge Every Frontend Dev Faces
  2. The Inspiration: Modular Backend Monoliths
  3. The Fundamental Principle
  4. The Traditional Organization Problem in Vue/Nuxt
  5. Nuxt Layers: The Silent Revolution
  6. How it works in practice
  7. The Transformative Benefits
  8. 1. Natural Domain Cohesion
  9. 2. Truly Parallel Development
  10. 3. Simplified Onboarding
  11. 4. Natural Boundaries and Encapsulation
  12. 5. Preparing for the Future
  13. Scenarios Where It Shines (and Where It Doesn’t)
  14. Perfect Scenarios ✅
  15. Where Maybe It Doesn’t Make Sense ❌
  16. Comparison with Other Approaches
  17. Monorepo with Workspaces
  18. Micro-frontends
  19. Implementing it in Practice: Simple Example
  20. Gradual Migration: The Secret to Success
  21. Phase 1: Identify Domains
  22. Phase 2: Create Base Layer
  23. Phase 3: Migrate a Pilot Domain
  24. Phase 4: Gradually Expand
  25. The Future is Modular
  26. Conclusion: Ready for the Next Step?
  27. 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:

  1. Navigate to components/cart/ for components
  2. Jump to pages/cart.vue for page
  3. Go to stores/cart.js for status logic
  4. Check composables/useCart.js for helper functions
  5. Maybe even api/cart.js if 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

  1. Official Nuxt Layers Documentation - The definitive source
  2. Nuxt Layers Unwrapped - In-depth article by Dave Stewart
  3. Domain-Driven Design - Eric Evans on domain modeling
  4. Building Evolutionary Architectures - About architectures that evolve