{"id":13,"date":"2026-06-06T21:48:26","date_gmt":"2026-06-06T21:48:26","guid":{"rendered":"https:\/\/softwareproduction.eu\/wordpress\/?p=13"},"modified":"2026-06-07T01:21:46","modified_gmt":"2026-06-07T01:21:46","slug":"architecting-enterprise-nuxt-with-custom-modules","status":"publish","type":"post","link":"https:\/\/softwareproduction.eu\/?p=13","title":{"rendered":"Architecting Enterprise Nuxt with Custom Modules"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Nuxt\u2019s module system is not just an add-on; it <em>is<\/em> the architecture. In a large enterprise Nuxt 4 stack, modules become the primary building blocks: they enforce boundaries, encapsulate behavior, and give you a clean mental model that scales beyond a handful of features and a couple of developers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This masterclass walks through how to architect such a system:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Why you need <strong>modules, not folders<\/strong><\/li>\n<li>How to design <strong>module anatomy<\/strong> and categories<\/li>\n<li>How to use <strong>hooks<\/strong> and the <strong>event bus<\/strong> (sparingly) for communication<\/li>\n<li>How to structure <strong>UIKit<\/strong>, <strong>commands<\/strong>, and <strong>debug tooling<\/strong> as modules<\/li>\n<li>How to build <strong>production-ready Nuxt modules<\/strong> with conventions that hold up at scale<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">From Folders to Real Boundaries<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Many applications start tidy: neat folders, a clear mental model, readable imports. A few years and a growing team later, you often see:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cross-cutting imports<\/li>\n<li>Circular dependencies<\/li>\n<li>Features reaching into each other\u2019s internals<\/li>\n<li>\u201cJust one more\u201d helper in <code>utils\/<\/code> that becomes everyone\u2019s dependency<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The standard response is more folders: <code>components\/<\/code>, <code>composables\/<\/code>, <code>services\/<\/code>, <code>utils\/<\/code>. But folders are <em>suggestions<\/em>, not boundaries. Nothing stops a file in <code>components\/<\/code> from importing <code>services\/checkout\/<\/code> internals that were never meant to be public.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Modules are boundaries.<\/strong> A module decides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>What it <strong>exports<\/strong> (the public API)<\/li>\n<li>What stays <strong>internal<\/strong><\/li>\n<li>How it is <strong>configured<\/strong><\/li>\n<li>How it is <strong>documented<\/strong><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Nuxt modules are exactly this abstraction.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Modules Are the Architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nuxt modules are not plugins or random utility packages. They are the <strong>architectural units<\/strong> of a Nuxt application:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>They run at <strong>build time<\/strong> to:<\/li>\n<li>Register auto-imported composables and components<\/li>\n<li>Add server handlers and middleware<\/li>\n<li>Inject plugins<\/li>\n<li>Modify Nuxt configuration<\/li>\n<li>Hook into Nuxt and Nitro lifecycle events<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>They provide the <strong>runtime code<\/strong> that powers your app:<\/li>\n<li>Composables<\/li>\n<li>Components<\/li>\n<li>Server plugins<\/li>\n<li>Request middleware<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In a stack with <strong>35+ custom modules<\/strong>, module development <em>is<\/em> feature development. You don\u2019t \u201cadd a feature\u201d; you \u201cadd a module\u201d. That\u2019s the shift.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Module Anatomy: One Shape for Everything<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every module follows the same structure:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TD\n  A[modules\/my-module\/] --&gt; B[\"index.ts&lt;br\/&gt;(Module definition - build time)\"]\n  A --&gt; C[runtime\/]\n  A --&gt; D[\"types.d.ts&lt;br\/&gt;(type declarations)\"]\n  A --&gt; E[\"README.md&lt;br\/&gt;(documentation)\"]\n\n  C --&gt; C1[\"composables\/&lt;br\/&gt;useMyFeature.ts\"]\n  C --&gt; C2[\"components\/&lt;br\/&gt;MyComponent.vue\"]\n  C --&gt; C3[server\/]\n  C --&gt; C4[\"plugins\/&lt;br\/&gt;my-plugin.ts\"]\n\n  C3 --&gt; C3a[middleware\/]\n  C3 --&gt; C3b[\"plugins\/&lt;br\/&gt;(server plugins)\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This structure codifies the <strong>two phases<\/strong> of a module\u2019s life.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 1: Build Time (<code>index.ts<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>index.ts<\/code> calls <code>defineNuxtModule()<\/code>. This code runs when Nuxt starts (dev server or production build):<\/p>\n\n\n\n<pre><code class=\"language-ts\">export default defineNuxtModule({\n  meta: { name: 'my-module' },\n  setup(options, nuxt) {\n    \/\/ Register composables for auto-import\n    addImports({\n      name: 'useMyFeature',\n      from: resolve('.\/runtime\/composables\/useMyFeature'),\n    })\n\n    \/\/ Add server handlers\n    addServerHandler({\n      route: '\/api\/my-module\/status',\n      handler: resolve('.\/runtime\/server\/status.get'),\n    })\n\n    \/\/ Add plugins\n    addPlugin(resolve('.\/runtime\/plugins\/my-plugin'))\n\n    \/\/ Configure runtime via runtimeConfig\n    nuxt.options.runtimeConfig.myModule = {\n      enabled: true,\n      apiUrl: process.env.MY_MODULE_API_URL,\n    }\n\n    \/\/ Hook into the build lifecycle\n    nuxt.hook('build:before', () =&gt; {\n      \/\/ e.g., validate config, generate files\n    })\n  },\n})<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is where the module <strong>declares its presence<\/strong> in the app: what it auto-imports, which APIs it exposes, and how it shapes Nuxt\u2019s configuration.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[Nuxt startup] --&gt; B[\"index.ts&lt;br\/&gt;defineNuxtModule setup()\"]\n  B --&gt; C[Register auto-imports]\n  B --&gt; D[Add server handlers]\n  B --&gt; E[Add plugins]\n  B --&gt; F[Configure runtimeConfig]\n  B --&gt; G[Register build hooks]<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 2: Runtime (<code>runtime\/<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Everything under <code>runtime\/<\/code> is executed when the app runs (SSR and\/or browser):<\/p>\n\n\n\n<pre><code class=\"language-text\">runtime\/\n\u251c\u2500 composables\/useMyFeature.ts   # Auto-imported composable\n\u2502   \u2192 Used directly in pages\/components\n\u2502\n\u251c\u2500 server\/plugin.ts              # Nitro server plugin\n\u2502   \u2192 Runs once at server start\n\u2502   \u2192 Not shipped to the browser\n\u2502\n\u251c\u2500 server\/middleware\/guard.ts    # Server middleware\n\u2502   \u2192 Runs on every HTTP request\n\u2502\n\u2514\u2500 plugins\/init.client.ts        # Client-only Nuxt plugin\n    \u2192 Runs once after hydration<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart TD\n  A[runtime\/] --&gt; B[\"composables\/useMyFeature.ts&lt;br\/&gt;Used in pages\/components\"]\n  A --&gt; C[\"server\/plugin.ts&lt;br\/&gt;Runs once at server start&lt;br\/&gt;Server only\"]\n  A --&gt; D[\"server\/middleware\/guard.ts&lt;br\/&gt;Runs on every HTTP request\"]\n  A --&gt; E[\"plugins\/init.client.ts&lt;br\/&gt;Client-only plugin&lt;br\/&gt;Runs after hydration\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>index.ts<\/code> = <strong>build-time configuration<\/strong><\/li>\n<li><code>runtime\/<\/code> = <strong>application-time behavior<\/strong><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Many subtle bugs come from mixing up those two.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Module Categories and Dependency Rules<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With 35+ modules, categorization stops being a luxury and becomes a necessity. A typical categorization in a large enterprise application looks like this:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph Core[Core Modules]\n    direction LR\n    core1[graphql-server]\n    core2[gqlt]\n    core3[i18nt]\n    core4[logging]\n    core5[uikit]\n  end\n\n  subgraph Features[Feature Modules]\n    direction TB\n    f1[chatbot]\n    f2[shopping-cart]\n    f3[product-search]\n    f4[notifications]\n    f5[forms]\n  end\n\n  subgraph Integrations[Integration Modules]\n    direction TB\n    i1[cms-client]\n    i2[cloud-auth]\n    i3[secrets-vault]\n    i4[app-insights]\n    i5[tag-manager]\n  end\n\n  subgraph Debug[Debug Modules]\n    direction TB\n    d1[debug-panel]\n    d2[debug-chatbot]\n    d3[debug-cms]\n    d4[debug-abtests]\n    d5[debug-pinia]\n  end\n\n  Core --&gt; Features\n  Core --&gt; Integrations\n\n  Features --&gt; Debug\n  Integrations --&gt; Debug<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Strict dependency direction:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Core modules<\/strong> sit at the bottom:<\/li>\n<li>Everything can depend on them<\/li>\n<li>They do not depend on feature, integration, or debug modules<\/li>\n<li><strong>Feature modules<\/strong> implement user-facing capabilities:<\/li>\n<li>They depend only on core modules<\/li>\n<li>They never import each other<\/li>\n<li><strong>Integration modules<\/strong> wrap external systems:<\/li>\n<li>They depend on core modules<\/li>\n<li>They do not depend on feature modules<\/li>\n<li><strong>Debug modules<\/strong> depend on whatever they inspect:<\/li>\n<li>They are loaded only in non-production environments<\/li>\n<li>They are fully tree-shaken out of production builds<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This explicit dependency rule is where modular monolith discipline lives in Nuxt.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Hooks: The Communication Layer Between Modules<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Beyond direct imports, Nuxt exposes a <strong>hook system<\/strong> that lets modules react to lifecycle events or extend each other without coupling.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Common Build-Time Hooks<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">These run while Nuxt is bootstrapping:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph BuildTimeHooks[Build-Time Hooks]\n    direction TB\n    h1[\"modules:before&lt;br\/&gt;Before any module runs\"]\n    h2[\"modules:done&lt;br\/&gt;After all modules\"]\n    h3[\"build:before&lt;br\/&gt;Before Vite\/webpack\"]\n    h4[\"build:done&lt;br\/&gt;After build completes\"]\n    h5[\"components:dirs&lt;br\/&gt;Register component dirs\"]\n    h6[\"imports:dirs&lt;br\/&gt;Register import dirs\"]\n  end\n\n  A[Nuxt bootstrap] --&gt; h1 --&gt; h2 --&gt; h3 --&gt; h4\n  A --&gt; h5\n  A --&gt; h6<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use these to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Add or alter component directories<\/li>\n<li>Register auto-import directories<\/li>\n<li>Generate code or files before the build<\/li>\n<li>Validate configuration<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Runtime Hooks (Nitro)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">At runtime, Nitro exposes hooks for server-side behavior:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph RuntimeHooks[Runtime Hooks]\n    direction TB\n    r1[\"request&lt;br\/&gt;On every HTTP request\"]\n    r2[\"render:html&lt;br\/&gt;Modify SSR HTML output\"]\n    r3[\"afterResponse&lt;br\/&gt;After response sent\"]\n    r4[\"error&lt;br\/&gt;On unhandled error\"]\n  end\n\n  C[Incoming HTTP request] --&gt; r1 --&gt; r2 --&gt; r3\n  C --&gt; r4<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These are how modules implement cross-cutting server concerns (logging, security headers, etc.) <em>without<\/em> importing each other.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Custom Hooks: Inversion of Control Between Modules<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Modules can define and emit <strong>custom hooks<\/strong> to allow other modules to plug into them. For example, a debug chatbot module can discover tools from other modules like this:<\/p>\n\n\n\n<pre><code class=\"language-text\">Module A (debug-chatbot): defines hook\n  nitroApp.hooks.callHook('mcp:setup', toolRegistry)\n\nModule B (shopping-cart): listens for hook\n  nitroApp.hooks.hook('mcp:setup', (tools) =&gt; {\n    tools.push({\n      name: 'get-cart',\n      handler: () =&gt; getCartState()\n    })\n  })\n\nResult:\n  - Module A exposes an extension point\n  - Module B registers tools into it\n  - No direct imports between A and B<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n  participant A as Module A&lt;br\/&gt;(debug-chatbot)\n  participant Nitro as nitroApp.hooks\n  participant B as Module B&lt;br\/&gt;(shopping-cart)\n\n  A-&gt;&gt;Nitro: callHook('mcp:setup', toolRegistry)\n  Nitro-&gt;&gt;B: mcp:setup(tools)\n  B--&gt;&gt;Nitro: tools.push({ name: 'get-cart', handler })\n  Nitro--&gt;&gt;A: toolRegistry populated<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is <strong>inversion of control<\/strong>: the consumer (debug-chatbot) defines <em>where<\/em> to extend; producers (feature modules) decide <em>how<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Runtime Configuration as the Configuration Backbone<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Module configuration lives in <code>nuxt.options.runtimeConfig<\/code>, not in custom config files.<\/p>\n\n\n\n<pre><code class=\"language-ts\">\/\/ modules\/my-module\/index.ts\nexport default defineNuxtModule({\n  setup(options, nuxt) {\n    nuxt.options.runtimeConfig.myModule = {\n      enabled: true,\n      apiUrl: process.env.MY_MODULE_API_URL || 'https:\/\/default.api.com',\n      cacheTtl: parseInt(process.env.MY_MODULE_CACHE_TTL || '3600'),\n    }\n  },\n})<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[Environment variables] --&gt; B[\"defineNuxtModule setup()\"]\n  B --&gt; C[nuxt.options.runtimeConfig.myModule]\n  C --&gt; D[\"Server code&lt;br\/&gt;useRuntimeConfig()\"]\n  C --&gt; E[\"Client composables&lt;br\/&gt;useRuntimeConfig()\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Why this matters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Container-friendly<\/strong>: everything can be overridden via environment variables<\/li>\n<li><strong>Typed<\/strong>: config shape is declared in <code>types.d.ts<\/code><\/li>\n<li><strong>Accessible<\/strong>: composables and server code read via <code>useRuntimeConfig()<\/code><\/li>\n<li><strong>No home-grown config loaders<\/strong>: fewer failure modes, simpler mental model<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">README as a First-Class API Surface<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every module ships with a <strong>README<\/strong> that follows a standard template and is updated with the code:<\/p>\n\n\n\n<pre><code class=\"language-markdown\"># Module: my-module\n\n## Purpose\nOne-paragraph description of what this module does.\n\n## Configuration\n| Variable           | Default | Description      |\n|--------------------|---------|------------------|\n| MY_MODULE_ENABLED  | true    | Enable\/disable   |\n| MY_MODULE_API_URL  | ...     | API endpoint     |\n\n## Composables\n### useMyFeature()\nDescription, parameters, return value.\n\n## Server Handlers\n### \/api\/my-module\/status\nGET \u2014 returns module status.\n\n## Dependencies\n- Requires: graphql-server module\n- Optional: logging module\n\n## Architecture Decisions\nWhy this module exists and what alternatives were considered.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Code tells you <strong>how<\/strong>; the README tells you <strong>why<\/strong>.   Without it, every module becomes a reverse-engineering exercise.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Environment-Aware Modules and Debug Tooling<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Debug and development-only modules must never leak into production. The pattern:<\/p>\n\n\n\n<pre><code class=\"language-ts\">export default defineNuxtModule({\n  setup(options, nuxt) {\n    const env = nuxt.options.runtimeConfig.public.environment\n\n    if (env === 'production') {\n      \/\/ Do nothing in production\n      return\n    }\n\n    \/\/ Non-production only: register debug UI, plugins, etc.\n    addPlugin(resolve('.\/runtime\/plugins\/debug-panel.client'))\n  },\n})<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because the module returns early:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Its composables and components are never registered in production<\/li>\n<li>Tree-shaking can remove their code from the final bundles<\/li>\n<li>Debug tooling stays powerful in dev and invisible in prod<\/li>\n<\/ul>\n\n\n\n<pre class=\"mermaid\">flowchart TD\n  A[\"defineNuxtModule setup()\"] --&gt; B[Read runtimeConfig.public.environment]\n  B --&gt;|production| C[\"Return early&lt;br\/&gt;No debug plugins registered\"]\n  B --&gt;|non-production| D[Register debug-panel.client plugin]\n  D --&gt; E[Debug UI available only in dev\/test]<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Graceful Degradation: Critical vs Optional Dependencies<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Enterprise modules often depend on external systems (secret stores, CMS, analytics, identity providers). Not all failures are equal.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TD\n  A[\"Module startup&lt;br\/&gt;(secrets-vault)\"] --&gt; B[Connect to secrets vault]\n\n  B --&gt;|Success| C[Load secrets into runtimeConfig]\n\n  B --&gt;|Failure| D[Classify secrets]\n  D --&gt; E[Critical secrets?]\n  E --&gt;|Yes| F[\"Fail fast&lt;br\/&gt;throw clear error\"]\n  E --&gt;|No| G[\"Log warning&lt;br\/&gt;Use defaults\/fallbacks\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This decision lives in the module\u2019s <code>setup()<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Mark secrets or integrations as <strong>critical<\/strong> (app cannot function without them)<\/li>\n<li>Mark the rest as <strong>optional<\/strong> and provide sane fallbacks<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Event Bus: One Narrow Use Case<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A global event bus is tempting. Nuxt\u2019s <code>useEventBus<\/code> (built on EventEmitter3) provides a publish\/subscribe mechanism. It exists in this architecture for exactly one reason:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">> <strong>Module-to-application communication<\/strong> without coupling modules to UI components.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Modules do <strong>not<\/strong> communicate with each other via the event bus.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Example: Dialog System<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The UIKit module defines <code>useDialog()<\/code> which broadcasts dialog events. The application layer listens and renders the actual modal components.<\/p>\n\n\n\n<pre><code class=\"language-text\">Module (useDialog)                      Application (modal component)\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 sendEvent(         \u2502 \u2500\u2500\u2500\u2500 event \u2500\u2500\u2500\u25b6 \u2502 useEventBusListener( \u2502\n\u2502   'dialog:open',   \u2502                  \u2502   'dialog:open',    \u2502\n\u2502   { name: 'faq' }  \u2502                  \u2502   ({ name }) =&gt; {   \u2502\n\u2502 )                  \u2502                  \u2502     isOpen = true   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                  \u2502   }                 \u2502\n                                        \u2502 )                   \u2502\n                                        \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n  participant M as Module&lt;br\/&gt;useDialog()\n  participant Bus as Event bus&lt;br\/&gt;useEventBus\n  participant App as Modal component\n\n  M-&gt;&gt;Bus: emit 'dialog:open'&lt;br\/&gt;{ name: 'faq' }\n  Bus-&gt;&gt;App: 'dialog:open' payload\n  App-&gt;&gt;App: isOpen = true&lt;br\/&gt;render FAQ modal<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Any composable can trigger a dialog without importing the dialog component.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Example: Cookie Consent<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a third-party cookie consent manager records user consent, it emits an event. The application listens and persists consent state in cookies. The module doesn\u2019t need to know where or how persistence happens.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>That\u2019s it.<\/strong> These are the only uses across the entire application.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why Not More?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">An event bus quickly becomes an invisible web of side effects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Handlers are separated from senders<\/li>\n<li>\u201cFind all references\u201d won\u2019t reveal who emits an event<\/li>\n<li>Debugging requires mental simulation rather than static navigation<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Direct function calls remain:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Easier to trace<\/li>\n<li>Easier to refactor<\/li>\n<li>Visible in tooling<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The rule:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">> <strong>Use the event bus only when the sender must not know about the receiver.<\/strong>   > Accept that those flows will be harder to debug.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">SSR Event Replay<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">What about events emitted during SSR before client listeners are attached?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Events sent during SSR are stored in Nuxt\u2019s <code>useState<\/code> and automatically <strong>replayed<\/strong> on the client after hydration:<\/p>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n  participant S as Server\n  participant State as useState store\n  participant C as Client\n\n  S-&gt;&gt;S: Module loads during SSR\n  S-&gt;&gt;State: Store emitted events\n  State--&gt;&gt;C: Serialized in SSR HTML\n\n  C-&gt;&gt;C: Hydration\n  C-&gt;&gt;State: Read stored events\n  State--&gt;&gt;C: Replay events to listeners<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This closes the gap between server-side emission and client-side listeners, ensuring that no event is lost during the hydration window.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Command Pattern: Encapsulating Actions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Buttons, links, and form submissions often carry a surprising amount of logic:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Loading state<\/li>\n<li>Disabled conditions<\/li>\n<li>Dynamic labels<\/li>\n<li>Error handling<\/li>\n<li>The action itself<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Spread across templates, this becomes brittle and repetitive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>useCommand<\/code> composable wraps all of this into a <strong>single reactive object<\/strong>:<\/p>\n\n\n\n<pre><code class=\"language-ts\">const submitCommand = useCommand({\n  execute: async () =&gt; {\n    await submitForm(formData.value)\n    router.push('\/confirmation')\n  },\n  loading: () =&gt; isSubmitting.value,\n  disabled: () =&gt; !formValid.value || isSubmitting.value,\n  label: () =&gt; (isSubmitting.value ? 'Submitting...' : 'Submit Order'),\n})<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Template usage is simple:<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;Button\n  @click=\"submitCommand.execute\"\n  :loading=\"submitCommand.loading\"\n  :disabled=\"submitCommand.disabled\"\n&gt;\n  {{ submitCommand.label }}\n&lt;\/Button&gt;<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[\"Business logic&lt;br\/&gt;submitForm + navigation\"] --&gt; B[useCommand]\n  B --&gt; C[\"submitCommand object&lt;br\/&gt;{ execute, loading, disabled, label }\"]\n  C --&gt; D[\"UI components&lt;br\/&gt;(Button, toolbar, dialog)\"]<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Passable Commands<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The real power: <strong>commands are values<\/strong>, so they can be passed around.<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;!-- Parent defines behavior, child renders it --&gt;\n&lt;CheckoutStep :submit-command=\"submitCommand\" \/&gt;\n\n&lt;!-- Or: child exposes a command upward --&gt;\n&lt;FormStep v-model:command=\"stepCommand\" \/&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This achieves:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Decoupling behavior from presentation<\/strong>:<\/li>\n<li>The component that renders the button doesn\u2019t define what it does<\/li>\n<li>The component defining behavior doesn\u2019t care how it\u2019s rendered<\/li>\n<li><strong>Testability<\/strong>:<\/li>\n<li>Commands are plain objects; test them without mounting any components<\/li>\n<li><strong>Reusability<\/strong>:<\/li>\n<li>The same command can be used in different UI contexts (toolbar, footer, dialog, etc.)<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The UIKit Compose Pattern: Testable, Typed Styling<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">UIKit uses a <strong>compose pattern<\/strong> to separate:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Style computation<\/strong> (pure TypeScript) from<\/li>\n<li><strong>Template markup<\/strong> (Vue SFCs)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Each component is split into two files:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>File<\/th><th>Responsibility<\/th><\/tr><\/thead><tbody><tr><td><code>composeButton.ts<\/code><\/td><td>Interface + <code>classes(props)<\/code> style function<\/td><\/tr><tr><td><code>Button.vue<\/code><\/td><td>Thin template calling the compose function<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The compose file:<\/p>\n\n\n\n<pre><code class=\"language-ts\">\/\/ composeButton.ts\nexport interface ButtonProps {\n  variant: 'solid' | 'outline' | 'ghost'\n  size: 'sm' | 'md' | 'lg'\n  color: 'primary' | 'secondary' | 'danger'\n  fullWidth: boolean\n}\n\nexport function classes(props: ButtonProps): string {\n  \/\/ return something like:\n  \/\/ \"btn btn-solid btn-lg btn-primary w-full\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Vue component:<\/p>\n\n\n\n<pre><code class=\"language-vue\">&lt;!-- Button.vue --&gt;\n&lt;template&gt;\n  &lt;button :class=\"classes\"&gt;\n    &lt;slot \/&gt;\n  &lt;\/button&gt;\n&lt;\/template&gt;\n\n&lt;script setup lang=\"ts\"&gt;\nimport { composeButton } from '.\/composeButton'\n\nconst props = defineProps&lt;composeButton.ButtonProps&gt;()\nconst classes = computed(() =&gt; composeButton.classes(props))\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[\"Design system&lt;br\/&gt;Button variants\"] --&gt; B[ButtonProps interface]\n  B --&gt; C[\"classes(props): string&lt;br\/&gt;pure TS style function\"]\n  C --&gt; D[\"Button.vue&lt;br\/&gt;template + computed classes\"]\n  D --&gt; E[\"Rendered button&lt;br\/&gt;with correct classes\"]<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Why This Works at Scale<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Props interface is the API contract<\/strong>  <\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">   TypeScript enforces correct prop usage everywhere the component is used.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong><code>classes()<\/code> is unit-testable<\/strong>  <\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">   It\u2019s a pure function from props to class strings. No Vue mounting required.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Design\/dev shared vocabulary<\/strong>  <\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">   If the design system says <code>variant: 'outline'<\/code>, the interface says the same. No translation layer.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>AI-friendly<\/strong>  <\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">   An AI assistant can understand a component\u2019s full API from the compose file alone.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Compared to <code><style scoped><\/code>, style logic in TypeScript is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Composable<\/li>\n<li>Reusable<\/li>\n<li>Discoverable by tooling<\/li>\n<li>Easy to refactor without hunting through CSS selectors<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Scaffolding: Enforcing Consistency by Default<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With dozens of modules, consistency is enforced by a <strong>scaffolding generator<\/strong> (e.g., <code>plop<\/code>):<\/p>\n\n\n\n<pre><code class=\"language-bash\">$ yarn plop\n? What type of module? Feature Module\n? Module name: loyalty-program\n? Include server handlers? Yes\n? Include DevTools tab? No\n\nCreated:\n  \u2713 modules\/loyalty-program\/index.ts\n  \u2713 modules\/loyalty-program\/runtime\/composables\/useLoyaltyProgram.ts\n  \u2713 modules\/loyalty-program\/runtime\/server\/plugin.ts\n  \u2713 modules\/loyalty-program\/types.d.ts\n  \u2713 modules\/loyalty-program\/README.md<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart TD\n  A[\"Run scaffolder&lt;br\/&gt;yarn plop\"] --&gt; B[\"Answer prompts&lt;br\/&gt;type, name, options\"]\n  B --&gt; C[Generate module skeleton]\n  C --&gt; C1[index.ts]\n  C --&gt; C2[runtime\/composables\/useLoyaltyProgram.ts]\n  C --&gt; C3[runtime\/server\/plugin.ts]\n  C --&gt; C4[types.d.ts]\n  C --&gt; C5[README.md]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Every new module:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Starts with the correct directory layout<\/li>\n<li>Has the expected boilerplate<\/li>\n<li>Includes a README template<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Developers focus on logic; the scaffolding takes care of wiring and conventions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">SSR and the Global State Trap<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nuxt (and Nitro) render pages on the server, often handling many concurrent requests. In this context, <strong>module-scope variables are shared<\/strong> across all requests:<\/p>\n\n\n\n<pre><code class=\"language-ts\">\/\/ DANGEROUS: Shared across all users and requests\nlet requestCount = 0\n\nexport function useCounter() {\n  requestCount++   \/\/ increments for EVERY user\n  return requestCount\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In SSR, this becomes a cross-request singleton \u2014 a classic source of heisenbugs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead, use per-request state:<\/p>\n\n\n\n<pre><code class=\"language-ts\">\/\/ SAFE: Per-request, per-user\nexport function useCounter() {\n  const count = useState('counter', () =&gt; 0)\n  count.value++\n  return count\n}<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  subgraph Bad[Global mutable state]\n    G1[\"Module-scope variable&lt;br\/&gt;let requestCount = 0\"]\n    G2[All requests share same counter]\n  end\n\n  subgraph Good[Per-request state]\n    P1[\"useState('counter', () =&gt; 0)\"]\n    P2[Each request gets its own counter]\n  end\n\n  G1 --&gt; G2\n  P1 --&gt; P2<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">General rules:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Avoid global mutable state in module scope<\/li>\n<li>Use <code>useState<\/code> or <code>useRequestEvent()<\/code> for request-specific data<\/li>\n<li>Keep build-time setup (<code>index.ts<\/code>) free of per-request concerns<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Anti-Patterns (and Their Better Alternatives)<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Anti-Pattern<\/th><th>Problem<\/th><th>Solution<\/th><\/tr><\/thead><tbody><tr><td>Importing between feature modules<\/td><td>Tight coupling, hard to evolve boundaries<\/td><td>Use hooks or the app\u2019s event bus where appropriate<\/td><\/tr><tr><td>Build-time side effects in <code>index.ts<\/code><\/td><td>Order-dependent execution<\/td><td>Express intent via Nuxt\/Nitro hooks<\/td><\/tr><tr><td>Global state in module scope (SSR)<\/td><td>Shared across all requests<\/td><td>Use <code>useState<\/code> or per-request context<\/td><\/tr><tr><td>Blocking async work in <code>setup()<\/code><\/td><td>Slows or blocks dev server startup<\/td><td>Use <code>nuxt.hook('ready', async () => ...)<\/code><\/td><\/tr><tr><td>Hardcoded <code>process.env.NODE_ENV<\/code> checks<\/td><td>Scattered environment logic<\/td><td>Use <code>runtimeConfig.public.environment<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  A[Anti-pattern] --&gt; B[Operational \/ maintenance problem]\n  B --&gt; C[Recommended Nuxt pattern]\n\n  A1[Feature imports another feature] --&gt;|tight coupling| B\n  C1[Use hooks or event bus] --&gt; C\n\n  A2[Global mutable state in module scope] --&gt;|cross-request leaks| B\n  C2[useState \/ per-request context] --&gt; C\n\n  A3[\"Blocking async in setup()\"] --&gt;|slow startup| B\n  C3[\"nuxt.hook('ready', async ...)\"] --&gt; C<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Modules are boundaries, folders are suggestions<\/strong>   A folder named <code>checkout\/<\/code> doesn\u2019t prevent <code>payments\/<\/code> from importing its internals. A Nuxt module with auto-imported composables and components creates a real API surface: consumers use the public exports; everything else is private.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. The build-time vs runtime split is the key mental model<\/strong>   Many module bugs come from confusing <code>index.ts<\/code> (build time) with <code>runtime\/<\/code> code (request\/application time). Once that line is clear, modules behave predictably.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. The event bus is a last resort, not a pattern<\/strong>   Every event is an invisible dependency. Prefer direct function calls and imports. Use the event bus only when the sender must not know about the receiver (e.g., module-to-app notifications).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. Debug modules should be first-class citizens<\/strong>   Debug tooling is not \u201cthrowaway code\u201d. Encapsulating it into proper, environment-aware modules with docs makes it reliable and keeps production builds clean.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>5. The compose pattern beats scoped styles at scale<\/strong>   Style logic as TypeScript functions is testable, composable, and type-safe. <code><style scoped><\/code> hides logic from tooling, is hard to share, and encourages duplication.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>6. Consistent conventions supercharge onboarding<\/strong>   With a standard module structure, runtime config, and README template, learning one module means you can navigate all of them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>7. The README is the most important file in a module<\/strong>   It captures the rationale and contract of the module. Without it, even well-structured code becomes a puzzle.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Nuxt\u2019s module system is not just an add-on; it is the architecture. In a large enterprise Nuxt 4 stack, modules become the primary building blocks: they enforce boundaries, encapsulate behavior, and give you a clean mental model that scales beyond a handful of features and a couple of developers. This masterclass walks through how to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":220,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-13","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced-web-app-with-nuxt-and-net"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Architecting Enterprise Nuxt with Custom Modules - Software Production<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/softwareproduction.eu\/?p=13\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Architecting Enterprise Nuxt with Custom Modules - Software Production\" \/>\n<meta property=\"og:description\" content=\"Nuxt\u2019s module system is not just an add-on; it is the architecture. In a large enterprise Nuxt 4 stack, modules become the primary building blocks: they enforce boundaries, encapsulate behavior, and give you a clean mental model that scales beyond a handful of features and a couple of developers. This masterclass walks through how to [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/softwareproduction.eu\/?p=13\" \/>\n<meta property=\"og:site_name\" content=\"Software Production\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-06T21:48:26+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-07T01:21:46+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/06-architecting-enterprise-nuxt-modules.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1880\" \/>\n\t<meta property=\"og:image:height\" content=\"1253\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Munir Husseini\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Munir Husseini\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13\"},\"author\":{\"name\":\"Munir Husseini\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\"},\"headline\":\"Architecting Enterprise Nuxt with Custom Modules\",\"datePublished\":\"2026-06-06T21:48:26+00:00\",\"dateModified\":\"2026-06-07T01:21:46+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13\"},\"wordCount\":1774,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/06-architecting-enterprise-nuxt-modules.jpg\",\"articleSection\":[\"Advanced Web App with Nuxt and .NET\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13\",\"name\":\"Architecting Enterprise Nuxt with Custom Modules - Software Production\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/06-architecting-enterprise-nuxt-modules.jpg\",\"datePublished\":\"2026-06-06T21:48:26+00:00\",\"dateModified\":\"2026-06-07T01:21:46+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=13\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#primaryimage\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/06-architecting-enterprise-nuxt-modules.jpg\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/06-architecting-enterprise-nuxt-modules.jpg\",\"width\":1880,\"height\":1253},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=13#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/softwareproduction.eu\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Architecting Enterprise Nuxt with Custom Modules\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"name\":\"Softwareproduction\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/softwareproduction.eu\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\",\"name\":\"Munir Husseini\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"width\":32,\"height\":32,\"caption\":\"Munir Husseini\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\",\"name\":\"Munir Husseini\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"caption\":\"Munir Husseini\"},\"sameAs\":[\"https:\\\/\\\/softwareproduction.eu\\\/\"],\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Architecting Enterprise Nuxt with Custom Modules - Software Production","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/softwareproduction.eu\/?p=13","og_locale":"en_US","og_type":"article","og_title":"Architecting Enterprise Nuxt with Custom Modules - Software Production","og_description":"Nuxt\u2019s module system is not just an add-on; it is the architecture. In a large enterprise Nuxt 4 stack, modules become the primary building blocks: they enforce boundaries, encapsulate behavior, and give you a clean mental model that scales beyond a handful of features and a couple of developers. This masterclass walks through how to [&hellip;]","og_url":"https:\/\/softwareproduction.eu\/?p=13","og_site_name":"Software Production","article_published_time":"2026-06-06T21:48:26+00:00","article_modified_time":"2026-06-07T01:21:46+00:00","og_image":[{"width":1880,"height":1253,"url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/06-architecting-enterprise-nuxt-modules.jpg","type":"image\/jpeg"}],"author":"Munir Husseini","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Munir Husseini","Est. reading time":"17 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/softwareproduction.eu\/?p=13#article","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/?p=13"},"author":{"name":"Munir Husseini","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f"},"headline":"Architecting Enterprise Nuxt with Custom Modules","datePublished":"2026-06-06T21:48:26+00:00","dateModified":"2026-06-07T01:21:46+00:00","mainEntityOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=13"},"wordCount":1774,"commentCount":0,"publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=13#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/06-architecting-enterprise-nuxt-modules.jpg","articleSection":["Advanced Web App with Nuxt and .NET"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/softwareproduction.eu\/?p=13#respond"]}]},{"@type":"WebPage","@id":"https:\/\/softwareproduction.eu\/?p=13","url":"https:\/\/softwareproduction.eu\/?p=13","name":"Architecting Enterprise Nuxt with Custom Modules - Software Production","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/#website"},"primaryImageOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=13#primaryimage"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=13#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/06-architecting-enterprise-nuxt-modules.jpg","datePublished":"2026-06-06T21:48:26+00:00","dateModified":"2026-06-07T01:21:46+00:00","breadcrumb":{"@id":"https:\/\/softwareproduction.eu\/?p=13#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/softwareproduction.eu\/?p=13"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/?p=13#primaryimage","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/06-architecting-enterprise-nuxt-modules.jpg","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/06-architecting-enterprise-nuxt-modules.jpg","width":1880,"height":1253},{"@type":"BreadcrumbList","@id":"https:\/\/softwareproduction.eu\/?p=13#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/softwareproduction.eu\/"},{"@type":"ListItem","position":2,"name":"Architecting Enterprise Nuxt with Custom Modules"}]},{"@type":"WebSite","@id":"https:\/\/softwareproduction.eu\/#website","url":"https:\/\/softwareproduction.eu\/","name":"Softwareproduction","description":"","publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/softwareproduction.eu\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/softwareproduction.eu\/#organization","name":"Munir Husseini","url":"https:\/\/softwareproduction.eu\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","width":32,"height":32,"caption":"Munir Husseini"},"image":{"@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f","name":"Munir Husseini","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","caption":"Munir Husseini"},"sameAs":["https:\/\/softwareproduction.eu\/"],"url":"https:\/\/softwareproduction.eu\/?author=1"}]}},"jetpack_featured_media_url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/06-architecting-enterprise-nuxt-modules.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/13","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=13"}],"version-history":[{"count":9,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/13\/revisions"}],"predecessor-version":[{"id":221,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/13\/revisions\/221"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/media\/220"}],"wp:attachment":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=13"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=13"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=13"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}