Skip to content

Setup โ€‹

The mono ecosystem has two roles: a Host and one or more Remotes (see Getting Started). The Host owns the shared shell. A Remote adds its own pages and plugs into it.

There's one twist: a Host can be Vue or Nuxt, but a Remote is always Vue. One Vue Remote works with either kind of Host. Each app says which kind it is with the type field in mono.config.ts. That field decides where its code lives โ€” src/ for Vue, app/ for Nuxt (see App type).

This page is the map: who connects to who, and the one file where each app's setup lives. For the full rules, follow the links into Config and Template Rules.

The matrix โ€‹

AppKindSourceSetup fileWorks with
HostVuesrc/vite.config.tsa Vue Remote
HostNuxtapp/nuxt.config.tsa Vue Remote
RemoteVuesrc/vite.config.tsa Vue or Nuxt Host

In short: only the Host changes between Vue and Nuxt. The Remote is always a Vue + Vite app โ€” only a small part of its vite.config.ts changes to reach a Nuxt host's app/ folders. The rest of this page covers each row.

Host โ€‹

The Host owns the shell โ€” Login/Logout, layouts, Middleware, and the theme โ€” and shares it with its Remotes. Where you set that up depends on the kind.

Vue โ€‹

A Vue Host is set up in vite.config.ts, and the whole mono wiring is one call: monoRepo() from mono-utils/vite โ€” the Vite twin of the mono-utils/nuxt module.

ts
// vite.config.ts (Vue host) โ€” the mono core
import { monoRepo } from 'mono-utils/vite'

export default defineConfig(async () => {
  const mono = await monoRepo()

  return {
    envPrefix: ['VITE_', 'MONO_'],
    plugins: [
      VueRouter({ routesFolder: [{ src: 'src/pages' }, ...mono.ecosystem('pages')] }),
      // vue (isCustomElement), UnoCSS,
      AutoImport({ dirs: ['src/composables', 'src/stores', ...mono.ecosystem(['composables', 'stores'])] }),
      Components({ dirs: ['./src/components', ...mono.ecosystem('components')] }),
      // a Host ships its own shell โ†’ 'src/layouts'. A Remote consumes the Host's
      // layouts instead โ†’ mono.ecosystem('layouts')
      Layouts({ layoutsDirs: 'src/layouts', defaultLayout: 'default' }),
      mono.plugin, // โ† must be LAST
    ],
  }
})

monoRepo() loads mono.config.ts once (c12/jiti, already taught the alias map) and resolves the extends-active apps, then hands you two things:

  • mono.ecosystem(subs) โ€” the remote dirs to feed each ecosystem plugin. It's type-aware: a nuxt remote resolves to app/<sub>, a vue remote to src/<sub>, so no path is hardcoded. This replaces the old hand-written monoEcosystem({ dirname, apps: activeApps, subs }) at every call site. A remote can also decline some of these dirs from its own mono.config.ts โ€” see Config โ†’ Taking only part of a layer; that allowlist is applied in here, so it wins over whatever a call site asks for.
  • mono.plugin โ€” register it last. It wires resolve.alias, __MONO_CONFIG_EXPOSE__, server.fs.allow (so Vite can read the synced Remote in .mono/apps/) and dependency dedup, all from that same single config load. You no longer write any of those blocks by hand.

Replaces monoAlias + getMonoConfig

Older hosts opened with a monoAlias() / getMonoConfig() / resolveExtendsAppNames block and repeated monoEcosystem({ โ€ฆ }) at four call sites. await monoRepo() collapses all of it. Those exports still work, but monoRepo is the current surface โ€” see Template Changelog โ†’ Simplify Config.

Last, register the config in src/main.ts:

ts
// src/main.ts
import { createMono } from 'mono-utils/runtime'
import monoConfig from '../mono.config'

app.use(createMono(monoConfig))

mono.config.ts sets type: 'vue'. For what each plugin does, see Template Rule V1 (the Vite core) and Rule 2 (the aliases).

Nuxt โ€‹

A Nuxt Host has no vite.config.ts. The same setup lives in nuxt.config.ts, driven by two modules and a mono key:

ts
// nuxt.config.ts (Nuxt host)
export default defineNuxtConfig({
  ssr: true,
  mono: { utils: {}, helper: {} },
  modules: ['mono-utils/nuxt', 'mono-helper/nuxt', /* โ€ฆunocss, pinia, vueuseโ€ฆ */],
  // nested shared/ dirs aren't scanned by default โ€” opt them in
  imports: { dirs: ['composables', 'composables/**', 'stores', 'stores/**'] },
  vite: { envPrefix: ['VITE_', 'MONO_'] },
})

mono-utils/nuxt adds the aliases, server.fs.allow, the __MONO_CONFIG_EXPOSE__ define, and the ecosystem merge โ€” everything a Vue host wires by hand. mono-helper/nuxt adds the base CSS, the mono-* isCustomElement rule, and SSR (monoSsr) so <mono-*> web components only render on the client. Since Nuxt also runs on the server, register createMono from a plugin and set the request resolver so cookies are read on both sides:

ts
// app/plugins/mono.ts
import { createMono, setMonoEventResolver } from 'mono-utils/runtime'
import monoConfig from '@mono-host-root/mono.config'

export default defineNuxtPlugin((nuxtApp) => {
  if (import.meta.server) setMonoEventResolver(() => useRequestEvent())
  nuxtApp.vueApp.use(createMono(monoConfig))
})

A Nuxt Host keeps its code under app/ (not src/) and sets type: 'nuxt'. For the mono.config.ts fields, see Config; the host notes are in the Template Host section.

