Alkademi Logo
Architecture

Architecture: Nuxt Layers

Alkademi LMS Open Source is built as a modular monolith. Every feature still runs inside one Nuxt application, but each domain feature is isolated as a Nuxt Layer so it can be maintained, replaced, or extended more easily.

Architecture: Nuxt Layers

Alkademi LMS Open Source is built as a modular monolith. Every feature still runs inside one Nuxt application, but each domain feature is isolated as a Nuxt Layer so it can be maintained, replaced, or extended more easily.

Layer Composition

The main layer composition lives in the root nuxt.config.ts:

import { resolve } from 'path'
import { getLayerDirs } from './shared/utils/layers'

const rootDir = resolve(__dirname)

export default defineNuxtConfig({
  extends: [
    './web',
    ...getLayerDirs(rootDir, 'plugins'),
    ...getLayerDirs(rootDir, 'cores')
  ],
  modules: ['@pinia/nuxt', '@nuxt/ui']
})

The actual project order is:

  1. ./web for the landing/public site.
  2. Active plugins from plugins/.
  3. Active cores from cores/.

Layers are not listed manually one by one. getLayerDirs(rootDir, baseDir) scans the plugins/ and cores/ folders, then only includes folders with nuxt.config.ts and an enabled config.

Core Auto-Discovery

A core is active when:

  • it is located at cores/{core-id};
  • it has nuxt.config.ts;
  • it has core.config.ts;
  • core.config.ts contains enabled: true;
  • the core is not replaced by an active plugin through replaces.

If a core does not have core.config.ts, does not have an enabled flag, or has enabled: false, the layer is skipped.

Plugin Auto-Discovery

A plugin is active when:

  • it is located at plugins/{plugin-id};
  • it has nuxt.config.ts;
  • it has plugin.config.ts;
  • plugin.config.ts contains enabled: true.

Active plugins are also read to determine replaced cores. If a plugin has replaces: ['program'], the program core is not included in the layer composition.

Core Replacement

The current replacement mechanism is based on the replaces field, not overrideMode or targetCore.

export default {
  enabled: true,
  replaces: ['program'],
  routes: [
    {
      key: 'custom-class',
      path: '/dashboard/custom-class',
      icon: 'i-heroicons-sparkles',
      label: 'Custom Class'
    }
  ]
}

When this plugin is active, discovery skips the program core. This lets a plugin take over a core feature without changing source files under cores/program.

Runtime Registry

In addition to build-time discovery in shared/utils/layers.ts, the app has a runtime registry in app/plugins/plugin-registry.ts.

The registry:

  • reads all plugins/*/plugin.config.ts files with import.meta.glob;
  • validates enabled, replaces, and routes;
  • only registers enabled plugins;
  • exposes usePluginRegistry() for other composables such as the sidebar.

The sidebar is not hardcoded in the component. app/composables/useSidebarMenu.ts builds menu items from:

  • core.config.ts for core menus;
  • plugin.config.ts for plugin menus;
  • optional sidebar.ts files from cores/plugins for dynamic menus;
  • the list of cores replaced by active plugins.

This keeps routes, plugin registry, and dashboard navigation aligned with feature configuration.


Learn more about the Plugin System to see how customization is done.

Copyright © 2026