All applications in this monorepo are static React apps deployed to S3. There are no server-side runtimes. The deployment target is the key distinguishing factor.
apps/ |
packages/ |
|
|---|---|---|
Produces a dist/ uploaded to S3 |
✅ | ❌ |
Produces a dist/ published to npm |
❌ | ✅ |
You import it at build-time |
❌ | ✅ |
| Has its own dev server / port | ✅ | ❌ |
| Consumed at runtime | ✅ | ❌ (build-time only) |
One-liner: If it gets uploaded to S3 → app. If it gets published to npm → package.
apps/
web/ # Main React app → deployed to S3
admin/ # Back-office React app → deployed to S3
storybook/ # Component explorer → deployed to S3
packages/
ui/ # Shared component library
utils/ # Shared helpers & types
config-typescript/ # Shared tsconfig.json
config-eslint/ # Shared ESLint config
Split into two: a package for the embeddable logic and an app as the standalone shell.
apps/
my-app/ # Standalone → built and uploaded to S3
my-app-dev/ # Dev harness for local iteration (never published or deployed)
packages/
my-app-core/ # Published to npm, consumed by external apps at build-time
apps/my-app is a thin wrapper that imports from packages/my-app-core. This ensures the embedded and standalone versions always run the same code.
MFE remotes go in apps/ because each remote produces its own remoteEntry.js that is uploaded to S3 and consumed at runtime by the shell — not at build-time like an npm package.
apps/
shell/ # Host app → uploaded to S3, consumes remotes at runtime
mfe-dashboard/ # Remote → uploaded to S3, exposes ./Dashboard
mfe-settings/ # Remote → uploaded to S3, exposes ./Settings
mfe-auth/ # Remote → uploaded to S3, exposes ./AuthProvider
packages/
ui/ # Shared components (build-time, no federation config)
utils/ # Shared helpers
mfe-types/ # Shared TypeScript contracts between MFEs
Shared TypeScript types and contracts between MFEs always live in packages/mfe-types — imported at build-time, keeping runtime coupling minimal.
┌──────────────────────────────────────────┐
│ apps/shell (host, S3) │
│ ┌─────────────┐ ┌────────────────────┐ │
│ │ mfe-dashboard│ │ mfe-settings │ │ ← apps/ (each on S3, runtime remotes)
│ └─────────────┘ └────────────────────┘ │
│ ↑ all consume ↑ │
├──────────────────────────────────────────┤
│ packages/ui, utils, mfe-types │ ← packages/ (build-time deps, npm only)
└──────────────────────────────────────────┘
When unsure, ask:
- Does it produce a
dist/that gets uploaded to S3? → app - Does it produce a
dist/that gets published to npm? → package - Does it have a
ModuleFederationPluginconfig or its ownremoteEntry.js? → app - Is it consumed only at build-time (tsconfig, eslint, types)? → package
- Does it do both (deploy to S3 AND publish to npm)? → split into app + package