# 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
  }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://engine.sygnal.com/usage/source-structure-and-key-files/route-dispatcher.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
