> For the complete documentation index, see [llms.txt](https://engine.sygnal.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://engine.sygnal.com/usage/source-structure-and-key-files/route-dispatcher.md).

# Route Dispatcher

The Route Dispatcher is the core routing system in SSE that maps URL paths to Page classes and manages the execution lifecycle.

## Overview

The RouteDispatcher handles:

* Matching URL paths to Page classes
* Managing Site-level code execution
* Coordinating the setup/execution lifecycle
* Supporting wildcard routes for CMS collections

## Basic Usage (v2.0+)

### Creating the Dispatcher

In `src/routes.ts`, create and configure your RouteDispatcher:

```typescript
import { RouteDispatcher, getAllPages } from "@sygnal/sse-core";
import { Site } from "./site";

// Import pages to trigger @page decorator registration
import "./pages/home";
import "./pages/about";
import "./pages/blog";

export const routeDispatcher = (): RouteDispatcher => {
    const dispatcher = new RouteDispatcher(Site);
    dispatcher.routes = getAllPages();  // Auto-populated from @page decorators
    return dispatcher;
}
```

### Using the Dispatcher

In `src/index.ts`, create the dispatcher **once** and reuse it:

```typescript
import { routeDispatcher } from './routes';

// Create dispatcher once
const dispatcher = routeDispatcher();

// Setup phase (runs in <head>)
dispatcher.setupRoute();

// Execution phase (runs after DOM ready)
window.Webflow ||= [];
window.Webflow.push(() => {
  dispatcher.execRoute();
});
```

{% hint style="danger" %}
**Critical:** The dispatcher must be created once and reused. Creating multiple instances causes data loss between setup and execution phases.
{% endhint %}

## Route Mapping

### Automatic Route Discovery (Recommended)

**New in v2.0:** Use the `@page` decorator for automatic route registration:

```typescript
import { PageBase, page } from '@sygnal/sse-core';

@page('/')
export class HomePage extends PageBase {
  protected async onLoad(): Promise<void> {
    console.log('This is the homepage.');
  }
}

@page('/about')
export class AboutPage extends PageBase {
  protected async onLoad(): Promise<void> {
    console.log('This is the about page.');
  }
}

@page('/blog/*')
export class BlogPage extends PageBase {
  protected async onLoad(): Promise<void> {
    console.log('This is a blog post.');
    console.log('Item slug:', this.pageInfo.itemSlug);
  }
}
```

Then import these pages in `routes.ts` to register them automatically.

### Manual Route Registration (Legacy)

You can still manually define routes if needed:

```typescript
import { RouteDispatcher } from "@sygnal/sse-core";
import { Site } from "./site";
import { HomePage } from "./pages/home";
import { AboutPage } from "./pages/about";
import { BlogPage } from "./pages/blog";

export const routeDispatcher = (): RouteDispatcher => {
    const dispatcher = new RouteDispatcher(Site);
    dispatcher.routes = {
        '/': HomePage,
        '/about': AboutPage,
        '/blog/*': BlogPage,
    };
    return dispatcher;
}
```

## Wildcard Routes

Wildcard paths use a trailing `/*` to match dynamic segments:

```typescript
@page('/blog/*')
export class BlogPage extends PageBase {
  protected async onLoad(): Promise<void> {
    // Matches: /blog/post-1, /blog/post-2, /blog/any-slug
    console.log('Current slug:', this.pageInfo.itemSlug);
  }
}

@page('/products/*')
export class ProductPage extends PageBase {
  protected async onLoad(): Promise<void> {
    // Matches: /products/item-a, /products/item-b
    console.log('Product slug:', this.pageInfo.itemSlug);
  }
}
```

**Wildcard Behavior:**

* `/blog/*` matches `/blog/post-1` but NOT `/blog` itself
* To match both, use two decorators:

  ```typescript
  @page('/blog')
  @page('/blog/*')
  export class BlogPage extends PageBase { }
  ```

## Execution Lifecycle

The RouteDispatcher manages a two-phase lifecycle:

### Phase 1: Setup (Synchronous)

Runs during `<head>` load via `dispatcher.setupRoute()`:

1. Site's `setup()` method executes
2. Matched Page's `onPrepare()` method executes
3. Matched Components' `onPrepare()` methods execute

```typescript
// This runs in <head> before DOM is fully loaded
dispatcher.setupRoute();
```

### Phase 2: Execution (Asynchronous)

Runs after DOM ready via `dispatcher.execRoute()`:

1. Site's `exec()` method executes
2. Matched Page's `onLoad()` method executes
3. Matched Components' `onLoad()` methods execute

```typescript
// This runs after DOM is ready
window.Webflow ||= [];
window.Webflow.push(() => {
  dispatcher.execRoute();
});
```

## Instance Persistence (Critical Fix)

{% hint style="danger" %}
**v2.0.0 Critical Fix:** RouteDispatcher must be created once and reused to prevent data loss.
{% endhint %}

### Wrong Approach (Data Loss)

```typescript
// ❌ BROKEN - Creates two different instances
routeDispatcher().setupRoute();   // Instance A stores data
routeDispatcher().execRoute();    // Instance B has no data - LOST!
```

This creates two separate RouteDispatcher instances. Any data stored during `setupRoute()` is lost because `execRoute()` runs on a different instance.

### Correct Approach (Data Preserved)

```typescript
// ✅ CORRECT - Single instance preserves data
const dispatcher = routeDispatcher();
dispatcher.setupRoute();   // Instance stores data
dispatcher.execRoute();    // SAME instance has the data
```

This ensures the same RouteDispatcher instance is used for both phases, preserving all data.

## Route Matching Logic

The dispatcher matches routes in the following order:

1. **Exact matches** first: `/about` matches before `/about/*`
2. **Wildcard matches** second: `/blog/*` matches `/blog/post-1`
3. **No match**: No page code executes (Site code still runs)

Example:

```typescript
// routes.ts
dispatcher.routes = {
    '/': HomePage,           // Exact: /
    '/blog': BlogIndexPage,  // Exact: /blog
    '/blog/*': BlogPostPage, // Wildcard: /blog/anything
};

// URL: / → HomePage
// URL: /blog → BlogIndexPage
// URL: /blog/my-post → BlogPostPage
// URL: /about → No page code (only Site)
```

## Accessing Route Information

When using `PageBase`, route information is automatically available:

```typescript
@page('/blog/*')
export class BlogPage extends PageBase {
  protected async onLoad(): Promise<void> {
    console.log('Path:', this.pageInfo.path);           // /blog/my-post
    console.log('Page ID:', this.pageInfo.pageId);      // Webflow page ID
    console.log('Collection:', this.pageInfo.collectionId); // CMS collection
    console.log('Item Slug:', this.pageInfo.itemSlug);  // my-post
  }
}
```

## Multiple Routes Per Page

Use multiple `@page` decorators to handle multiple routes with one class:

```typescript
@page('/about')
@page('/about-us')
@page('/team')
export class AboutPage extends PageBase {
  protected async onLoad(): Promise<void> {
    // Check which route was accessed
    if (this.pageInfo.path === '/team') {
      this.showTeamSection();
    }
  }
}
```

## Best Practices

1. **Create dispatcher once** - Store in a variable and reuse for both setup and exec
2. **Use @page decorator** - Simplifies route registration
3. **Import all pages** - Import page files in routes.ts to trigger decorators
4. **Wildcard for CMS** - Use `/*` for collection template pages
5. **Extend PageBase** - Get automatic context detection and pageInfo access

## Example: Complete Setup

Here's a complete example showing proper dispatcher usage:

**src/routes.ts:**

```typescript
import { RouteDispatcher, getAllPages } from "@sygnal/sse-core";
import { Site } from "./site";

// Import all pages
import "./pages/home";
import "./pages/about";
import "./pages/blog";
import "./pages/products";

// Import all components
import "./components/navigation";
import "./components/footer";

export const routeDispatcher = (): RouteDispatcher => {
    const dispatcher = new RouteDispatcher(Site);
    dispatcher.routes = getAllPages();
    return dispatcher;
}
```

**src/index.ts:**

```typescript
import { routeDispatcher } from './routes';

// Create once
const dispatcher = routeDispatcher();

// Setup phase
dispatcher.setupRoute();

// Execution phase
window.Webflow ||= [];
window.Webflow.push(() => {
  dispatcher.execRoute();
});
```

**src/pages/blog.ts:**

```typescript
import { PageBase, page } from '@sygnal/sse-core';

@page('/blog/*')
export class BlogPage extends PageBase {

  protected onPrepare(): void {
    console.log('Blog page preparing...');
  }

  protected async onLoad(): Promise<void> {
    console.log('Blog post loaded:', this.pageInfo.itemSlug);

    // Your blog-specific code here
    this.loadComments();
    this.setupSocialSharing();
  }

  private async loadComments() {
    // Load comments for this post
  }

  private setupSocialSharing() {
    // Setup social sharing buttons
  }
}
```
