Skip to content

Getting Started โ€‹

Mono-repo is a way to build a large-scale, modular front-end on top of the Vue ecosystem. The goal is a maintainable, clean, complex project that takes minimum effort to grow.

Concept โ€‹

Vue gives every project the same predictable shape:

  • src/pages โ€” routing
  • src/layouts โ€” layouts

Inside a layout you usually find the same generic pieces: Sidebar, Navbar, Login, Logout, Middleware. Every normal website needs them.

Mono-repo makes this simple. Those generic pieces live once in the Host, and every Remote can use them without rebuilding.

A Remote only adds its own modules. It does not re-create Login, Logout, or any of the generic things โ€” the Host already handles all of that.

Because of this modular concept, the Host can have many Remotes, and all of them live inside the Host. Think about a house that have many rooms.

A Host can be a Vue app or a Nuxt app; a Remote is always a Vue app and works with either. See Setup for how each kind is wired.

Visual Example โ€‹

One Host owns the shell; each finance module plugs in as its own Remote:

Host โ€” owns the shell of the app.

host/
โ””โ”€โ”€ src/
    โ”œโ”€โ”€ pages/         # login, logout, etc.
    โ”œโ”€โ”€ layouts/       # sidebar, navbar, etc.
    โ””โ”€โ”€ ...etc/

A Nuxt Host uses app/ instead of src/ (same folders inside). See Setup.

Remote โ€” just adds modules, for example a Budget module.

remote/
โ””โ”€โ”€ src/
    โ”œโ”€โ”€ pages/         # budget module
    โ””โ”€โ”€ ...etc/

Install โ€‹

Both the Host and every Remote are standalone pnpm apps. They install the EJI libraries from a private git repo and pin every dependency through a pnpm workspace catalog โ€” a single pnpm-workspace.yaml that both apps share so their versions never drift. Your package.json then just points at the catalog with catalog:<group>.

Prerequisites โ€‹

  • pnpm (the catalog feature below needs a recent pnpm).
  • A GitHub classic token โ€” the libraries live in the private EJI-ICT/libs repo, so the git URLs are token-authenticated. The snippets below use <GITHUB_CLASSIC_TOKEN> as a placeholder; replace it with your token.

pnpm mono:i is not ready yet

The mono-* templates already install their libraries through mono-cli, so their package.json says workspace:* where this page says catalog:internal. That is deliberate โ€” leave the templates as they are, and do not move your own project onto mono:i until it is announced as stable โ€” Mono CLI explains what it does and why it is not ready.

1. pnpm-workspace.yaml โ€‹

This is the single source of truth for versions. The internal catalog pulls the three EJI libraries from git; the other groups pin the dependencies those libraries expect (their peers).

A Vue host & remote share the Vite-based catalog. A Nuxt host swaps the Vite plugins for Nuxt and its modules (a nuxt catalog group) โ€” pick the tab that matches your app.

yaml
shellEmulator: true
trustPolicy: no-downgrade
packages: []

catalogs:
  # Internal EJI-ICT libraries (private git repo, token-authenticated)
  internal:
    mono-devextreme: git+https://<GITHUB_CLASSIC_TOKEN>@github.com/EJI-ICT/libs#path:/packages/mono-devextreme
    mono-helper: git+https://<GITHUB_CLASSIC_TOKEN>@github.com/EJI-ICT/libs#path:/packages/mono-helper
    mono-utils: git+https://<GITHUB_CLASSIC_TOKEN>@github.com/EJI-ICT/libs#path:/packages/mono-utils

  # Build pipeline: Vite, bundler plugins and codegen tooling
  build:
    '@odata2ts/http-client-api': ^0.6.3
    '@odata2ts/odata-query-objects': ^0.28.1
    '@odata2ts/odata-service': ^0.23.1
    '@odata2ts/odata2ts': ^0.40.1
    '@vitejs/plugin-vue': ^6.0.7
    unocss: ^66.7.0
    unplugin-auto-import: ^21.0.0
    unplugin-vue-components: ^32.1.0
    vite: 8.0.16
    vite-plugin-mkcert: ^2.1.0
    vite-plugin-vue-devtools: ^8.1.2
    vite-plugin-vue-layouts-next: ^2.1.0

  # Dev tooling: type-checking, env helpers, upgrade/maintenance utilities
  dev:
    '@iconify/json': ^2.2.483
    '@tsconfig/node22': ^22.0.5
    '@vue/tsconfig': ^0.9.1
    dotenv: ^17.4.2
    dotenv-cli: ^11.0.0
    taze: ^19.14.1
    typescript: ~6.0.3
    vue-tsc: ^3.3.3

  # Frontend runtime: shipped to the browser bundle
  frontend:
    '@unhead/vue': 3.1.3
    '@vueuse/core': 14.3.0
    '@vueuse/integrations': 14.3.0
    jwt-decode: ^4.0.0
    notivue: ^2.4.5
    pinia: 3.0.4
    universal-cookie: ^8.1.2
    vue: 3.5.24
    vue-router: 5.0.4
    yup: 1.7.1

  # Type definitions
  types:
    '@types/node': ^25.9.2

