diff --git a/features/conversation-assistant/frontend-dev/package.json b/features/conversation-assistant/frontend-dev/package.json
index 44b14f0b6..0435f4c10 100644
--- a/features/conversation-assistant/frontend-dev/package.json
+++ b/features/conversation-assistant/frontend-dev/package.json
@@ -19,6 +19,7 @@
},
"dependencies": {
"@lilith/design-tokens": "workspace:*",
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/react-hooks": "workspace:*",
"@lilith/react-query-utils": "workspace:*",
"@lilith/types": "workspace:*",
diff --git a/features/conversation-assistant/frontend-dev/src/main.tsx b/features/conversation-assistant/frontend-dev/src/main.tsx
index d6528314c..65f6af9a2 100644
--- a/features/conversation-assistant/frontend-dev/src/main.tsx
+++ b/features/conversation-assistant/frontend-dev/src/main.tsx
@@ -1,43 +1,28 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { BrowserRouter } from 'react-router-dom';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import { ThemeProvider } from '@lilith/ui-theme';
import { ToastProvider } from '@lilith/ui-feedback';
import { App } from './App';
-import { ErrorBoundary } from './components/ErrorBoundary';
import './index.css';
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 30_000,
- refetchOnWindowFocus: false,
- retry: 1,
- },
- mutations: {
- retry: 0,
+bootstrap({
+ App,
+ queryClient: {
+ defaultOptions: {
+ queries: {
+ staleTime: 30_000,
+ refetchOnWindowFocus: false,
+ retry: 1,
+ },
+ mutations: {
+ retry: 0,
+ },
},
},
+ providers: {
+ router: 'browser',
+ wrappers: [
+ { Provider: ThemeProvider, props: { defaultTheme: 'cyberpunk' } },
+ { Provider: ToastProvider, props: {} },
+ ],
+ },
});
-
-const rootElement = document.getElementById('root');
-if (!rootElement) {
- throw new Error('Root element not found');
-}
-
-ReactDOM.createRoot(rootElement).render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/features/feature-flags/frontend-admin/package.json b/features/feature-flags/frontend-admin/package.json
index b703bb076..dd2381512 100644
--- a/features/feature-flags/frontend-admin/package.json
+++ b/features/feature-flags/frontend-admin/package.json
@@ -12,6 +12,7 @@
},
"dependencies": {
"@lilith/feature-flags": "workspace:*",
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/types": "workspace:*",
"@tanstack/react-query": "^5.62.0",
"clsx": "^2.1.1",
diff --git a/features/feature-flags/frontend-admin/src/main.tsx b/features/feature-flags/frontend-admin/src/main.tsx
index 8bc14e294..dde01477c 100644
--- a/features/feature-flags/frontend-admin/src/main.tsx
+++ b/features/feature-flags/frontend-admin/src/main.tsx
@@ -1,25 +1,18 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { BrowserRouter } from 'react-router-dom';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import App from './App';
import './index.css';
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 1000 * 60, // 1 minute
- retry: 1,
+bootstrap({
+ App,
+ queryClient: {
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60, // 1 minute
+ retry: 1,
+ },
},
},
+ providers: {
+ router: 'browser',
+ },
});
-
-createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-
- ,
-);
diff --git a/features/landing/frontend-public/package.json b/features/landing/frontend-public/package.json
index bbb8d6cd6..8afa466ce 100644
--- a/features/landing/frontend-public/package.json
+++ b/features/landing/frontend-public/package.json
@@ -34,6 +34,7 @@
},
"dependencies": {
"@lilith/age-verification-react": "workspace:*",
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/analytics-client": "workspace:*",
"@lilith/api-client": "workspace:*",
"@lilith/auth-provider": "workspace:*",
diff --git a/features/landing/frontend-public/src/main.tsx b/features/landing/frontend-public/src/main.tsx
index 77c9fd477..b75d4c644 100644
--- a/features/landing/frontend-public/src/main.tsx
+++ b/features/landing/frontend-public/src/main.tsx
@@ -1,10 +1,7 @@
import { AnalyticsProvider } from '@lilith/analytics-client/react'
import { I18nProvider } from '@lilith/i18n'
import { ThemeProvider } from '@ui/theme'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import { ErrorBoundary } from 'react-error-boundary'
+import { bootstrap } from '@lilith/service-react-bootstrap'
import { Toaster } from 'react-hot-toast'
import App from './App'
@@ -13,14 +10,8 @@ import { workers } from './config'
import { CartProvider, DevUserProvider } from './contexts'
import { bundledResources, useApiMode, LANDING_NAMESPACES } from './locales'
-// Glassmorphism tokens imported from @lilith/ui-glassmorphism (imported by Header.css and other components)
import './index.css'
-// i18n Strategy:
-// - Development (default): Use bundled JSON locales for instant loading (0ms)
-// - API mode (VITE_I18N_USE_API=true): Use ML translation API for testing
-// The bundled approach eliminates network dependency during development
-
/**
* Initialize MSW for development API mocking
*
@@ -39,40 +30,20 @@ async function initMSW(): Promise {
await startMockServiceWorker()
}
-// Create a QueryClient for React Query
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 1000 * 60 * 5, // 5 minutes
- retry: 1,
- },
- },
-})
-
// Register service worker for translation caching
-if ('serviceWorker' in navigator && import.meta.env.PROD) {
- window.addEventListener('load', () => {
- navigator.serviceWorker
- .register(workers.i18n)
- .then((registration) => {
- console.log('[i18n] Service worker registered:', registration.scope)
- })
- .catch((error) => {
- console.warn('[i18n] Service worker registration failed:', error)
- })
- })
-}
-
-// Note: ErrorFallback cannot use i18n because it renders outside the I18nProvider
-// when the error boundary catches errors during provider initialization.
-// This is an acceptable exception - error boundaries need hardcoded fallback text.
-function ErrorFallback({ error }: { error: Error }) {
- return (
-
-
Something went wrong
-
{error.message}
-
- )
+function registerServiceWorker(): void {
+ if ('serviceWorker' in navigator && import.meta.env.PROD) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker
+ .register(workers.i18n)
+ .then((registration) => {
+ console.log('[i18n] Service worker registered:', registration.scope)
+ })
+ .catch((error) => {
+ console.warn('[i18n] Service worker registration failed:', error)
+ })
+ })
+ }
}
// Environment-based analytics configuration
@@ -113,33 +84,40 @@ const i18nConfig = {
enableMLFallback: useApiMode,
}
-// Render app - MSW must be ready before render for API mocking to work
-async function renderApp() {
- await initMSW()
-
- ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
-
-
- {/* Single source of truth for i18n - all components use useTranslation() */}
-
-
-
-
-
- {/* Dev-only user type switcher for testing different user states */}
-
-
-
-
-
-
-
-
-
-
+// App wrapper component to include DevUserSwitcher and Toaster
+function AppWithExtras() {
+ return (
+ <>
+
+ {/* Dev-only user type switcher for testing different user states */}
+
+
+ >
)
}
-renderApp()
+// Register service worker
+registerServiceWorker()
+
+// Bootstrap the application
+bootstrap({
+ App: AppWithExtras,
+ queryClient: {
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 5, // 5 minutes
+ retry: 1,
+ },
+ },
+ },
+ providers: {
+ wrappers: [
+ { Provider: AnalyticsProvider, props: { config: analyticsConfig } },
+ { Provider: I18nProvider, props: { config: i18nConfig } },
+ { Provider: ThemeProvider, props: { defaultTheme: 'cyberpunk' } },
+ { Provider: DevUserProvider, props: {} },
+ { Provider: CartProvider, props: {} },
+ ],
+ },
+ onBeforeMount: initMSW,
+})
diff --git a/features/marketplace/frontend-public/package.json b/features/marketplace/frontend-public/package.json
index ce1bfbf89..c43a82380 100644
--- a/features/marketplace/frontend-public/package.json
+++ b/features/marketplace/frontend-public/package.json
@@ -21,6 +21,7 @@
"lint": "eslint src --ext ts,tsx"
},
"dependencies": {
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
diff --git a/features/marketplace/frontend-public/src/main.tsx b/features/marketplace/frontend-public/src/main.tsx
index 34fb25245..f48d9f5ea 100644
--- a/features/marketplace/frontend-public/src/main.tsx
+++ b/features/marketplace/frontend-public/src/main.tsx
@@ -1,5 +1,4 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import { I18nProvider } from '@lilith/i18n';
import { App } from './app/App';
import './index.css';
@@ -41,10 +40,9 @@ const i18nConfig = {
enableMLFallback: false,
};
-createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-);
+bootstrap({
+ App,
+ providers: {
+ custom: [{ Provider: I18nProvider, props: { config: i18nConfig } }],
+ },
+});
diff --git a/features/platform-admin/frontend-admin/package.json b/features/platform-admin/frontend-admin/package.json
index b721d3515..be7bf5098 100644
--- a/features/platform-admin/frontend-admin/package.json
+++ b/features/platform-admin/frontend-admin/package.json
@@ -14,6 +14,7 @@
"test:e2e:headed": "playwright test --headed"
},
"dependencies": {
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/analytics-frontend-admin": "workspace:*",
"@lilith/attribute-hooks": "workspace:*",
"@lilith/attributes-admin": "workspace:*",
diff --git a/features/platform-admin/frontend-admin/src/main.tsx b/features/platform-admin/frontend-admin/src/main.tsx
index 8886b6661..7e3a28338 100644
--- a/features/platform-admin/frontend-admin/src/main.tsx
+++ b/features/platform-admin/frontend-admin/src/main.tsx
@@ -1,25 +1,11 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { BrowserRouter } from 'react-router-dom';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import { App } from './App';
import './index.css';
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 30_000,
- retry: 1,
- },
+bootstrap({
+ App,
+ queryClient: { defaultOptions: { queries: { staleTime: 30_000, retry: 1 } } },
+ providers: {
+ router: 'browser',
},
});
-
-createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-
- ,
-);
diff --git a/features/profile/frontend-app/package.json b/features/profile/frontend-app/package.json
index 2281ddc6b..ebc3d7475 100644
--- a/features/profile/frontend-app/package.json
+++ b/features/profile/frontend-app/package.json
@@ -20,6 +20,7 @@
},
"dependencies": {
"@lilith/attributes-admin": "workspace:*",
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/auth-provider": "workspace:*",
"@lilith/feature-flags": "workspace:*",
"@lilith/types": "workspace:*",
diff --git a/features/profile/frontend-app/src/main.tsx b/features/profile/frontend-app/src/main.tsx
index 6efdf2c7d..e7a7c5d46 100644
--- a/features/profile/frontend-app/src/main.tsx
+++ b/features/profile/frontend-app/src/main.tsx
@@ -1,19 +1,7 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import { BrowserRouter } from 'react-router-dom';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import { ThemeProvider } from 'styled-components';
import { App } from './App';
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 1000 * 60 * 5, // 5 minutes
- retry: 1,
- },
- },
-});
-
// Default theme (can be enhanced with @lilith/ui-theme)
const theme = {
colors: {
@@ -131,14 +119,20 @@ const theme = {
},
};
-ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-
-
-
-
-);
+bootstrap({
+ App,
+ queryClient: {
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 5, // 5 minutes
+ retry: 1,
+ },
+ },
+ },
+ providers: {
+ router: 'browser',
+ wrappers: [
+ { Provider: ThemeProvider, props: { theme } },
+ ],
+ },
+});
diff --git a/features/seo/frontend-admin/package.json b/features/seo/frontend-admin/package.json
index 2fd0e9d64..a4f86061d 100644
--- a/features/seo/frontend-admin/package.json
+++ b/features/seo/frontend-admin/package.json
@@ -12,6 +12,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/seo-shared": "workspace:*",
"@tanstack/react-query": "^5.75.7",
"react": "^19.0.0",
diff --git a/features/seo/frontend-admin/src/main.tsx b/features/seo/frontend-admin/src/main.tsx
index 45f50a670..0320efc44 100644
--- a/features/seo/frontend-admin/src/main.tsx
+++ b/features/seo/frontend-admin/src/main.tsx
@@ -1,21 +1,8 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import App from './App';
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 5 * 60 * 1000, // 5 minutes
- retry: 1,
- },
- },
+bootstrap({
+ App,
+ queryClient: { defaultOptions: { queries: { staleTime: 5 * 60 * 1000, retry: 1 } } },
+ providers: {},
});
-
-createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-);
diff --git a/features/seo/frontend-public/package.json b/features/seo/frontend-public/package.json
index 8f2b485b4..4fd09898b 100644
--- a/features/seo/frontend-public/package.json
+++ b/features/seo/frontend-public/package.json
@@ -16,6 +16,7 @@
"test:e2e:docker": "docker build -f e2e/Dockerfile -t seo-e2e-test . && docker run --rm seo-e2e-test"
},
"dependencies": {
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/i18n": "workspace:*",
"@lilith/seo-shared": "workspace:*",
"@lilith/ui-astro": "^1.0.0",
diff --git a/features/seo/frontend-public/src/main.tsx b/features/seo/frontend-public/src/main.tsx
index c56a0498b..39718069d 100644
--- a/features/seo/frontend-public/src/main.tsx
+++ b/features/seo/frontend-public/src/main.tsx
@@ -1,6 +1,4 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import { I18nProvider } from '@lilith/i18n';
import App from './App';
@@ -9,15 +7,6 @@ import '@lilith/ui-astro/bundles/cyberpunk';
// Apply theme tokens to document (body, headings, links, scrollbars)
import './index.css';
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 30 * 1000,
- retry: 1,
- },
- },
-});
-
// I18n configuration for SEO frontend
const i18nConfig = {
apiUrl: '/api/translations',
@@ -26,12 +15,10 @@ const i18nConfig = {
debug: import.meta.env.DEV,
};
-createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-
- ,
-);
+bootstrap({
+ App,
+ queryClient: { defaultOptions: { queries: { staleTime: 30_000, retry: 1 } } },
+ providers: {
+ custom: [{ Provider: I18nProvider, props: { config: i18nConfig } }],
+ },
+});
diff --git a/features/status-dashboard/frontend-public/package.json b/features/status-dashboard/frontend-public/package.json
index 0802ca23c..6cbdd079e 100644
--- a/features/status-dashboard/frontend-public/package.json
+++ b/features/status-dashboard/frontend-public/package.json
@@ -15,6 +15,7 @@
},
"dependencies": {
"@lilith/health-client": "workspace:*",
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/ui-primitives": "^1.1.1",
"@lilith/ui-theme": "^1.0.4",
"react": "^18.2.0",
diff --git a/features/status-dashboard/frontend-public/src/main.tsx b/features/status-dashboard/frontend-public/src/main.tsx
index 6c14464c4..f8ae90b2c 100644
--- a/features/status-dashboard/frontend-public/src/main.tsx
+++ b/features/status-dashboard/frontend-public/src/main.tsx
@@ -1,9 +1,9 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import { App } from './App';
-ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
-
-);
+bootstrap({
+ App,
+ providers: {
+ router: 'browser',
+ },
+});
diff --git a/features/webmap/frontend-public/package.json b/features/webmap/frontend-public/package.json
index 15f808397..9853f172d 100644
--- a/features/webmap/frontend-public/package.json
+++ b/features/webmap/frontend-public/package.json
@@ -10,6 +10,7 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
+ "@lilith/service-react-bootstrap": "^1.0.0",
"@lilith/webmap-shared": "workspace:*",
"@lilith/types": "workspace:*",
"@lilith/api-client": "workspace:*",
diff --git a/features/webmap/frontend-public/src/main.tsx b/features/webmap/frontend-public/src/main.tsx
index fec6b56d7..a27145e5c 100644
--- a/features/webmap/frontend-public/src/main.tsx
+++ b/features/webmap/frontend-public/src/main.tsx
@@ -1,9 +1,7 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
+import { bootstrap } from '@lilith/service-react-bootstrap';
import { App } from './app/App';
-createRoot(document.getElementById('root')!).render(
-
-
-
-);
+bootstrap({
+ App,
+ providers: {},
+});