Routing Architecture - Hybrid Strategy
How platform tools use static routes and user modules use dynamic routes
Routing Architecture
Last Updated: November 1, 2025
Status: Production Implementation
Version: 2.0 - Hybrid Strategy
Overview
GoGrab Space uses a hybrid routing strategy that combines static routes for platform tools with dynamic routes for user-created modules, supporting both building mode and deployed mode.
Building Mode (Platform Builders)
URL Pattern
/:spaceSlug/tool
Example Routes
/my-space/dashboard
/my-space/editor
/my-space/space-map
/my-space/singularity
/my-space/modularity
/my-space/launch-station
/my-space/ui-builder
Characteristics
- Static Definition: Routes defined in
apps/gograb-space/src/app/app.routes.ts - Space Context: Extracted from
:spaceSlugparameter - Navigation: Preserves space when switching tools
- Authentication: Protected by
authGuardandspaceGuard - Resolution: Pre-loads space data via
spaceResolver
Route Configuration
// apps/gograb-space/src/app/app.routes.ts
{
path: ':spaceSlug',
loadComponent: () => import('./core/layout/main-shell/main-shell.component')
.then(m => m.MainShellComponent),
canActivate: [authGuard, spaceGuard],
resolve: { space: spaceResolver },
children: [
{
path: 'dashboard',
loadComponent: () => import('./components/space-components/g-space-dashboard/g-space-dashboard.component')
.then(m => m.GSpaceDashboardComponent),
title: 'Dashboard'
},
{
path: 'editor',
children: EditorRoutes, // Static routes from @editor-web-lib
title: 'Editor'
},
{
path: 'space-map',
children: spaceMapRoutes, // Static routes from @space-map-lib
title: 'Space Map'
},
{
path: 'singularity',
children: SingularityRoutes, // Static routes from @singularity-lib
title: 'Singularity'
},
{
path: 'modularity',
children: ModularityRoutes, // Static routes from @modularity-lib
title: 'Modularity'
}
// Dynamic user module routes injected here at runtime
]
}
Deployed Mode (End Users)
URL Pattern
subdomain.gograb.space/route
Example Routes
myuniverse.gograb.space/
myuniverse.gograb.space/about
myuniverse.gograb.space/products
myuniverse.gograb.space/contact
myuniverse.gograb.space/blog
myuniverse.gograb.space/blog/post-1
Characteristics
- No Space Slug: Clean production URLs
- Subdomain Routing: Universe determined from subdomain
- Module Routes: Loaded from deployed module manifest
- Static Generation: Pre-rendered pages where possible
- API Routes: Backend routes for data operations
Subdomain Resolution
// Pseudocode
const subdomain = window.location.hostname.split('.')[0];
const universe = await loadUniverseBySubdomain(subdomain);
const routes = universe.deployedModules.flatMap(m => m.routes);
// Register routes dynamically
await registerDeployedRoutes(routes);
Dynamic User Module Routes
Registration Process
User Creates Module in Modularity
const module: ModuleManifest = { id: 'blog', version: '1.0.0', name: 'Blog Module', routes: [ { path: 'blog', loadComponent: () => import('./blog-list.component') }, { path: 'blog/:postId', loadComponent: () => import('./blog-detail.component') } ] };Module Deployed
await dynamicRouteService.registerModuleRoutes(module);Routes Available
/my-space/blog (building mode) myuniverse.gograb.space/blog (deployed mode)
Dynamic Route Service
@Injectable({ providedIn: 'root' })
export class DynamicRouteService {
async registerModuleRoutes(manifest: ModuleManifest): Promise<void> {
const routes: Routes = manifest.routes.map(route => ({
...route,
data: {
moduleId: manifest.id,
moduleVersion: manifest.version
}
}));
// Inject into router
this.router.config.push(...routes);
await this.router.navigateByUrl(this.router.url);
}
}
Platform Tool Routes (Static)
Editor
Base Path: /:spaceSlug/editor
// @editor-web-lib/src/lib/editor.routes.ts
export const EditorRoutes: Routes = [
{
path: '',
loadComponent: () => import('./components/editor-workspace/editor-workspace.component')
.then(m => m.EditorWorkspaceComponent)
},
{
path: 'page/:pageId',
loadComponent: () => import('./components/page-editor/page-editor.component')
.then(m => m.PageEditorComponent)
}
];
URLs:
/my-space/editor- Editor workspace/my-space/editor/page/page-1- Edit specific page
Space Map
Base Path: /:spaceSlug/space-map
// @space-map-lib/src/lib/space-map.routes.ts
export const spaceMapRoutes: Routes = [
{
path: '',
loadComponent: () => import('./components/space-overview/space-overview.component')
.then(m => m.SpaceOverviewComponent)
},
{
path: ':universeIdentifier',
loadComponent: () => import('./components/universe-detail/universe-detail.component')
.then(m => m.UniverseDetailComponent)
},
{
path: ':universeIdentifier/:galaxyId',
loadComponent: () => import('./components/galaxy-detail/galaxy-detail.component')
.then(m => m.GalaxyDetailComponent)
}
];
URLs:
/my-space/space-map- Space overview/my-space/space-map/universe-1- Universe detail/my-space/space-map/universe-1/galaxy-1- Galaxy detail
Singularity
Base Path: /:spaceSlug/singularity
// @singularity-lib/src/lib/singularity.routes.ts
export const SingularityRoutes: Routes = [
{
path: 'logic-builder',
loadComponent: () => import('./components/logic-builder/logic-builder.component')
.then(m => m.LogicBuilderComponent)
},
{
path: 'flow-composer',
loadComponent: () => import('./components/flow-composer/flow-composer.component')
.then(m => m.FlowComposerComponent)
}
];
URLs:
/my-space/singularity/logic-builder- Visual logic blocks/my-space/singularity/flow-composer- Flow orchestration
Modularity
Base Path: /:spaceSlug/modularity
// @modularity-lib/src/lib/modularity.routes.ts
export const ModularityRoutes: Routes = [
{
path: '',
loadComponent: () => import('./components/module-list/module-list.component')
.then(m => m.ModuleListComponent)
},
{
path: 'create',
loadComponent: () => import('./components/module-builder/module-builder.component')
.then(m => m.ModuleBuilderComponent)
},
{
path: ':moduleId',
loadComponent: () => import('./components/module-detail/module-detail.component')
.then(m => m.ModuleDetailComponent)
}
];
URLs:
/my-space/modularity- Module list/my-space/modularity/create- Create new module/my-space/modularity/blog- Module detail
Launch Station
Base Path: /:spaceSlug/launch-station
// @launch-station-lib/src/lib/launch-station.routes.ts
export const LaunchStationRoutes: Routes = [
{
path: '',
loadComponent: () => import('./components/deployment-wizard/deployment-wizard.component')
.then(m => m.DeploymentWizardComponent)
},
{
path: 'deployments',
loadComponent: () => import('./components/deployment-list/deployment-list.component')
.then(m => m.DeploymentListComponent)
}
];
URLs:
/my-space/launch-station- Deployment wizard/my-space/launch-station/deployments- Deployment history
Context Management
Space Context Service
@Injectable({ providedIn: 'root' })
export class SpaceContextService {
private currentSpace = signal<Space | null>(null);
private currentUniverse = signal<Universe | null>(null);
private currentTool = signal<string | null>(null);
// Automatically updated from router
updateFromRoute(params: Params): void {
const spaceSlug = params['spaceSlug'];
const universeId = params['universeIdentifier'];
const tool = this.detectToolFromUrl();
if (spaceSlug) {
this.loadSpace(spaceSlug);
}
if (universeId) {
this.loadUniverse(universeId);
}
if (tool) {
this.currentTool.set(tool);
}
}
}
Navigation Preservation
When navigating between tools, context is preserved:
// User at: /my-space/space-map/universe-1
// Clicks "Edit in Editor"
// Navigates to: /my-space/editor/page/[last-page-in-universe-1]
Subdomain Deployment
DNS Configuration
*.gograb.space → CNAME → deployment-server.gograb.space
Subdomain Router Service
@Injectable({ providedIn: 'root' })
export class SubdomainRouterService {
async initializeFromSubdomain(): Promise<void> {
const subdomain = this.getSubdomain();
if (this.isDeployedMode(subdomain)) {
// Load universe configuration
const universe = await this.loadUniverseBySubdomain(subdomain);
// Load and register module routes
const routes = await this.loadModuleRoutes(universe);
await this.registerDeployedRoutes(routes);
// Hide platform UI, show only user content
this.hideBuilderUI();
}
}
private isDeployedMode(subdomain: string): boolean {
// Not 'www', not 'app', not platform subdomains
return !['www', 'app', 'admin', 'api'].includes(subdomain);
}
}
Deployment Process
- User clicks "Deploy" in Launch Station
- Select subdomain:
myuniverse - System generates:
- Static pages for content
- API routes for data
- Module route configuration
- Deploy to CDN:
- HTML/CSS/JS assets
- Images and media
- Configuration files
- Update DNS:
- Create CNAME record
- Configure SSL certificate
- Universe live at:
myuniverse.gograb.space
Route Guards and Resolvers
Auth Guard
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
if (authService.isAuthenticated()) {
return true;
}
return inject(Router).createUrlTree(['/auth/login'], {
queryParams: { returnUrl: state.url }
});
};
Space Guard
export const spaceGuard: CanActivateFn = async (route, state) => {
const spaceSlug = route.params['spaceSlug'];
const spaceService = inject(SpaceService);
const spaceExists = await spaceService.verifySpaceAccess(spaceSlug);
if (!spaceExists) {
return inject(Router).createUrlTree(['/dashboard']);
}
return true;
};
Space Resolver
export const spaceResolver: ResolveFn<Space> = async (route, state) => {
const spaceSlug = route.params['spaceSlug'];
const spaceService = inject(SpaceService);
return await spaceService.getSpaceBySlug(spaceSlug);
};
Navigation Service
Unified Navigation
@Injectable({ providedIn: 'root' })
export class UnifiedNavigationService {
async navigateToTool(
tool: 'editor' | 'space-map' | 'singularity' | 'modularity' | 'launch-station',
options?: { universe?: string; galaxy?: string; page?: string }
): Promise<void> {
const currentSpace = this.spaceContext.currentSpace();
const path = [currentSpace.slug, tool];
if (options?.universe) path.push(options.universe);
if (options?.galaxy) path.push(options.galaxy);
if (options?.page) path.push('page', options.page);
await this.router.navigate(path);
}
}
Benefits of Hybrid Approach
1. Reliability
- ✅ Platform tools always work (static routes)
- ✅ No dynamic loading failures for core functionality
- ✅ Predictable navigation behavior
2. Flexibility
- ✅ Users can create custom modules
- ✅ Dynamic routes registered at runtime
- ✅ Extensible architecture
3. Performance
- ✅ Static routes = faster initial load
- ✅ Lazy loading for module routes
- ✅ Code splitting per tool
4. Maintainability
- ✅ Clear separation of concerns
- ✅ Easy to debug static routes
- ✅ Dynamic routes isolated
5. Production-Ready
- ✅ Clean URLs for deployed universes
- ✅ SEO-friendly routes
- ✅ Subdomain isolation
Related Documentation
- Module System Architecture - Why BrowserModuleContract
- Phase 0 Cleanup - Current implementation
- Deployment Guide - Subdomain deployment
Last Updated: November 1, 2025
Status: Production Implementation
Version: 2.0 - Hybrid Strategy