onlyBuiltDependencies:
  - esbuild
yaml
shellEmulator: true
trustPolicy: no-downgrade
packages: []

catalogs:
  # Internal EJI-ICT libraries (private git repo, token-authenticated)
  internal:
    mono-devextreme: git+https://<GITHUB_CLASSIC_TOKEN>@github.com/EJI-ICT/libs#path:/packages/mono-devextreme
    mono-helper: git+https://<GITHUB_CLASSIC_TOKEN>@github.com/EJI-ICT/libs#path:/packages/mono-helper
    mono-utils: git+https://<GITHUB_CLASSIC_TOKEN>@github.com/EJI-ICT/libs#path:/packages/mono-utils

  # Nuxt framework + Nuxt modules (replaces the Vue Vite plugins)
  nuxt:
    nuxt: ^4.5.0
    '@nuxt/kit': ^4.5.0       # build-time module API; pinned to Nuxt's version
    '@pinia/nuxt': ^1.0.1
    '@unocss/nuxt': ^66.7.5
    '@vueuse/nuxt': 14.3.0

  # Build pipeline: OData codegen (Nuxt bundles its own Vite)
  build:
    '@odata2ts/http-client-api': ^0.6.5
    '@odata2ts/odata-query-objects': ^0.28.2
    '@odata2ts/odata-service': ^0.23.2
    '@odata2ts/odata2ts': ^0.40.2
    unocss: ^66.7.5

  # Dev tooling: type-checking and env helpers
  dev:
    '@iconify/json': ^2.2.501
    dotenv: ^17.4.2
    dotenv-cli: ^11.0.0
    taze: ^19.14.1
    # Stay on the classic (JS) TypeScript โ€” 7.0.x is the native Go port, which
    # drops the compiler API `@vue/compiler-sfc` and `vue-tsc` still need.
    typescript: ~6.0.3
    vue-tsc: ^3.3.7

  # Frontend runtime: shipped to the browser bundle
  frontend:
    '@sentry/nuxt': ^10.66.0
    '@unhead/vue': 3.1.3
    '@vueuse/core': 14.3.0
    '@vueuse/integrations': 14.3.0
    jwt-decode: ^4.0.0
    notivue: ^2.4.5
    pinia: 3.0.4
    universal-cookie: ^8.1.2
    vue: 3.5.24
    vue-router: ^5.2.0
    yup: 1.7.1

  # Lit SSR: renders mono-helper's shadow components server-side.
  # Both are EXACT pins, not ranges โ€” see the note under the table below.
  lit:
    nuxt-ssr-lit: 1.6.33
    '@lit-labs/ssr': 3.2.2

  # Type definitions
  types:
    '@types/node': ^26.1.1

onlyBuiltDependencies:
  - esbuild

allowBuilds:
  '@sentry/cli': false
  core-js: false
  esbuild: false
  inferno: false

A Vue Host adds a few extras for error reporting (@sentry/vue, @sentry/integrations, @sentry/vite-plugin); the Nuxt host tab already pins its equivalent (@sentry/nuxt). A Remote doesn't need them. The mono Nuxt modules ship inside the libraries (mono-utils/nuxt, mono-helper/nuxt), so there's nothing extra to install for them โ€” see Setup.

2. package.json โ€‹

Reference the catalog with catalog:<group> โ€” pnpm resolves the version from pnpm-workspace.yaml, so you never repeat version numbers per app. The Vue tab covers both a Vue host and any Remote; the Nuxt host tab swaps the Vite plugins for nuxt + its modules.

