@jeffrey2423/coding-standards 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -174
  3. package/bin/cli.js +373 -20
  4. package/package.json +22 -3
  5. package/standards/backend/architecture/event-driven.md +112 -0
  6. package/standards/backend/architecture/microservice-anatomy.md +106 -0
  7. package/standards/backend/architecture/multitenancy.md +112 -0
  8. package/standards/backend/architecture/public-api-facade.md +112 -0
  9. package/standards/backend/architecture/shared-vs-owned.md +62 -0
  10. package/standards/{backend-standards.md → backend/backend-standards.md} +8 -1
  11. package/standards/{database-conventions.md → backend/database-conventions.md} +7 -0
  12. package/standards/backend/technology-stack.md +73 -0
  13. package/standards/core/ai-collaboration.md +64 -0
  14. package/standards/core/clean-architecture-ddd.md +69 -0
  15. package/standards/core/coding-conventions.md +66 -0
  16. package/standards/core/testing-strategy.md +46 -0
  17. package/standards/{mobile-flutter-standards.md → mobile/flutter/flutter-standards.md} +9 -1
  18. package/standards/{mobile-react-native-standards.md → mobile/react-native/react-native-standards.md} +9 -1
  19. package/standards/{technical-preferences-ux.md → web/_base/design-system-ux.md} +8 -1
  20. package/standards/web/_base/frontend-architecture.md +75 -0
  21. package/standards/{frontend-standards.md → web/_base/frontend-standards.md} +7 -0
  22. package/standards/web/_base/technology-stack.md +40 -0
  23. package/standards/web/microfrontends/module-federation-standard.md +216 -0
  24. package/standards/web/single-spa/single-spa-standard.md +196 -0
  25. package/standards/web/spa/spa-standard.md +53 -0
  26. package/standards/architecture-patterns.md +0 -444
  27. package/standards/technology-stack.md +0 -294
  28. package/standards/vite-config-standard.md +0 -531
@@ -1,6 +1,14 @@
1
+ ---
2
+ title: React Native Development Standards
3
+ platform: mobile
4
+ track: react-native
5
+ load_when: "Building a React Native app — Expo, Zustand, Expo Router, NativeWind."
6
+ updated: 2026-06
7
+ ---
8
+
1
9
  # Mobile React Native Development Standards
2
10
 
3
- > **Note**: For shared architectural principles (Clean Architecture, DDD, design philosophy), refer to [architecture-patterns.md](./architecture-patterns.md). For design system (colors, typography, UX), refer to [technical-preferences-ux.md](./technical-preferences-ux.md). React Native shares the same core architecture philosophy as the frontend standards in [frontend-standards.md](./frontend-standards.md) — adapted for native mobile.
11
+ > **Note**: For shared architectural principles (Clean Architecture, DDD), refer to [`../../core/clean-architecture-ddd.md`](../../core/clean-architecture-ddd.md). For design system (colors, typography, UX), refer to [`../../web/_base/design-system-ux.md`](../../web/_base/design-system-ux.md). React Native shares the same core architecture philosophy as the web frontend standards in [`../../web/_base/frontend-standards.md`](../../web/_base/frontend-standards.md) — adapted for native mobile.
4
12
 
5
13
  ---
6
14
 
@@ -1,4 +1,11 @@
1
- # Technical Preferences - UX Configuration
1
+ ---
2
+ title: Design System & UX
3
+ platform: web
4
+ load_when: "UI/visual work — colors, typography, spacing, accessibility, performance targets."
5
+ updated: 2026-06
6
+ ---
7
+
8
+ # Design System & UX
2
9
 
3
10
  ## Project Setup Configuration
