Layered architecture
Most large frontend codebases settle on some kind of layering: app → pages → widgets → features → entities → shared (FSD), or domain → application → infrastructure (clean architecture), or some custom variant. The promise is the same: you can change a feature without touching the layer below it.
Archora checks that promise.
Declaring layers
In archora.config:
layers: [
{ name: 'app', patterns: ['src/app/**'] },
{ name: 'pages', patterns: ['src/pages/**'] },
{ name: 'widgets', patterns: ['src/widgets/**'] },
{ name: 'features', patterns: ['src/features/**'] },
{ name: 'entities', patterns: ['src/entities/**'] },
{ name: 'shared', patterns: ['src/shared/**'] },
]The order matters. Earlier layers may import later ones, never the other way around. That's the entire rule. No additional configuration, no per-pair allowlists.
If a module's path matches multiple patterns, the first match wins. Modules that match no pattern are unlayered — they don't participate in layer checks.
Layer violation
A layer violation is an import from a later layer to an earlier one. Example: src/shared/lib/foo.ts importing from src/features/bar/index.ts is a violation, because shared should not know about features.
Each violation is a single edge: from, to, fromLayer, toLayer. They show up:
- In the Insights panel of the desktop app.
- As a
<testcase failure>in JUnit reports. - In
--fail-on layer-violations:Nfor CI gates.
Layer-aware recommendations
Beyond raw violation counting, Archora adds two layer-aware insights:
misplaced-by-layer— a module's path puts it in layer X, but ≥ 70% of its dependents live in a single different layer Y. Re-homing it removes a lot of cross-layer noise.top-violator— a single module is the source of a disproportionate number of layer violations. Often the cheapest single-file fix in the entire scan.
Without layer config
Without layers in your config, layer checking is silently disabled — no violations, no layer recommendations. This is intentional: layered architecture is opinionated, and we don't want to invent rules you didn't ask for.
Limitations
- Layer membership is path-only. A
*.vuefile insrc/shared/ui/is treated assharedregardless of whether its<script>imports something fromfeatures. We're never going to infer "this should really be a widget" from code shape alone. - The "may import" rule is one-directional. If you want bidirectional rules (e.g. "entities and shared may not import from each other") they'd need to live in two same-rank layers — currently not supported. Open an issue if you need this.
See also
- Recommendations — full catalog including layer-aware ones.