json
{
  "dependencies": {
    "mono-devextreme": "catalog:internal",
    "@unhead/vue": "catalog:frontend",
    "@vueuse/core": "catalog:frontend",
    "@vueuse/integrations": "catalog:frontend",
    "dotenv": "catalog:dev",
    "jwt-decode": "catalog:frontend",
    "notivue": "catalog:frontend",
    "pinia": "catalog:frontend",
    "universal-cookie": "catalog:frontend",
    "vue": "catalog:frontend",
    "vue-router": "catalog:frontend",
    "yup": "catalog:frontend"
  },
  "devDependencies": {
    "mono-helper": "catalog:internal",
    "mono-utils": "catalog:internal",
    "@iconify/json": "catalog:dev",
    "@odata2ts/http-client-api": "catalog:build",
    "@odata2ts/odata-query-objects": "catalog:build",
    "@odata2ts/odata-service": "catalog:build",
    "@odata2ts/odata2ts": "catalog:build",
    "@tsconfig/node22": "catalog:dev",
    "@types/node": "catalog:types",
    "@vitejs/plugin-vue": "catalog:build",
    "@vue/tsconfig": "catalog:dev",
    "dotenv-cli": "catalog:dev",
    "taze": "catalog:dev",
    "typescript": "catalog:dev",
    "unocss": "catalog:build",
    "unplugin-auto-import": "catalog:build",
    "unplugin-vue-components": "catalog:build",
    "vite": "catalog:build",
    "vite-plugin-mkcert": "catalog:build",
    "vite-plugin-vue-devtools": "catalog:build",
    "vite-plugin-vue-layouts-next": "catalog:build",
    "vue-tsc": "catalog:dev"
  }
}
json
{
  "dependencies": {
    "mono-devextreme": "catalog:internal",
    "@sentry/nuxt": "catalog:frontend",
    "@unhead/vue": "catalog:frontend",
    "@vueuse/core": "catalog:frontend",
    "@vueuse/integrations": "catalog:frontend",
    "dotenv": "catalog:dev",
    "jwt-decode": "catalog:frontend",
    "notivue": "catalog:frontend",
    "nuxt": "catalog:nuxt",
    "pinia": "catalog:frontend",
    "universal-cookie": "catalog:frontend",
    "vue": "catalog:frontend",
    "vue-router": "catalog:frontend",
    "yup": "catalog:frontend"
  },
  "devDependencies": {
    "mono-helper": "catalog:internal",
    "mono-utils": "catalog:internal",
    "@iconify/json": "catalog:dev",
    "@lit-labs/ssr": "catalog:lit",
    "@odata2ts/http-client-api": "catalog:build",
    "@odata2ts/odata-query-objects": "catalog:build",
    "@odata2ts/odata-service": "catalog:build",
    "@odata2ts/odata2ts": "catalog:build",
    "@pinia/nuxt": "catalog:nuxt",
    "@types/node": "catalog:types",
    "@unocss/nuxt": "catalog:nuxt",
    "@vueuse/nuxt": "catalog:nuxt",
    "dotenv-cli": "catalog:dev",
    "nuxt-ssr-lit": "catalog:lit",
    "taze": "catalog:dev",
    "typescript": "catalog:dev",
    "unocss": "catalog:build",
    "vue-tsc": "catalog:dev"
  }
}

The Nuxt host drops the Vite-only plugins (@vitejs/plugin-vue, unplugin-*, vite-plugin-*) โ€” its mono-utils/nuxt + mono-helper/nuxt modules replace them โ€” and adds nuxt plus @pinia/nuxt / @unocss/nuxt / @vueuse/nuxt. Its lit catalog carries the pair that renders mono-helper's shadow components server-side โ€” see Setup.

Keep the lit pins exact. nuxt-ssr-lit@1.6.33 needs @lit-labs/ssr v3: v4's renderShadow() returns a thunk and drops the <!--lit-part--> markers, so every shadow component fails hydration with "no root part". A caret that picked up a nuxt-ssr-lit expecting v4 would break SSR silently, which is why both sit in one catalog and move together.

3. Why these deps โ€” peer dependencies โ€‹

The EJI libraries don't bundle their runtime dependencies; they declare them as peerDependencies. That means your app installs them once โ€” so there's a single copy of vue, vue-router, etc., and you control the versions (via the catalog above). The list above already covers them; here's the full picture so you know what each library expects:

LibraryDeclares as peers (you must provide)
mono-helper(none) โ€” it bundles its own lit + flatpickr, so just install it.
mono-utilsvue, vue-router, jwt-decode, @unhead/vue, dotenv, dotenv-cli, mono-devextreme, @odata2ts/http-client-fetch, notivue, yup

mono-utils is self-contained โ€” import everything from mono-utils/runtime (useMonoUtility, MonoNotivue, useUtils, MonoOdataMapTypes, and every validation / OData type) or mono-utils/fetching. notivue and yup are optional peers (only if you use notifications / validation โ€” see the Notivue and Yup addons); @odata2ts/http-client-fetch is only needed when you fetch OData. The OData codegen peers (@odata2ts/*, etc.) live in your app's dev deps for type generation โ€” see OData Types.

4. Install โ€‹

Install everything declared in package.json:

sh
pnpm i

Finally, sync the federated apps and generate the aliases:

sh
pnpm mono:sync      # mono sync && mono prepare

Next, head to Setup to wire your Host and Remote. For the UI components and theming, continue with Mono-UI โ†’ Getting started and Theme.