Architecture
Deep dive into the NaaP micro-frontend architecture and how the shell, plugins, and services interact.
High-Level Architecture#
NaaP follows a micro-frontend architecture where the shell application acts as a host and plugins are independently deployable frontends loaded at runtime.
| 1 | ┌─────────────────────────────────────────────────────┐ |
| 2 | │ Shell (Next.js Host) │ |
| 3 | │ │ |
| 4 | │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ |
| 5 | │ │ Auth │ │ Theme │ │ Plugin Registry │ │ |
| 6 | │ └──────────┘ └──────────┘ └──────────────────┘ │ |
| 7 | │ │ |
| 8 | │ ┌───────────────────────────────────────────────┐ │ |
| 9 | │ │ Plugin Container (UMD) │ │ |
| 10 | │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ |
| 11 | │ │ │Plugin A│ │Plugin B│ │Plugin C│ ... │ │ |
| 12 | │ │ │(React) │ │(React) │ │(React) │ │ │ |
| 13 | │ │ └───┬────┘ └───┬────┘ └───┬────┘ │ │ |
| 14 | │ └──────┼───────────┼───────────┼────────────────┘ │ |
| 15 | └─────────┼───────────┼───────────┼───────────────────┘ |
| 16 | │ │ │ |
| 17 | ▼ ▼ ▼ |
| 18 | ┌──────────┐ ┌──────────┐ ┌──────────┐ |
| 19 | │Backend A │ │Backend B │ │Backend C │ |
| 20 | │(Express) │ │(Express) │ │(Express) │ |
| 21 | └────┬─────┘ └────┬─────┘ └────┬─────┘ |
| 22 | │ │ │ |
| 23 | └──────┬──────┘ │ |
| 24 | ▼ ▼ |
| 25 | ┌─────────────────────────────┐ |
| 26 | │ @naap/database (shared) │ |
| 27 | │ Single PostgreSQL + Schemas│ |
| 28 | └─────────────────────────────┘ |
Core Principles#
1. Vertical Slicing#
Each plugin owns its entire vertical slice:
- Frontend: React components and pages
- Backend: Express API server
- Database: Isolated PostgreSQL schema within the unified database (see Database Architecture)
- Documentation: Plugin-specific docs
This means teams can develop, test, and deploy plugins independently.
2. Runtime Loading (UMD)#
Plugins are compiled to UMD (Universal Module Definition) bundles and loaded at runtime. The shell:
- Fetches the plugin manifest from the registry
- Loads the UMD bundle via a
<script>tag - Calls the plugin's
mount()function with the shell context - Renders the plugin inside a container element
3. Shared Context via SDK#
Plugins receive a ShellContext object that provides access to all shell services:
| 1 | interface ShellContext { |
| 2 | auth: IAuthService; // Authentication & authorization |
| 3 | navigate: NavigateFunction; // Client-side navigation |
| 4 | eventBus: IEventBus; // Inter-plugin communication |
| 5 | theme: IThemeService; // Theme management |
| 6 | notifications: INotificationService; // Toast notifications |
| 7 | integrations: IIntegrationService; // AI, storage, email |
| 8 | logger: ILoggerService; // Structured logging |
| 9 | permissions: IPermissionService; // Permission checking |
| 10 | tenant?: ITenantService; // Tenant context |
| 11 | team?: ITeamContext; // Team context |
| 12 | } |
4. API Routes#
On Vercel (production), plugin API logic runs as Next.js API route handlers at /api/v1/[plugin-name]/* — there are no separate backend servers. On local development, the shell proxies these requests to standalone Express backends:
| 1 | # Production (Vercel) |
| 2 | Browser → /api/v1/my-plugin/data → Next.js API Route Handler → Database |
| 3 | |
| 4 | # Local Development |
| 5 | Browser → /api/v1/my-plugin/data → Proxy → Express Backend → Database |
All API responses use a standard envelope format:
// Success: { success: true, data: T, meta?: { page, limit, total } }
// Error: { success: false, error: { code, message } }This ensures:
- Consistent authentication across all plugins
- No CORS issues (same-origin on Vercel)
- Plugin backends don't need to be publicly accessible
Request Flow#
- User navigates to
/my-plugin - Middleware rewrites to
/plugins/myPlugin - Plugin loader fetches manifest from registry
- UMD bundle is loaded from CDN / Vercel Blob
mount()is called with shell context- Plugin renders inside the container
- API calls go through
/api/v1/my-plugin/*proxy
Deployment Model#
Vercel (Production)#
The entire platform is deployed to Vercel as a single Next.js application:
- Shell + API Routes: The Next.js App Router serves the shell UI and 46+ API route handlers that replace the standalone Express backends
- Plugin Assets: UMD bundles are served via the same-origin CDN route at
/cdn/plugins/[name]/[version]/[file] - Database: Single PostgreSQL (e.g. Neon) connected via
DATABASE_URL
Local Development#
For local development, plugin backends run as standalone Express servers on individual ports (4001-4012). The shell proxies API requests to the appropriate backend.
Multi-Tenant#
- Virtual installations: Per-user/team installations with isolated configs
- Data isolation: Each plugin uses its own PostgreSQL schema within the shared database
- Reference counting: Cleanup when the last user uninstalls