4
11
  Based on Frontend Development Standards and Clean Architecture Implementation
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: Frontend Architecture
3
+ platform: web
4
+ load_when: "Any web work — defines folder structure, routing conventions, and how to pick a web track."
5
+ updated: 2026-06
6
+ ---
7
+
8
+ # Frontend Architecture
9
+
10
+ Applies to every web track (SPA, Single-SPA, Module Federation). Implements [`core/clean-architecture-ddd.md`](../../core/clean-architecture-ddd.md) on the frontend.
11
+
12
+ ## Choosing a web track
13
+
14
+ These are **independent options**, not a default-plus-exceptions. Pick by need:
15
+
16
+ | Track | Choose when | Doc |
17
+ |---|---|---|
18
+ | **SPA** | One cohesive app, single deployable | [`web/spa/spa-standard.md`](../spa/spa-standard.md) |
19
+ | **Single-SPA** | Independently-deployed modules, possibly **mixed frameworks**, hard lifecycle isolation | [`web/single-spa/single-spa-standard.md`](../single-spa/single-spa-standard.md) |
20
+ | **Module Federation** | Homogeneous React, capabilities reused across products, **license-gated runtime composition** | [`web/microfrontends/module-federation-standard.md`](../microfrontends/module-federation-standard.md) |
21
+
22
+ Default to **SPA** until a concrete need justifies microfrontend complexity. Don't adopt it speculatively.
23
+
24
+ ## Folder structure
25
+
26
+ Organize by **business module → domain → feature**, with Clean Architecture layers inside each feature:
27
+
28
+ ```
29
+ src/
30
+ ├── main.tsx # entry point
31
+ ├── routes/ # TanStack Router (route definitions only)
32
+ │ ├── __root.tsx # root layout (providers)
33
+ │ ├── _auth.tsx # pathless public layout
34
+ │ └── _app.tsx # pathless protected layout
35
+ ├── modules/ # business logic
36
+ │ └── sales/ # MODULE
37
+ │ └── quotes/ # DOMAIN
38
+ │ └── cart/ # FEATURE
39
+ │ ├── domain/ # entities, repo interfaces, types
40
+ │ ├── application/ # use-cases, hooks, store
41
+ │ ├── infrastructure/ # repo impls, api, adapters
42
+ │ └── presentation/ # components
43
+ ├── shared/ # reusable components, hooks, lib, types
44
+ ├── app/ # global providers + stores
45
+ ├── infrastructure/ # global services (api, storage, pwa)
46
+ └── styles/ # global styles
47
+ ```
48
+
49
+ ## TanStack Router conventions
50
+
51
+ | Prefix | Effect | Example |
52
+ |---|---|---|
53
+ | `_` | Pathless layout (no URL segment) | `_app.tsx` |
54
+ | `.` | Flat routing | `orders.$id.tsx` → `/orders/:id` |
55
+ | `-` | Ignored by router (colocated files) | `-components/` |
56
+ | `$` | Dynamic parameter | `$orderId.tsx` → `:orderId` |
57
+
58
+ ## Core rules
59
+
60
+ - **MUST** keep `routes/` for route definitions only; business logic lives in `modules/`.
61
+ - **MUST** split state by ownership: **Zustand** (global client) / **TanStack Query** (server) / `useState` (local).
62
+ - **MUST** keep `routeTree.gen.ts` untouched (auto-generated).
63
+ - **MUST** meet WCAG 2.1 AA in every component.
64
+ - **SHOULD** lazy-load routes (automatic with TanStack Router `autoCodeSplitting`).
65
+ - **SHOULD** build complex UI by composing small reusable components.
66
+
67
+ ## State management split
68
+
69
+ | State | Tool | Example |
70
+ |---|---|---|
71
+ | Server data (fetch/cache/sync) | TanStack Query | product list, order detail |
72
+ | Global client state | Zustand | auth session, theme, cart |
73
+ | Local UI state | `useState`/`useReducer` | modal open, form field focus |
74
+
75
+ See [`frontend-standards.md`](frontend-standards.md) for detailed component, form, and testing rules.
@@ -1,3 +1,10 @@
1
+ ---
2
+ title: Frontend Development Standards
3
+ platform: web
4
+ load_when: "Any web UI implementation — components, hooks, forms, state, testing."
5
+ updated: 2026-06
6
+ ---
7
+
1
8
  # Frontend Development Standards
2
9
 