Remote โ€‹

A Remote is always a Vue + Vite app. Its own pages, components, and stores live under src/, and it's set up in vite.config.ts. It doesn't rebuild the shell โ€” it uses the Host's. The only thing that changes is a small bit of config so it can reach the Host's folders, and that depends on whether the Host is Vue or Nuxt.

Vue host โ†’ Vue remote โ€‹

When the Host is Vue, its code is under src/. The Remote pulls the Host's shared folders with mono.ecosystem(...) (same monoRepo() handle as above โ€” no mono.nuxt() needed), and sets the host as apps[].type: 'vue' in mono.config.ts. The matching TypeScript alias (@mono-host/* โ†’ the host's src/*) is generated into .mono/tsconfig.json by mono prepare โ€” you don't hand-write it.

Nuxt host โ†’ Vue remote โ€‹

When the Host is Nuxt, its code is under app/ and its pages use Nuxt's definePageMeta. The Remote uses the same monoRepo() handle, plus its mono.nuxt() compat helpers:

ts
// vite.config.ts (Vue remote, Nuxt host)
import { monoRepo } from 'mono-utils/vite'

const mono = await monoRepo()

plugins: [
  // before VueRouter: strips definePageMeta, fixes layout <slot/>, defines import.meta.server/client
  ...mono.nuxt().hostResolver(),
  VueRouter({
    routesFolder: [
      { src: 'src/pages' },
      // type-driven: finds the host's app/pages, no hardcoded path
      ...mono.ecosystem('pages'),
    ],
    // adds layout/title meta for the host's definePageMeta pages
    extendRoute: mono.nuxt().extendRoute(),
  }),
  // โ€ฆ
  mono.plugin, // โ† last
]

mono.ecosystem(subs) finds the host's app/ folders for pages/components/composables/stores/layouts on its own. The two mono.nuxt() helpers rewrite what a Vue/Vite app can't run natively:

  • hostResolver() โ€” strips the definePageMeta({โ€ฆ}) macro from synced .vue files, rewrites a remote layout's default <slot/> to <router-view/>, resolves Nuxt's useState() to the mono-utils/runtime shim, rewrites <NuxtLink> to <RouterLink>, and defines import.meta.server / import.meta.client. Spread it before VueRouter().
  • extendRoute() โ€” injects { layout, title } parsed from a synced page's source so setupLayouts wraps it. Host pages using definePage are read natively and skipped.

Only when the other app is Nuxt

A Vue host federation needs none of this โ€” its pages use definePage and its layouts already render <router-view/>, so omit mono.nuxt() entirely. (The standalone monoNuxtHost / monoExtendRoute exports remain for back-compat; mono.nuxt() is the current surface.)

Set the host as apps[].type: 'nuxt' in mono.config.ts; mono prepare then points @mono-host/* at the host's app/* (not src/*) in the generated .mono/tsconfig.json automatically. Full details: Template Remote โ†’ "When the host is a Nuxt app".

mono prepare โ€‹

monoRepo() (via mono.plugin) builds the import aliases for Vite at runtime, but TypeScript and your editor can't run it โ€” they only read tsconfig.json. mono prepare bridges that gap: it derives the exact same @mono-* aliases (own name from mono.config.ts, remotes by scanning .mono/apps/) and writes them as compilerOptions.paths into a generated .mono/tsconfig.json, then points your root tsconfig.json at it:

jsonc
// tsconfig.json (root) โ€” prepare wires this for you
{
  "extends": ["./.mono/tsconfig.json"], // generated aliases (kept LAST)
  "compilerOptions": { /* your own options; no @mono-* paths here anymore */ }
}
jsonc
// .mono/tsconfig.json (generated โ€” do not edit)
{
  "compilerOptions": {
    "paths": {
      "@mono-apps/*": ["./apps/*"],
      "@mono-host/*": ["./apps/mono-host/src/*"], // or app/* when the host is Nuxt
      "@mono-host-root/*": ["./apps/mono-host/*"]
    }
  }
}

Because both sides now come from the same source, the TS aliases can never drift from the Vite ones โ€” there's nothing to hand-write. prepare strips any inline @mono-* paths from the root and is idempotent, so it's safe to run any time. The templates run it on postinstall and on dev, and after a sync:

json
{
  "scripts": {
    "postinstall": "mono sync && mono prepare",
    "mono:prepare": "mono prepare",
    "dev": "mono prepare && mono env -e .env.dev -- vite",
    "mono:sync": "mono sync && mono prepare"
  }
}

Run it yourself any time with mono prepare (e.g. after adding or renaming a remote in mono.config.ts).

postinstall syncs first โ€” on purpose

postinstall leads with mono sync so a plain pnpm i can recover from an empty or stale .mono/apps/. (A Nuxt host chains one more step: "mono sync && mono prepare && nuxt prepare".)

It matters because pnpm auto-runs install before any script, so a broken .mono/ makes every pnpm command fail โ€” including pnpm mono:sync, the one command that would fix it. Syncing first breaks that deadlock. If you're already stuck, bypass pnpm once:

bash
node node_modules/mono-utils/dist/mono.mjs sync   # reads .env itself via dotenv

Next steps โ€‹

  • Config โ€” mono.config.ts: the type field, cookies, JWT, and the deploy menu.
  • Sync โ€” how the other app is pulled into .mono/apps/.
  • Environment โ€” secrets and the shared MONO_ prefix.
  • Template Rules โ€” the full rules the AI follows in a Host or Remote.