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

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

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

  1. 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')
        }
      ]
    };
    
  2. Module Deployed

    await dynamicRouteService.registerModuleRoutes(module);
    
  3. 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:

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:

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:

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:

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:


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

  1. User clicks "Deploy" in Launch Station
  2. Select subdomain: myuniverse
  3. System generates:
    • Static pages for content
    • API routes for data
    • Module route configuration
  4. Deploy to CDN:
    • HTML/CSS/JS assets
    • Images and media
    • Configuration files
  5. Update DNS:
    • Create CNAME record
    • Configure SSL certificate
  6. 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

2. Flexibility

3. Performance

4. Maintainability

5. Production-Ready


Related Documentation


Last Updated: November 1, 2025
Status: Production Implementation
Version: 2.0 - Hybrid Strategy