Theme Registry
Overview
The Theme Registry (core/lib/registries/theme-registry.ts) provides ultra-fast, zero-I/O access to theme configurations, assets, and metadata. It's auto-generated by scripts/build-registry.mjs and enables instant theme switching without runtime filesystem access.
Key Features:
- ✅ Zero runtime I/O for theme configuration access
- ✅ Active theme selection via environment variables
- ✅ Theme asset and component discovery
- ✅ Integration with entities, plugins, and routes
- ✅ Full TypeScript type safety
Architecture
Build-Time Generation
// Generated at build time by scripts/build-registry.mjs
export const THEME_REGISTRY: Record<string, ThemeRegistryEntry> = {
'default': {
name: 'default',
config: boilerplateThemeConfig, // Pre-imported static config
hasComponents: false,
hasStyles: true,
hasAssets: false,
hasMessages: true,
hasDashboardConfig: true,
dashboardConfig: defaultDashboardConfig,
hasAppConfig: true,
appConfig: defaultAppConfig,
componentsPath: null,
stylesPath: '@/contents/themes/default/styles',
assetsPath: null,
messagesPath: '@/contents/themes/default/messages',
entities: [...], // Theme-specific entities
routeFiles: [...], // Theme-specific API routes
plugins: ['ai'] // Plugins used by this theme
}
}
Runtime Access (Zero I/O)
// Ultra-fast object property access
import { THEME_REGISTRY, getTheme } from '@/core/lib/registries/theme-registry'
// Direct access (instant)
const defaultTheme = THEME_REGISTRY.default
// Helper function (instant)
const theme = getTheme('default')
Theme Discovery
Automatic Discovery Process
Build script (scripts/build-registry.mjs) automatically discovers:
- Theme Directory Structure:
contents/themes/
└── default/ # Theme name
├── theme.config.ts # Main theme config (required)
├── dashboard.config.ts # Dashboard config (optional)
├── app.config.ts # App config overrides (optional)
├── styles/ # Theme styles (optional)
├── components/ # Theme components (optional)
├── assets/ # Theme assets (optional)
├── messages/ # i18n translations (optional)
│ ├── en.json
│ └── es.json
├── entities/ # Theme entities (optional)
│ └── tasks/
│ ├── tasks.config.ts
│ ├── tasks.fields.ts
│ └── migrations/
└── templates/ # Theme templates (optional)
└── (public)/
└── layout.tsx
- Discovery Logic:
// From scripts/build-registry.mjs
async function discoverThemes() {
const themesDir = join(contentsDir, 'themes')
const themes = []
for (const themeDir of await readdir(themesDir)) {
const themePath = join(themesDir, themeDir)
// Find theme.config.ts
const configFile = join(themePath, 'theme.config.ts')
if (!existsSync(configFile)) continue
// Discover theme resources
const themeEntry = {
name: themeDir,
hasComponents: existsSync(join(themePath, 'components')),
hasStyles: existsSync(join(themePath, 'styles')),
hasAssets: existsSync(join(themePath, 'assets')),
hasMessages: existsSync(join(themePath, 'messages')),
hasDashboardConfig: existsSync(join(themePath, 'dashboard.config.ts')),
hasAppConfig: existsSync(join(themePath, 'app.config.ts')),
entities: await discoverThemeEntities(themePath),
routeFiles: await discoverThemeRoutes(themePath),
plugins: await extractUsedPlugins(configFile)
}
themes.push(themeEntry)
}
return themes
}
Active Theme Selection
Environment Variable Control
Active theme is determined by NEXT_PUBLIC_ACTIVE_THEME:
# .env.local
NEXT_PUBLIC_ACTIVE_THEME=default
Usage in Application
// Get active theme at runtime
const activeThemeName = process.env.NEXT_PUBLIC_ACTIVE_THEME || 'default'
const activeTheme = THEME_REGISTRY[activeThemeName]
// Access theme configuration
const themeConfig = activeTheme.config
const dashboardConfig = activeTheme.dashboardConfig
const appConfig = activeTheme.appConfig
Helper Functions
import {
getTheme,
getThemeDashboardConfig,
getThemeAppConfig
} from '@/core/lib/registries/theme-registry'
// Get theme configuration
const theme = getTheme('default')
// Get dashboard configuration
const dashboard = getThemeDashboardConfig('default')
// Get app configuration overrides
const appOverrides = getThemeAppConfig('default')
Theme Registry Entry Structure
ThemeRegistryEntry Interface
export interface ThemeRegistryEntry {
name: string // Theme identifier
config: ThemeConfig // Main theme configuration
hasComponents: boolean // Has components/ directory
hasStyles: boolean // Has styles/ directory
hasAssets: boolean // Has assets/ directory
hasMessages: boolean // Has messages/ directory
hasDashboardConfig: boolean // Has dashboard.config.ts
dashboardConfig: any | null // Dashboard configuration
hasAppConfig: boolean // Has app.config.ts
appConfig: any | null // App configuration overrides
componentsPath: string | null // Path to components
stylesPath: string | null // Path to styles
assetsPath: string | null // Path to assets
messagesPath: string | null // Path to messages
entities: ThemeEntity[] // Theme-specific entities
routeFiles: ThemeRouteFile[] // Theme-specific API routes
plugins: string[] // Plugin dependencies
}
ThemeEntity Structure
export interface ThemeEntity {
name: string // Entity slug
exportName: string // Config export name
configPath: string // Import path to config
actualConfigFile: string // Actual filename
relativePath: string // Relative path within theme
depth: number // Hierarchy depth
parent: string | null // Parent entity (if child)
children: string[] // Child entities
hasComponents: boolean // Has components/
hasHooks: boolean // Has hooks/
hasMigrations: boolean // Has migrations/
hasMessages: boolean // Has messages/
hasAssets: boolean // Has assets/
messagesPath: string // Path to i18n messages
pluginContext: null // Always null for theme entities
themeContext: { // Theme context
themeName: string
}
source: 'theme' // Source identifier
}
ThemeRouteFile Structure
export interface ThemeRouteFile {
path: string // API route path
filePath: string // Actual file path
relativePath: string // Relative to theme
methods: string[] // HTTP methods (GET, POST, etc.)
isRouteFile: boolean // True if valid route file
theme: string // Theme name
}
Theme Configuration
theme.config.ts Structure
Required file that defines theme metadata:
// contents/themes/default/theme.config.ts
import type { ThemeConfig } from '@/core/types/theme'
export const boilerplateThemeConfig: ThemeConfig = {
name: 'SaaS Boilerplate',
slug: 'default',
version: '1.0.0',
author: 'Your Name',
description: 'Modern SaaS boilerplate with Next.js',
// Feature flags
features: {
multiLanguage: true,
darkMode: true,
authentication: true,
billing: false
},
// Color scheme
colors: {
primary: '#3b82f6',
secondary: '#8b5cf6',
accent: '#f59e0b'
},
// Layout configuration
layout: {
sidebar: 'left',
header: true,
footer: true
}
}
dashboard.config.ts (Optional)
Dashboard-specific configuration:
// contents/themes/default/dashboard.config.ts
export const DASHBOARD_CONFIG = {
navigation: [
{ label: 'Home', href: '/dashboard', icon: 'Home' },
{ label: 'Tasks', href: '/dashboard/tasks', icon: 'CheckSquare' },
{ label: 'Settings', href: '/dashboard/settings', icon: 'Settings' }
],
widgets: [
{ id: 'stats', enabled: true },
{ id: 'recent-tasks', enabled: true },
{ id: 'activity-feed', enabled: false }
],
defaultView: 'grid'
}
app.config.ts (Optional)
App-level configuration overrides:
// contents/themes/default/app.config.ts
export const APP_CONFIG_OVERRIDES = {
app: {
name: 'My Custom SaaS',
url: 'https://mysaas.com'
},
features: {
newsletter: true,
blog: false
},
seo: {
titleTemplate: '%s | My SaaS',
defaultTitle: 'My Custom SaaS'
}
}
Accessing Theme Resources
Theme Entities
import { THEME_REGISTRY } from '@/core/lib/registries/theme-registry'
// Get all entities from active theme
const activeTheme = THEME_REGISTRY.default
const themeEntities = activeTheme.entities
// Filter entities by criteria
const migratedEntities = themeEntities.filter(e => e.hasMigrations)
const rootEntities = themeEntities.filter(e => e.depth === 0)
Theme Routes
import { THEME_REGISTRY } from '@/core/lib/registries/theme-registry'
// Get all API routes from theme
const themeRoutes = THEME_REGISTRY.default.routeFiles
// Find specific route
const taskRoute = themeRoutes.find(r => r.path.includes('/tasks'))
Theme Translations
import { THEME_TRANSLATION_LOADERS } from '@/core/lib/registries/translation-registry'
// Theme translations are in separate registry
const enLoader = THEME_TRANSLATION_LOADERS.default.en
const translations = await enLoader()
Theme Helper Functions
getRegisteredThemes()
Get all registered themes (zero I/O):
import { getRegisteredThemes } from '@/core/lib/registries/theme-registry'
// Returns ThemeConfig[] for all themes
const allThemes = getRegisteredThemes()
// Use in theme selector
export function ThemeSelector() {
const themes = getRegisteredThemes()
return (
<select>
{themes.map(theme => (
<option key={theme.slug} value={theme.slug}>
{theme.name}
</option>
))}
</select>
)
}
getTheme(name)
Get specific theme by name:
import { getTheme } from '@/core/lib/registries/theme-registry'
const defaultTheme = getTheme('default')
const customTheme = getTheme('my-custom-theme')
// Returns undefined if theme not found
if (!customTheme) {
console.warn('Custom theme not available')
}
getThemeDashboardConfig(name)
Get dashboard configuration for a theme:
import { getThemeDashboardConfig } from '@/core/lib/registries/theme-registry'
const dashboardNav = getThemeDashboardConfig('default')
// Use in dashboard layout
export function DashboardLayout() {
const config = getThemeDashboardConfig('default')
return (
<aside>
{config?.navigation.map(item => (
<NavItem key={item.href} {...item} />
))}
</aside>
)
}
getThemeAppConfig(name)
Get app configuration overrides for a theme:
import { getThemeAppConfig } from '@/core/lib/registries/theme-registry'
const appOverrides = getThemeAppConfig('default')
// Merge with base config
const finalConfig = {
...baseAppConfig,
...appOverrides
}
getThemesWithEntities()
Get themes that have entities:
import { getThemesWithEntities } from '@/core/lib/registries/theme-registry'
const themesWithEntities = getThemesWithEntities()
// Each entry includes full theme metadata + entities
themesWithEntities.forEach(theme => {
console.log(`${theme.name} has ${theme.entities.length} entities`)
})
getThemesWithRoutes()
Get themes that have API routes:
import { getThemesWithRoutes } from '@/core/lib/registries/theme-registry'
const themesWithRoutes = getThemesWithRoutes()
// List all theme-provided routes
themesWithRoutes.forEach(theme => {
theme.routeFiles.forEach(route => {
console.log(`${route.path} [${route.methods.join(', ')}]`)
})
})
getThemesUsingPlugin(pluginName)
Get themes that use a specific plugin:
import { getThemesUsingPlugin } from '@/core/lib/registries/theme-registry'
const aiThemes = getThemesUsingPlugin('ai')
// Check if current theme uses AI plugin
const currentTheme = THEME_REGISTRY.default
const usesAI = currentTheme.plugins.includes('ai')
getPluginUsage(pluginName)
Get plugin usage statistics across themes:
import { getPluginUsage } from '@/core/lib/registries/theme-registry'
const aiUsage = getPluginUsage('ai')
// Returns: { theme: string, entities: number, routes: number }[]
aiUsage.forEach(usage => {
console.log(`${usage.theme}: ${usage.entities} entities, ${usage.routes} routes`)
})
Theme Metadata
Registry Metadata
Global theme statistics:
export const THEME_METADATA = {
totalThemes: 1,
themesWithComponents: 0,
themesWithStyles: 1,
themesWithAssets: 0,
themesWithMessages: 1,
themesWithDashboardConfig: 1,
themesWithEntities: 1,
themesWithRoutes: 0,
themesUsingPlugins: 1,
totalThemeEntities: 1,
totalThemeRoutes: 0,
generatedAt: '2025-11-20T02:53:31.032Z',
themes: ['default']
}
Usage in Application
import { THEME_METADATA } from '@/core/lib/registries/theme-registry'
// Display theme statistics
console.log(`Total themes: ${THEME_METADATA.totalThemes}`)
console.log(`Themes with entities: ${THEME_METADATA.themesWithEntities}`)
console.log(`Total entities across all themes: ${THEME_METADATA.totalThemeEntities}`)
// Check generation timestamp
const generatedDate = new Date(THEME_METADATA.generatedAt)
console.log(`Registry generated: ${generatedDate.toLocaleString()}`)
Integration with Build Script
build-theme.mjs
The scripts/build-theme.mjs script works in conjunction with the registry system:
// scripts/build-theme.mjs handles:
// - CSS compilation
// - Asset optimization
// - Component bundling
// scripts/build-registry.mjs handles:
// - Theme discovery
// - Configuration imports
// - Registry generation
// They work together but serve different purposes
Build Order
# Development
pnpm dev
↓
1. build-registry.mjs (discovers themes)
↓
2. build-theme.mjs (compiles active theme)
↓
3. Next.js dev server starts
# Production
pnpm build
↓
1. build-registry.mjs --build
↓
2. build-theme.mjs --build
↓
3. Next.js build
Real-World Examples
Example 1: Theme Switcher Component
'use client'
import { getRegisteredThemes } from '@/core/lib/registries/theme-registry'
import { useRouter } from 'next/navigation'
export function ThemeSwitcher() {
const themes = getRegisteredThemes()
const router = useRouter()
const handleThemeChange = async (themeSlug: string) => {
// Update environment variable (requires server action)
await fetch('/api/settings/theme', {
method: 'POST',
body: JSON.stringify({ theme: themeSlug })
})
// Refresh to apply new theme
router.refresh()
}
return (
<select onChange={(e) => handleThemeChange(e.target.value)}>
{themes.map(theme => (
<option key={theme.slug} value={theme.slug}>
{theme.name} v{theme.version}
</option>
))}
</select>
)
}
Example 2: Dashboard Navigation
import { getThemeDashboardConfig } from '@/core/lib/registries/theme-registry'
export function DashboardSidebar() {
const config = getThemeDashboardConfig('default')
if (!config) {
return <div>No dashboard configuration</div>
}
return (
<nav>
{config.navigation.map(item => (
<a key={item.href} href={item.href}>
<Icon name={item.icon} />
<span>{item.label}</span>
</a>
))}
</nav>
)
}
Example 3: Theme Feature Detection
import { getTheme } from '@/core/lib/registries/theme-registry'
export function FeatureGate({ feature, children }: Props) {
const theme = getTheme('default')
const isEnabled = theme?.features?.[feature] ?? false
if (!isEnabled) {
return null
}
return <>{children}</>
}
// Usage
<FeatureGate feature="darkMode">
<DarkModeToggle />
</FeatureGate>
Performance Characteristics
Zero I/O Access
// Before (Runtime I/O): ~40ms
const theme = await findTheme('default')
const config = await loadThemeConfig(theme.path)
// After (Registry): <1ms
const theme = THEME_REGISTRY.default
const config = theme.config
Constant Time Lookups
// O(1) complexity regardless of theme count
const theme1 = THEME_REGISTRY.default // <1ms
const theme2 = THEME_REGISTRY['custom-theme'] // <1ms
const theme3 = THEME_REGISTRY['another-theme'] // <1ms
Best Practices
✅ DO: Use Registry Functions
import { getTheme, getThemeDashboardConfig } from '@/core/lib/registries/theme-registry'
const theme = getTheme('default')
const dashboard = getThemeDashboardConfig('default')
✅ DO: Check for Undefined
const theme = getTheme(themeName)
if (!theme) {
console.warn(`Theme '${themeName}' not found`)
return defaultTheme
}
✅ DO: Access Directly for Active Theme
// When you know the active theme
const activeTheme = THEME_REGISTRY.default
❌ DON'T: Import from contents/
// ❌ FORBIDDEN
import { boilerplateThemeConfig } from '@/contents/themes/default/theme.config'
// ✅ CORRECT
import { THEME_REGISTRY } from '@/core/lib/registries/theme-registry'
const config = THEME_REGISTRY.default.config
❌ DON'T: Use Dynamic Imports
// ❌ FORBIDDEN
const theme = await import(`@/contents/themes/${themeName}/theme.config`)
// ✅ CORRECT
const theme = getTheme(themeName)
Troubleshooting
Theme Not Found
Problem: getTheme('my-theme') returns undefined
Solutions:
- Check theme exists in
contents/themes/my-theme/ - Verify
theme.config.tsfile exists - Run
pnpm registry:buildto regenerate - Restart dev server
Theme Changes Not Reflected
Problem: Updated theme config but changes don't appear
Solutions:
- Run
pnpm registry:buildto regenerate registry - Restart dev server (registries can't hot reload)
- Check browser console for errors
- Verify active theme in
.env.local
Missing Dashboard Config
Problem: getThemeDashboardConfig() returns null
Solutions:
- Check if
dashboard.config.tsexists in theme - Verify export name matches expected pattern
- Regenerate registry:
pnpm registry:build
Type Safety
TypeScript Types
import type {
ThemeRegistryEntry,
ThemeEntity,
ThemeRouteFile,
ThemeName
} from '@/core/lib/registries/theme-registry'
// Strongly typed theme access
const theme: ThemeRegistryEntry = THEME_REGISTRY.default
// Type-safe theme name
const themeName: ThemeName = 'default' // Auto-complete available!
// Fully typed entities
const entities: ThemeEntity[] = theme.entities
Next Steps
- Plugin Registry - Plugin system integration
- Translation Registry - i18n integration
- Route Handlers Registry - API route handling
- Performance & Caching - Optimization strategies
Last Updated: 2025-11-20
Version: 1.0.0
Status: Complete
Auto-Generated: Yes (by scripts/build-registry.mjs)
Registry File: core/lib/registries/theme-registry.ts