3
10
  ## Resumen
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Web Technology Stack
3
+ platform: web
4
+ load_when: "Any web work — the approved frontend toolchain and versions."
5
+ updated: 2026-06
6
+ ---
7
+
8
+ # Web Technology Stack
9
+
10
+ The approved frontend toolchain. Use these by default; deviations need a stated reason.
11
+
12
+ | Category | Technology | Version | Notes |
13
+ |---|---|---|---|
14
+ | Bundler | Vite | 7+ | native ESM, fast HMR |
15
+ | Framework | React | 19 (18+ supported) | functional components + hooks |
16
+ | Language | TypeScript | 5+ | `strict` mode mandatory |
17
+ | Routing | TanStack Router | 1+ | file-based, type-safe, auto code-splitting |
18
+ | Client state | Zustand | 5+ | feature-based stores |
19
+ | Server state | TanStack Query | 5+ | cache, sync, optimistic updates |
20
+ | UI components | shadcn/ui + Radix UI | latest | install shadcn via MCP, not by hand |
21
+ | Styling | TailwindCSS | v4 | utility-first; design tokens from the UI kit |
22
+ | Forms | React Hook Form + Zod | latest | schema-driven validation |
23
+ | HTTP | Axios | latest | with interceptors |
24
+ | Testing | Vitest + React Testing Library + MSW | latest | MSW for API mocking |
25
+ | PWA | `vite-plugin-pwa` + Workbox | latest | offline-first where required |
26
+
27
+ ## Rules
28
+
29
+ - **MUST** use TypeScript `strict`; no implicit `any`.
30
+ - **MUST** validate forms with Zod schemas; never trust client input.
31
+ - **MUST** keep the initial bundle < 500KB gzipped (see performance targets in [`design-system-ux.md`](design-system-ux.md)).
32
+ - **SHOULD** install shadcn/ui components via the MCP integration rather than hand-copying.
33
+ - **SHOULD** prefer TanStack Query for all server state instead of bespoke fetch-in-effect logic.
34
+
35
+ ## Microfrontend dependency sharing
36
+
37
+ When using a microfrontend track, shared dependencies (React, router, query client, Zustand) **MUST** be declared `singleton: true` with a `requiredVersion` to prevent duplicate runtime instances. Track-specific configuration:
38
+
39
+ - Module Federation → [`web/microfrontends/module-federation-standard.md`](../microfrontends/module-federation-standard.md)
40
+ - Single-SPA → [`web/single-spa/single-spa-standard.md`](../single-spa/single-spa-standard.md)
@@ -0,0 +1,216 @@
1
+ ---
2
+ title: Module Federation Microfrontends Standard
3
+ platform: web
4
+ track: microfrontends
5
+ load_when: "Building a homogeneous (all-React) multi-product web platform where capabilities are reused across products and modules are enabled/disabled per license without redeploy."
6
+ updated: 2026-06
7
+ ---
8
+
9
+ # Module Federation Microfrontends Standard
10
+
11
+ > **Choose this track when** the whole frontend is a **homogeneous React stack** and you want: efficient sharing of singleton dependencies, end-to-end TypeScript typing across module boundaries, native code-splitting, and **runtime composition driven by the backend** (per-tenant licensing). If you must mix frameworks or need hard per-module lifecycle isolation, use the [Single-SPA track](../single-spa/single-spa-standard.md) instead.
12
+
13
+ This standard uses **Module Federation 2.0** (`@module-federation/enhanced`), whose runtime is decoupled from the bundler into a standalone SDK. That decoupling is what makes **dynamic, license-gated remote loading** possible: the host asks the backend which remotes to load and registers them at runtime.
14
+
15
+ ## The two axes
16
+
17
+ A multi-product platform has two **independent** dimensions. Conflating them is the classic mistake.
18
+
19
+ | | Capability 1 | Capability 2 | Capability 3 | Capability 4 |
20
+ |--------------|:---:|:---:|:---:|:---:|
21
+ | **Product A** | ✅ | ✅ | | |
22
+ | **Product B** | ✅ | | ✅ | |
23
+ | **Product C** | | ✅ | | |
24
+ | **Product D** | | | | ✅ |
25
+
26
+ - **Capabilities** (horizontal) = **remote MFEs**. Built, deployed and versioned independently. Written **once**, reused across products.
27
+ - **Products** (vertical) = **router layouts inside the single shell**. They compose capabilities and pass them props. Creating a new product = adding a folder with a `route.tsx`, not a new app.
28
+
29
+ ## Core rules
30
+
31
+ - **MUST** use a **single shell, single router, single session**. Never nest a shell inside a shell.
32
+ - **MUST** model products as **route layouts** in the shell; model capabilities as **remote MFEs**.
33
+ - **MUST** keep capabilities **product-agnostic**: a remote receives typed props and adapts; it never knows which product hosts it.
34
+ - **MUST** declare shared singletons (`react`, `react-dom`, router, query client, UI kit, contracts) with `singleton: true` and a `requiredVersion`. Symmetric config between shell and every remote.
35
+ - **MUST** decide which remotes load from a **backend manifest endpoint** keyed by tenant/license — the frontend never hardcodes that decision.
36
+ - **MUST** guard products and capabilities with declarative two-level license guards (`beforeLoad`).
37
+ - **MUST** version the shared `@org/contracts` package semantically; a breaking prop change is a new major.
38
+ - **SHOULD** wrap remote loading in `Suspense` + an `ErrorBoundary` with an offline fallback (CDN failure is a real risk).
39
+ - **SHOULD NOT** create "private" props or events the contracts package doesn't describe — that breaks parity and typing.
40
+
41
+ ## Stack (2026)
42
+
43
+ | Concern | Choice |
44
+ |---|---|
45
+ | Federation | **Module Federation 2.0** — `@module-federation/enhanced` (runtime SDK + `mf-manifest.json`) |
46
+ | Bundler | Vite 7+ (`@module-federation/vite`) or Rspack (`@module-federation/rsbuild-plugin`) |
47
+ | Framework | React 19 + TypeScript strict |
48
+ | Router | TanStack Router 1+ (typed nested layouts) |
49
+ | Server state | TanStack Query 5+ (singleton) |
50
+ | Shared packages | `@org/contracts` (types/props), `@org/ui-kit` (design system), `@org/license` (entitlements) |
51
+
52
+ ## Architecture in three layers
53
+
54
+ ```
55
+ SHELL (single host)
56
+ • boots, fetches license + dynamic manifest
57
+ • defines product layouts, owns global routing + guards
58
+ │ Module Federation 2.0 runtime (registerRemotes / loadRemote)
59
+
60
+ CAPABILITY MFEs (remotes, deployed independently)
61
+ mfe-capacidad-1 mfe-capacidad-2 mfe-capacidad-3 ...
62
+ │ build-time imports
63
+
64
+ SHARED PACKAGES (npm, singleton at runtime)
65
+ @org/contracts @org/ui-kit @org/license
66
+ ```
67
+
68
+ ## Products as router layouts
69
+
70
+ ```tsx
71
+ // shell/src/routes/producto-a/route.tsx
72
+ export const Route = createFileRoute('/producto-a')({
73
+ beforeLoad: () => requireProduct('producto-a'), // product-level license guard
74
+ component: () => (
75
+ <div className="producto-a-layout">
76
+ <ProductoAHeader />
77
+ <ProductoASidebar />
78
+ <main><Outlet /></main> {/* a capability renders here */}
79
+ </div>
80
+ ),
81
+ });
82
+ ```
83
+
84
+ ```tsx
85
+ // shell/src/routes/producto-a/capacidad-1.tsx
86
+ import { loadRemote } from '@module-federation/enhanced/runtime';
87
+ const Capacidad1 = lazy(() => loadRemote('mfe_capacidad_1/View'));
88
+
89
+ export const Route = createFileRoute('/producto-a/capacidad-1')({
90
+ beforeLoad: () => requireFeature('capacidad-1'), // capability-level guard
91
+ component: () => (
92
+ <Suspense fallback={<CapabilitySkeleton />}>
93
+ <Capacidad1 mode="producto-a" showExtraContext />
94
+ </Suspense>
95
+ ),
96
+ });
97
+ ```
98
+
99
+ The remote consumes typed props from `@org/contracts` and adapts — it has no knowledge of the host product:
100
+
101
+ ```tsx
102
+ // mfe-capacidad-1/src/View.tsx
103
+ import type { Capacidad1Props } from '@org/contracts';
104
+ export default function View({ mode, showExtraContext, onAction }: Capacidad1Props) {
105
+ // same component, behavior adapts to `mode`
106
+ }
107
+ ```
108
+
109
+ ## Dynamic, license-gated loading (the differentiator)
110
+
111
+ On boot the shell asks the backend which remotes this tenant is licensed for, then registers them with the MF 2.0 runtime. Unlicensed bundles are **never downloaded**.
112
+
113
+ ```
114
+ GET /api/mf-manifest?tenant=cliente-123
115
+ {
116
+ "products": ["producto-a", "producto-c"],
117
+ "features": ["capacidad-1", "capacidad-2"],
118
+ "remotes": {
119
+ "mfe_capacidad_1": "https://cdn.org.com/cap1/2.4.1/mf-manifest.json",
120
+ "mfe_capacidad_2": "https://cdn.org.com/cap2/1.8.0/mf-manifest.json"
121
+ }
122
+ }
123
+ ```
124
+
125
+ ```ts
126
+ // shell/src/bootstrap.ts
127
+ import { init, registerRemotes } from '@module-federation/enhanced/runtime';
128
+
129
+ const manifest = await fetch(`/api/mf-manifest?tenant=${tenantId}`).then(r => r.json());
130
+
131
+ init({
132
+ name: 'shell',
133
+ remotes: [],
134
+ shared: {
135
+ react: { singleton: true, requiredVersion: '^19.0.0' },
136
+ 'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
137
+ },
138
+ });
139
+
140
+ registerRemotes(
141
+ Object.entries(manifest.remotes).map(([name, entry]) => ({ name, entry })),
142
+ );
143
+ ```
144
+
145
+ **Commercial implication:** selling a new module to a tenant = flipping a backend flag. The next session's manifest includes the remote and the UI exposes it. **No redeploy, no client update.**
146
+
147
+ ## Static shell config (build-time remotes that are always present)
148
+
149
+ ```ts
150
+ // shell/vite.config.ts
151
+ import { federation } from '@module-federation/vite';
152
+ export default defineConfig({
153
+ plugins: [react(), federation({
154
+ name: 'shell',
155
+ remotes: {
156
+ mfe_capacidad_1: 'mfe_capacidad_1@/remotes/cap1/mf-manifest.json',
157
+ },
158
+ shared: {
159
+ react: { singleton: true, requiredVersion: '^19.0.0' },
160
+ 'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
161
+ '@tanstack/react-router': { singleton: true },
162
+ '@tanstack/react-query': { singleton: true },
163
+ '@org/ui-kit': { singleton: true },
164
+ '@org/contracts': { singleton: true },
165
+ },
166
+ })],
167
+ });
168
+ ```
169
+
170
+ Remote config exposes its view and **mirrors** the `shared` block (must be symmetric, or dependencies duplicate at runtime):
171
+
172
+ ```ts
173
+ // mfe-capacidad-1/vite.config.ts
174
+ federation({
175
+ name: 'mfe_capacidad_1',
176
+ filename: 'remoteEntry.js',
177
+ exposes: { './View': './src/View.tsx' },
178
+ shared: { /* same singleton block as the shell */ },
179
+ });
180
+ ```
181
+
182
+ ## Contract typing & version-skew safety
183
+
184
+ Module Federation 2.0 can download a remote's **TypeScript types at build time** (`dts`). Treat exposed modules as **public APIs**: add optional props with defaults, never rename/remove props consumers rely on. If the host downloads remote types, a breaking contract change becomes a **build error** instead of a runtime crash.
185
+
186
+ - **MUST** run contract tests in CI between shell and remotes.
187
+ - **SHOULD** deprecate props with a window before removal; bump `@org/contracts` major on breaking change.
188
+
189
+ ## Risks & mitigations
190
+
191
+ | Risk | Mitigation |
192
+ |---|---|
193
+ | Version skew between shell and a remote | Semver `@org/contracts`; MF 2.0 `dts` build-time types; contract tests in CI |
194
+ | Duplicate React/router instances | `singleton: true` + `requiredVersion` on both sides; symmetric `shared` |
195
+ | CDN serving remotes goes down | Service Worker caching + offline fallback in `ErrorBoundary` |
196
+ | CSS collisions between MFEs | CSS Modules / per-MFE prefix; shared design tokens via `@org/ui-kit` |
197
+ | Uncontrolled `ui-kit` growth | Allow duplication first; promote to the kit only after a pattern is validated in 2+ MFEs |
198
+
199
+ ## The checkpoint that proves the design
200
+
201
+ The critical moment is introducing the **second product**: if it requires modifying any capability MFE, the prop contract is wrong and must be fixed before continuing. A well-designed contract makes the 20th product as easy as the first.
202
+
203
+ ## Metrics of success
204
+
205
+ - **Zero functional duplication** — a business rule changes in exactly one place.
206
+ - **Bundle proportional to license** — a single-product tenant never downloads the full catalog.
207
+ - **Independent deploy** — a capability fix reaches production without redeploying the others (< 15 min).
208
+ - **License activation without redeploy** — a new module is live in the next session.
209
+ - **INP < 100ms** on critical interactions; resilient during CDN/connectivity loss.
210
+
211
+ ## References
212
+
213
+ - Module Federation 2.0 — https://module-federation.io/blog/announcement.html
214
+ - Runtime API (`init`, `registerRemotes`, `loadRemote`) — https://module-federation.io/guide/runtime/runtime-api
215
+ - `@module-federation/vite` — https://module-federation.io/
216
+ - TanStack Router — https://tanstack.com/router
@@ -0,0 +1,196 @@
1
+ ---
2
+ title: Single-SPA Microfrontends Standard
3
+ platform: web
4
+ track: single-spa
5
+ load_when: "Building a web app composed of independently-deployed microfrontends that may use DIFFERENT frameworks, or that require hard runtime isolation (CSS/JS/lifecycle) per module."
6
+ updated: 2026-06
7
+ ---
8
+
9
+ # Single-SPA Microfrontends Standard
10
+
11
+ > **Choose this track when** you need to compose microfrontends that are **independently deployed** and possibly built with **different frameworks/versions** (React + Angular + legacy), or when each module must have a **fully isolated lifecycle** (its own bootstrap/mount/unmount, CSS injected and removed on navigation). If your whole frontend is a single homogeneous React stack, prefer the [Module Federation track](../microfrontends/module-federation-standard.md) instead — it shares dependencies more efficiently and gives end-to-end typing.
12
+
13
+ Single-SPA is a **top-level router/orchestrator**: a thin shell registers applications and decides which one is active for a given route. Each microfrontend exposes `bootstrap`/`mount`/`unmount` lifecycles.
14
+
15
+ ## Core rules
16
+
17
+ - **MUST** keep the shell free of business logic. The shell only registers apps, owns global providers (auth, i18n, event bus) and passes them as `customProps`.
18
+ - **MUST** expose `bootstrap`, `mount`, `unmount` lifecycles from each MFE entry (`src/spa.tsx`).
19
+ - **MUST** set `domElementGetter` so the MFE mounts inside the shell container, not at the end of `<body>`.
20
+ - **MUST** isolate CSS per MFE via `cssLifecycleFactory` (injected on mount, removed on unmount).
21
+ - **MUST** wrap each MFE in an error boundary so one module failing never takes down the shell.
22
+ - **MUST** give every MFE a unique route prefix matching its `base` (e.g. `/finance/*`).
23
+ - **SHOULD** ship a standalone entry (`src/main.tsx`) so each MFE runs in isolation during development.
24
+ - **SHOULD** share singletons (`react`, `react-dom`, router, query client) via an SystemJS import map to avoid duplicate instances.
25
+
26
+ ## Stack (2026)
27
+
28
+ | Concern | Choice |
29
+ |---|---|
30
+ | Orchestrator | `single-spa` 6+ |
31
+ | React adapter | `single-spa-react` |
32
+ | Bundler plugin | `vite-plugin-single-spa` |
33
+ | Framework | React 19 (18 still supported) + TypeScript strict |
34
+ | Router (per MFE) | TanStack Router 1+ |
35
+ | Module loader | SystemJS 6 + import maps |
36
+
37
+ ## MFE entry (`src/spa.tsx`)
38
+
39
+ ```tsx
40
+ import React from 'react';
41
+ import ReactDOMClient from 'react-dom/client';
42
+ import singleSpaReact from 'single-spa-react';
43
+ import { cssLifecycleFactory } from 'vite-plugin-single-spa/ex';
44
+ import App from './App';
45
+
46
+ // Context the shell injects into every MFE.
47
+ export interface ShellProps {
48
+ i18n?: unknown;
49
+ eventBus?: unknown;
50
+ authContext?: unknown;
51
+ }
52
+
53
+ function ErrorFallback({ error }: { error: Error }) {
54
+ return (
55
+ <div className="flex min-h-screen items-center justify-center bg-red-50">
56
+ <div className="max-w-md rounded-lg bg-white p-6 shadow-lg">
57
+ <h2 className="mb-2 text-xl font-semibold text-red-600">Error en el módulo</h2>
58
+ <p className="mb-4 text-slate-600">{error.message}</p>
59
+ <button
60
+ onClick={() => window.location.reload()}
61
+ className="rounded bg-red-600 px-4 py-2 text-white hover:bg-red-700"
62
+ >
63
+ Recargar
64
+ </button>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ const lifecycles = singleSpaReact({
71
+ React,
72
+ ReactDOMClient,
73
+ rootComponent: App,
74
+ errorBoundary: (err: Error) => <ErrorFallback error={err} />,
75
+ // CRITICAL: mount inside the shell container, not at the end of <body>.
76
+ domElementGetter: () => document.getElementById('single-spa-application')!,
77
+ });
78
+
79
+ const cssLc = cssLifecycleFactory('spa');
80
+
81
+ export const bootstrap = [cssLc.bootstrap, lifecycles.bootstrap];
82
+ export const mount = [cssLc.mount, lifecycles.mount];
83
+ export const unmount = [cssLc.unmount, lifecycles.unmount];
84
+ ```
85
+
86
+ > **Why `domElementGetter` is mandatory:** without it, `single-spa-react` creates a fresh `<div>` at the end of `<body>` and the MFE renders *outside* the shell layout — mounted correctly but invisible to the user. The shell must expose `<div id="single-spa-application">` in its layout.
87
+
88
+ ## MFE Vite config (`vite.config.ts`)
89
+
90
+ ```ts
91
+ import { defineConfig } from 'vite';
92
+ import react from '@vitejs/plugin-react';
93
+ import vitePluginSingleSpa from 'vite-plugin-single-spa';
94
+
95
+ export default defineConfig({
96
+ plugins: [
97
+ react(),
98
+ vitePluginSingleSpa({
99
+ serverPort: 3001, // unique per module
100
+ spaEntryPoints: 'src/spa.tsx',
101
+ cssStrategy: 'singleMife', // CSS injected/removed on mount/unmount
102
+ projectId: 'finance-module',
103
+ }),
104
+ ],
105
+ server: { port: 3001, cors: true },
106
+ base: '/finance/', // route prefix
107
+ });
108
+ ```
109
+
110
+ ## Shell registration (`app-shell/src/microfrontends/register.ts`)
111
+
112
+ ```ts
113
+ import { registerApplication, start } from 'single-spa';
114
+
115
+ const shared = {
116
+ i18n: i18nInstance,
117
+ eventBus,
118
+ authContext: authStore.getState(),
119
+ };
120
+
121
+ registerApplication({
122
+ name: 'finance',
123
+ app: () => System.import('http://localhost:3001/finance/spa.js'),
124
+ activeWhen: ['/finance'],
125
+ customProps: shared,
126
+ });
127
+
128
+ registerApplication({
129
+ name: 'inventory',
130
+ app: () => System.import('http://localhost:3002/inventory/spa.js'),
131
+ activeWhen: ['/inventory'],
132
+ customProps: shared,
133
+ });
134
+
135
+ start();
136
+ ```
137
+
138
+ Shell import map (`app-shell/index.html`) — pins shared singletons:
139
+
140
+ ```html
141
+ <script type="systemjs-importmap">
142
+ {
143
+ "imports": {
144
+ "react": "https://cdn.jsdelivr.net/npm/react@19/umd/react.production.min.js",
145
+ "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@19/umd/react-dom.production.min.js",
146
+ "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@6/lib/system/single-spa.min.js"
147
+ }
148
+ }
149
+ </script>
150
+ <script src="https://cdn.jsdelivr.net/npm/systemjs@6/dist/system.min.js"></script>
151
+ ```
152
+
153
+ ## Port convention
154
+
155
+ | Module | Port | Role |
156
+ |---|---|---|
157
+ | app-shell | 3000 | Orchestrator |
158
+ | finance | 3001 | MFE |
159
+ | inventory | 3002 | MFE |
160
+ | hr | 3003 | MFE |
161
+ | crm | 3004 | MFE |
162
+
163
+ ## Per-MFE routing (TanStack Router)
164
+
165
+ Every route MUST carry the module prefix that matches `base`:
166
+
167
+ ```tsx
168
+ const dashboardRoute = createRoute({
169
+ getParentRoute: () => rootRoute,
170
+ path: '/finance/dashboard',
171
+ component: Dashboard,
172
+ });
173
+ ```
174
+
175
+ ## Checklist
176
+
177
+ **MFE**
178
+ - [ ] `vite-plugin-single-spa` + `single-spa-react` installed
179
+ - [ ] `src/spa.tsx` exports `bootstrap`/`mount`/`unmount`
180
+ - [ ] `cssLifecycleFactory` wired for CSS isolation
181
+ - [ ] `errorBoundary` implemented
182
+ - [ ] **`domElementGetter` → `#single-spa-application`** ⚠️
183
+ - [ ] Unique port + `base` prefix; all routes use the prefix
184
+ - [ ] `cors: true`; standalone `src/main.tsx` for dev
185
+
186
+ **Shell**
187
+ - [ ] `single-spa` installed; import map with shared singletons
188
+ - [ ] `registerApplication` per MFE + `start()`
189
+ - [ ] `customProps` with i18n/auth/eventBus
190
+ - [ ] `<div id="single-spa-application">` present in layout ⚠️
191
+
192
+ ## References
193
+
194
+ - Single-SPA — https://single-spa.js.org/
195
+ - single-spa-react — https://single-spa.js.org/docs/ecosystem-react/
196
+ - vite-plugin-single-spa — https://github.com/single-spa/vite-plugin-single-spa
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: Single-Page Application (SPA) Standard
3
+ platform: web
4
+ track: spa
5
+ load_when: "Building one cohesive web application that ships as a single deployable — no microfrontends, no runtime composition."
6
+ updated: 2026-06
7
+ ---
8
+
9
+ # Single-Page Application (SPA) Standard
10
+
11
+ > **Choose this track** for a single, cohesive app deployed as one unit. It's the default for most products. Only move to [Single-SPA](../single-spa/single-spa-standard.md) or [Module Federation](../microfrontends/module-federation-standard.md) when you have a real need for independently-deployed modules — don't adopt microfrontend complexity speculatively.
12
+
13
+ A monolithic SPA still follows the same Clean Architecture + DDD folder structure and conventions as every web track. See [`frontend-architecture.md`](../_base/frontend-architecture.md) and [`frontend-standards.md`](../_base/frontend-standards.md) for the shared rules; this document only states what is specific to the single-deployable case.
14
+
15
+ ## Core rules
16
+
17
+ - **MUST** ship as a single Vite build; no `remoteEntry`/federation, no SystemJS import maps.
18
+ - **MUST** use TanStack Router file-based routing with pathless layouts for auth/app boundaries.
19
+ - **MUST** code-split per route (automatic with TanStack Router) to keep the initial bundle < 500KB gzipped.
20
+ - **SHOULD** organize by business module/domain/feature, not by technical layer.
21
+ - **SHOULD** lazy-load heavy, rarely-used routes and features.
22
+
23
+ ## Vite config (`vite.config.ts`)
24
+
25
+ ```ts
26
+ import { defineConfig } from 'vite';
27
+ import react from '@vitejs/plugin-react';
28
+ import { tanstackRouter } from '@tanstack/router-plugin/vite';
29
+
30
+ export default defineConfig({
31
+ plugins: [
32
+ tanstackRouter({ target: 'react', autoCodeSplitting: true }),
33
+ react(),
34
+ ],
35
+ build: { target: 'esnext' },
36
+ });
37
+ ```
38
+
39
+ ## When to graduate to microfrontends
40
+
41
+ Adopt a microfrontend track only when **at least one** is true:
42
+
43
+ - Independent teams need to deploy modules on **separate release cadences**.
44
+ - A capability is **reused across multiple products** with different layouts.
45
+ - You need to **enable/disable modules per tenant/license at runtime** (→ Module Federation).
46
+ - You must integrate modules built with **different frameworks** (→ Single-SPA).
47
+
48
+ Until then, a well-structured SPA is faster to build, debug and deploy.
49
+
50
+ ## References
51
+
52
+ - TanStack Router — https://tanstack.com/router
53
+ - Vite — https://vite.dev/