Here is a Vue component. Spot the architecture bug.
<!-- lyears/admin/app/components/AdminPanel.vue -->
<script setup lang="ts">
const cart = useCart();
</script>
<template>
<CartSummary />
</template>
Looks fine, right? No weird imports. Nothing red in the editor. ESLint is happy. The PR gets approved.
The problem: useCart and <CartSummary> live in the shop layer. The admin layer was never supposed to touch shop. You just tied two parts of your app together, and nobody noticed. Not you, not the reviewer, not a single tool in the pipeline.
Do that thirty more times and your nice modular Nuxt app is one big ball of yarn with a layers/ folder on top.
Why nothing catches it
Nuxt layers are a great way to split a big app: a shared layer for the design system, an auth layer, a shop layer, an admin layer. You get a modular monolith for free.
But the modular part only holds if each layer stays in its lane. And the tools that guard boundaries (ESLint import rules, dependency-cruiser and friends) all read import statements.
In Nuxt, most dependencies have no import statement. Composables, utils, components and Nitro server utils are auto-imported. The boundary tool looks at the file, sees zero imports, and gives it a gold star.
I hit this on a real project. After a few months, "admin may only use shared and auth" was a rule that lived in a README and nowhere else.
So I built layerscope
layerscope resolves auto-imports the same way Nuxt does, and fails CI when a layer uses something it is not allowed to.
You write the rules once:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-layerscope'],
layerscope: {
layers: {
shared: { allow: [] },
auth: { allow: ['shared'] },
shop: { allow: ['shared', 'auth'] },
admin: { allow: ['shared', 'auth'] },
},
},
});
Then run:
npx layerscope check --prepare
And the component from the top of this post finally gets caught:
layers/admin/app/components/AdminPanel.vue
2:14 error Auto-import "useCart" crosses from layer "admin" into "shop" layer-boundary
useCart → layers/shop/app/composables/useCart.ts
allowed for "admin": shared, auth
8:5 error Component <CartSummary> crosses from layer "admin" into "shop" layer-boundary
CartSummary → layers/shop/app/components/CartSummary.vue
allowed for "admin": shared, auth
It tells you what crossed, where it came from, and what the layer is allowed to use. No guessing.
How it knows
layerscope does not guess from file names. A small Nuxt module records what Nuxt actually registered: every component, every auto-import, every server util, per layer. So layer priority, overridden components, pathPrefix, custom component dirs and module auto-imports like @vueuse/nuxt all come out the way Nuxt sees them.
Then it parses your scripts with oxc and your templates with the real Vue compiler, tracks scope (a local const useCart shadows the auto-import, same as at runtime), and checks every edge against your rules.
If it can't resolve something, it says so. It never quietly calls an unknown reference "safe".
On a test project with 3,000 source files in 10 layers, a full check takes about 770 ms.
"But my project already has 200 violations"
Same. That is why there is a baseline.
npx layerscope init --baseline
init looks at your layers, writes a config that allows exactly what exists today, and accepts the current findings. Your first CI run is green. From now on, only new violations fail the build. Delete a line from the config when you want to fix that dependency for real.
The GitHub Action even adds a summary like adds 3, fixes 5 and Baseline: 47 → 42, so you can watch the mess shrink one PR at a time.
The fun extras
-
layerscope why useCartshows every place a symbol is used, and which uses break the rules. Great before you move a composable. -
layerscope graphdraws the dependency graph between your layers. -
layerscope unusedfinds things nothing depends on. - A Nuxt DevTools tab with findings, the layer graph and symbol traces. It updates when you save. Try the live demo.
- Cycle detection, presets, an ESLint plugin, SARIF output for the GitHub Security tab, and an MCP server so your coding assistant can ask "may I use this here?" before it writes the code.
Works with Nuxt 3 and 4, and with local, npm and git layers.
Try it in five minutes
npx nuxi module add nuxt-layerscope --dev
npx layerscope init --baseline
npx layerscope check --prepare
Or poke at the example project on StackBlitz first. It has four layers and one violation for each rule.
- Docs: https://layerscope.kitdev.space/
- GitHub: https://github.com/hamedniroomand/nuxt-layerscope
- npm: https://www.npmjs.com/package/nuxt-layerscope
It is MIT and still young (0.x), so bug reports and weird edge cases are very welcome. If your layers have a secret you want to find out, run it and tell me how many violations you got. I promise not to judge. Much.
Top comments (0)