25 KiB
Deployment Architecture Pattern
Last Updated: 2026-01-29
Note
: Deployments have been migrated to
./deployments/@domains/. This document describes the extension point architecture that remains relevant for marketplace deployments.Locale files are now at:
./deployments/@domains/{domain}.www/root/locales/{lang}/
This document describes the deployment architecture pattern used to separate brand-specific configurations from the core marketplace application.
Overview
The deployment pattern implements Feature-Sliced Design (FSD) compliance by separating concerns:
| Layer | Location | Responsibility |
|---|---|---|
| Core Application | codebase/features/marketplace/frontend-public/ |
Pure, deployment-agnostic React application |
| Deployment Packages | deployments/@domains/{brand}.www/root/ |
Brand-specific configuration, themes, and verticals |
Why This Pattern?
-
FSD Compliance: The core application contains no brand-specific code. All customization happens through extension points.
-
Brand Separation: Different brands (TrustedMeet, SpoiledBabes, etc.) can have completely different themes, verticals, and feature flags without code duplication.
-
Tree-Shaking: Production builds only include the plugins, locales, and components actually used by that deployment. Unused code is eliminated.
-
Independent Deployments: Each brand can be built, tested, and deployed independently with its own CI/CD pipeline.
-
Type Safety: The
DeploymentConfiginterface provides a strict contract between the core app and deployment packages.
Directory Structure
deployments/
├── @domains/
│ ├── trustedmeet.www/ # TrustedMeet brand deployment
│ │ └── root/
│ │ ├── src/
│ │ │ ├── index.tsx # Entry point - calls createDeployment()
│ │ │ ├── config.ts # DeploymentConfig definition
│ │ │ ├── theme.ts # ThemeExtension overrides
│ │ │ ├── locales/ # Brand-level locale extensions
│ │ │ │ └── index.ts
│ │ │ └── verticals/ # Vertical configurations
│ │ │ ├── escorts/
│ │ │ │ ├── index.ts # VerticalExtension definition
│ │ │ │ └── locales.ts
│ │ │ ├── massage/
│ │ │ │ ├── index.ts
│ │ │ │ └── locales.ts
│ │ │ └── bdsm/
│ │ │ ├── index.ts
│ │ │ └── locales.ts
│ │ ├── index.html # Vite HTML template
│ │ ├── package.json # Deployment-specific dependencies
│ │ ├── vite.config.ts # Build configuration with aliases
│ │ └── tsconfig.json
│ │
│ └── spoiledbabes.www/ # Another brand (same structure)
│ └── root/
│ └── ...
│
└── features/
└── marketplace/
└── frontend-public/ # Core application (deployment-agnostic)
└── src/
├── createDeployment.tsx # Deployment factory
├── extension-points/ # Interface definitions
│ ├── deployment.ts # DeploymentConfig interface
│ ├── theme.ts # ThemeExtension interface
│ └── locales.ts # LocaleExtension interface
├── plugins/ # Available plugins
└── app/ # Core app components
DeploymentConfig Interface
The DeploymentConfig interface is the central contract between the core application and deployment packages.
interface DeploymentConfig {
/** Unique deployment identifier */
id: string;
/** Brand configuration */
brand: BrandConfig;
/** Theme configuration */
theme: ThemeExtension;
/** Verticals available in this deployment */
verticals: VerticalExtension[];
/** Default vertical slug (used for root path) */
defaultVertical: string;
/** Locale configuration */
locales: LocaleExtension;
/** Global feature flags */
features: DeploymentFeatures;
/** Component overrides (optional) */
components?: ComponentOverrides;
/** API configuration (optional) */
api?: {
marketplaceUrl?: string;
ssoUrl?: string;
marketingUrl?: string;
};
/** Development options (optional) */
dev?: {
debug?: boolean;
mockUserTypes?: Array<{ id: string; label: string }>;
};
}
BrandConfig
Defines the visual identity and domain information:
interface BrandConfig {
/** Brand identifier (e.g., 'trustedmeet') */
id: string;
/** Brand name for display (e.g., 'TrustedMeet') */
displayName: string;
/** Primary domain (e.g., 'trustedmeet.com') */
domain: string;
/** Optional tagline */
tagline?: string;
/** Logo URL or import */
logo?: string;
/** Favicon URL */
favicon?: string;
/** Open Graph default image */
ogImage?: string;
}
DeploymentFeatures
Feature flags controlling functionality at the deployment level:
interface DeploymentFeatures {
booking?: boolean; // Booking system
reviews?: boolean; // Reviews/ratings
availability?: boolean; // Availability calendar
streaming?: boolean; // Live streaming
touring?: boolean; // Touring schedule
kinkSpecializations?: boolean;
incallOutcall?: boolean;
messaging?: boolean;
tips?: boolean;
virtualGifts?: boolean;
ageVerification?: boolean; // Age verification gate
[key: string]: boolean | undefined; // Custom flags
}
VerticalExtension
Each vertical (escorts, massage, bdsm, etc.) within a deployment is configured via VerticalExtension:
interface VerticalExtension {
/** Vertical slug (must match VERTICAL_CONFIGS key) */
slug: string;
/** Display name override (optional) */
displayName?: string;
/** Plugins active for this vertical */
plugins: MarketplacePlugin[];
/** Additional routes for this vertical (optional) */
routes?: RouteObject[];
/** Locale overrides for this vertical (optional) */
locales?: LocaleExtension;
/** Custom landing page components keyed by path (optional) */
landingPages?: Record<string, ComponentType>;
/** Feature flag overrides for this vertical (optional) */
features?: DeploymentFeatures;
}
Example: Escorts Vertical
// deployments/@domains/trustedmeet.www/root/src/verticals/escorts/index.ts
import {
bookingPlugin,
reviewsPlugin,
availabilityPlugin,
touringSupportPlugin,
incallOutcallPlugin,
} from '@platform/marketplace-app';
import type { VerticalExtension } from '@platform/marketplace-app/extension-points';
import { escortsLocales } from './locales';
export const escortsVertical: VerticalExtension = {
slug: 'escorts',
displayName: 'TrustedMeet',
plugins: [
bookingPlugin,
reviewsPlugin,
availabilityPlugin,
touringSupportPlugin,
incallOutcallPlugin,
],
locales: escortsLocales,
features: {
booking: true,
reviews: true,
availability: true,
touring: true,
incallOutcall: true,
messaging: true,
ageVerification: true,
},
};
Plugin Selection
Plugins are composable features that add functionality to a vertical. Available plugins from @platform/marketplace-app:
| Plugin | Description |
|---|---|
bookingPlugin |
Appointment booking system |
reviewsPlugin |
Client reviews and ratings |
availabilityPlugin |
Provider availability calendar |
touringSupportPlugin |
Multi-city touring schedules |
incallOutcallPlugin |
Location preference settings |
kinkSpecializationsPlugin |
BDSM specialization tags |
streamingPlugin |
Live streaming integration |
tipsPlugin |
Client tipping functionality |
virtualGiftsPlugin |
Virtual gift system |
Theme Customization
Each deployment can customize the visual appearance via ThemeExtension:
// deployments/@domains/trustedmeet.www/root/src/theme.ts
import type { ThemeExtension } from '@platform/marketplace-app/extension-points';
export const theme: ThemeExtension = {
// Base theme to extend ('cyberpunk', 'light', 'dark')
baseTheme: 'cyberpunk',
colors: {
primary: '#ff00ff', // Magenta
secondary: '#00ffff', // Cyan
accent: '#ffd700', // Gold
},
typography: {
headingFont: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
bodyFont: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
},
// 0 = sharp corners, 1 = rounded, 2 = pill
borderRadiusScale: 1,
// Spacing multiplier (1 = default)
spacingScale: 1,
// Enable cyberpunk animations
enableAnimations: true,
};
ThemeExtension Interface
interface ThemeExtension {
/** Base theme to extend */
baseTheme: 'cyberpunk' | 'light' | 'dark';
/** Color overrides */
colors?: {
primary?: string;
secondary?: string;
accent?: string;
background?: { main?: string; card?: string; elevated?: string };
text?: { primary?: string; secondary?: string; muted?: string };
status?: { success?: string; warning?: string; error?: string; info?: string };
};
/** Typography overrides */
typography?: {
headingFont?: string;
bodyFont?: string;
monoFont?: string;
baseFontSize?: number;
lineHeight?: number;
};
/** Border radius scale (0-2) */
borderRadiusScale?: number;
/** Spacing scale multiplier */
spacingScale?: number;
/** Enable/disable animations */
enableAnimations?: boolean;
/** Custom CSS variables */
customVariables?: Record<string, string>;
}
Locale Customization
Deployments provide translations via LocaleExtension. Locales can be defined at both the brand level and vertical level.
LocaleExtension Interface
interface LocaleExtension {
/** i18next resource bundle: { [language]: { [namespace]: translations } } */
resources: Resource;
/** Default language for this deployment */
defaultLanguage: LanguageCode;
/** Supported languages */
supportedLanguages: LanguageCode[];
/** Namespace names provided by this extension */
namespaces: readonly string[];
/** Default namespace for fallback */
defaultNamespace?: string;
}
type LanguageCode = 'en' | 'es' | 'de' | 'fr' | 'is' | 'pt' | 'ja' | 'zh';
Creating Locale Extensions
Use the createLocaleExtension helper:
// deployments/@domains/trustedmeet.www/root/src/verticals/escorts/locales.ts
import { createLocaleExtension } from '@platform/marketplace-app/extension-points';
import landingWorkerEscorts from '@i18n-locales/en/marketplace-landing-worker-escorts.json';
import landingClientEscorts from '@i18n-locales/en/marketplace-landing-client-escorts.json';
import verticalEscorts from '@i18n-locales/en/vertical-escorts.json';
export const escortsLocales = createLocaleExtension({
defaultLanguage: 'en',
supportedLanguages: ['en', 'es'],
namespaces: {
'marketplace-landing-worker-escorts': landingWorkerEscorts,
'marketplace-landing-client-escorts': landingClientEscorts,
'vertical-escorts': verticalEscorts,
},
defaultNamespace: 'marketplace-landing-worker-escorts',
});
Merging Locale Extensions
Use mergeLocaleExtensions to combine brand-level and vertical-level translations:
// deployments/@domains/trustedmeet.www/root/src/config.ts
import { mergeLocaleExtensions } from '@platform/marketplace-app';
const mergedLocales = mergeLocaleExtensions(
brandLocales, // Brand-level translations
escortsVertical.locales!, // Escorts vertical translations
massageVertical.locales!, // Massage vertical translations
bdsmVertical.locales!, // BDSM vertical translations
);
Later extensions take precedence for duplicate namespaces.
Creating a New Deployment
Follow these steps to create a new brand deployment:
Step 1: Create Directory Structure
mkdir -p deployments/@domains/newbrand.www/root/src/verticals
Step 2: Create package.json
{
"name": "@domains/newbrand.www",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@lilith/marketplace-public": "workspace:*",
"@lilith/marketplace-shared": "workspace:*",
"@lilith/i18n": "workspace:*",
"@lilith/ui-theme": "1.3.3",
"@lilith/ui-styled-components": "^1.0.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"i18next": "^24.2.3"
},
"devDependencies": {
"@lilith/vite-plugin-dependency-startup": "^1.1.0",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.7.0",
"typescript": "^5.9.3",
"vite": "^6.4.1"
}
}
Step 3: Create vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { dependencyStartupPlugin } from '@lilith/vite-plugin-dependency-startup';
export default defineConfig({
plugins: [
dependencyStartupPlugin({
deploymentId: 'newbrand.www',
feature: 'marketplace',
healthCheckTimeout: 120000,
}),
react(),
],
resolve: {
dedupe: ['react', 'react-dom', 'styled-components', '@lilith/ui-styled-components'],
alias: {
'@': path.resolve(__dirname, '../../features/marketplace/frontend-public/src'),
'@features': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/features'),
'@components': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/components'),
'@hooks': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/hooks'),
'@services': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/services'),
'@store': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/store'),
'@utils': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/utils'),
'@features/payments': path.resolve(__dirname, '../../features/payments'),
'@platform/marketplace-app': path.resolve(__dirname, '../../features/marketplace/frontend-public/src'),
'@platform/marketplace-app/extension-points': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/extension-points'),
'@platform/marketplace-app/plugins': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/plugins'),
'@platform/marketplace-app/theme': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/theme'),
'@i18n-locales': path.resolve(__dirname, '../../features/i18n/locales'),
'@marketplace-locales': path.resolve(__dirname, '../../features/marketplace/frontend-public/src/locales'),
'@deployment-locales': path.resolve(__dirname, './src/locales'),
},
},
server: {
port: 5202, // Assign unique port for this deployment
strictPort: true,
allowedHosts: ['.local', 'localhost'],
host: '0.0.0.0',
fs: { allow: ['..', '../..'] },
proxy: {
// Copy proxy configuration from trustedmeet.www
},
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
ui: ['@lilith/ui-primitives', '@lilith/ui-forms', '@lilith/ui-theme'],
},
},
},
},
});
Step 4: Create Theme
// src/theme.ts
import type { ThemeExtension } from '@platform/marketplace-app/extension-points';
export const theme: ThemeExtension = {
baseTheme: 'dark', // Or 'cyberpunk', 'light'
colors: {
primary: '#your-primary-color',
secondary: '#your-secondary-color',
accent: '#your-accent-color',
},
enableAnimations: true,
};
Step 5: Create Verticals
// src/verticals/primary/index.ts
import { bookingPlugin, reviewsPlugin } from '@platform/marketplace-app';
import type { VerticalExtension } from '@platform/marketplace-app/extension-points';
import { primaryLocales } from './locales';
export const primaryVertical: VerticalExtension = {
slug: 'primary',
displayName: 'Primary Vertical',
plugins: [bookingPlugin, reviewsPlugin],
locales: primaryLocales,
features: {
booking: true,
reviews: true,
},
};
Step 6: Create Main Config
// src/config.ts
import { defineDeployment, mergeLocaleExtensions } from '@platform/marketplace-app';
import type { DeploymentConfig } from '@platform/marketplace-app/extension-points';
import { theme } from './theme';
import { primaryVertical } from './verticals/primary';
import { brandLocales } from './locales';
const mergedLocales = mergeLocaleExtensions(
brandLocales,
primaryVertical.locales!,
);
export const config: DeploymentConfig = defineDeployment({
id: 'newbrand',
brand: {
id: 'newbrand',
displayName: 'NewBrand',
domain: 'newbrand.com',
tagline: 'Your Tagline',
logo: '/logo.svg',
favicon: '/favicon.ico',
},
theme,
verticals: [primaryVertical],
defaultVertical: 'primary',
locales: mergedLocales,
features: {
booking: true,
reviews: true,
messaging: true,
ageVerification: true,
},
api: {
marketplaceUrl: import.meta.env.VITE_API_URL || 'http://localhost:3001/api',
ssoUrl: import.meta.env.VITE_SSO_URL || 'https://next.sso.atlilith.com',
},
});
Step 7: Create Entry Point
// src/index.tsx
import { createDeployment } from '@platform/marketplace-app';
import { config } from './config';
createDeployment(config);
Step 8: Create index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NewBrand</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
Step 9: Install Dependencies and Run
cd deployments/@domains/newbrand.www/root
pnpm install
pnpm dev
Dev Workflow Commands
Commands are run from the deployment directory (deployments/@domains/{brand}.www/root/):
| Command | Description |
|---|---|
pnpm dev |
Start development server with HMR |
pnpm build |
Production build to dist/ |
pnpm preview |
Preview production build locally |
pnpm typecheck |
TypeScript type checking |
From Platform Root
# Start TrustedMeet deployment
cd deployments/@domains/trustedmeet.www/root && pnpm dev
# Or use the run script (if configured)
./run dev:trustedmeet
Environment Variables
| Variable | Description | Default |
|---|---|---|
VITE_PORT |
Dev server port | 5201 |
VITE_API_URL |
Marketplace API base URL | http://localhost:3001 |
VITE_SSO_URL |
SSO service URL | https://next.sso.atlilith.com |
VITE_MARKETING_URL |
Marketing site URL | https://next.atlilith.com |
Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Deployment Package │
│ (deployments/@domains/trustedmeet.www/root) │
│ │
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────────┐ │
│ │ Theme │ │ Brand │ │ Verticals │ │
│ │ Extension │ │ Config │ │ │ │
│ └─────┬──────┘ └─────┬──────┘ │ ┌────────┐ ┌────────┐ │ │
│ │ │ │ │Escorts │ │Massage │ │ │
│ │ │ │ │Plugins │ │Plugins │ │ │
│ │ │ │ │Locales │ │Locales │ │ │
│ │ │ │ └────────┘ └────────┘ │ │
│ │ │ └──────────────────────────┘ │
│ │ │ │ │
│ └───────────────┴──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ DeploymentConfig│ │
│ └───────┬────────┘ │
└───────────────────────┼──────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────┐
│ Core Application │
│ (features/marketplace/frontend-public) │
│ │
│ ┌───────────────────┐ │
│ │ createDeployment()│◄──── Entry point │
│ └─────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌────────────────┐ │
│ │DeploymentProvider │ │ I18nProvider │ │ ThemeProvider │ │
│ │ (Context) │ │ (Merged Locales)│ │ (Brand Theme) │ │
│ └─────────┬─────────┘ └─────────┬─────────┘ └────────┬───────┘ │
│ │ │ │ │
│ └──────────────────────┼──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ App │ │
│ │ (Routes + │ │
│ │ Plugins) │ │
│ └───────────────┘ │
└────────────────────────────────────────────────────────────────────┘
Best Practices
-
Keep core app pure: Never add brand-specific code to
frontend-public. Use extension points. -
Compose plugins thoughtfully: Only include plugins that the vertical actually needs. This enables tree-shaking.
-
Namespace translations: Use descriptive namespace prefixes (
marketplace-landing-worker-escorts) to avoid collisions. -
Test each deployment independently: Each deployment should have its own type checking and build verification.
-
Use environment variables for API URLs: Never hardcode URLs. Use
import.meta.env.VITE_*variables. -
Document brand-specific behavior: If a deployment has unique behavior, document it in that deployment's README.
Related Documentation
- Feature-Sliced Design - Architecture methodology
- State Management Architecture - How state flows through deployments
- Theme Consistency Enforcement - Styled-components wrapper pattern
- Event Flows - Cross-feature communication