# Register routes

By allowing consumers to register dynamic routes, Squide enables developers to build scalable modular applications with well-defined boundaries. Each module contributes its own routing configuration, which the host assembles into a unified routing structure at bootstrapping.

Below are the most common use cases. For more details, refer to the reference documentation.

# Register a basic route

Typically, simple routes using only the path and element options will be defined:

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Page } from "./Page.tsx"

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        path: "/page-1",
        element: <Page />
    });
};

# Register a route with an id

The registerRoute function accepts a parentId option, allowing a route to be nested under an existing parent route. When searching for the parent route matching the parentId option, the parentId will be matched against the $id option of every route.

Here's an example of a "pathless" route with an id:

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { RootErrorBoundary } from "./RootErrorBoundary.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        $id: "error-boundary",
        element: <RootErrorBoundary />
    });
};

# Register a nested route

React router nested routes enable applications to render nested layouts at various points within the router tree. This is quite helpful for modular applications as it enables composable and decoupled UI.

To fully harness the power of nested routes, the registerRoute function allows a route to be registered under any previously registered route, even if that route was registered by another module. The only requirement is that the parent route must have been registered with the registerRoute function.

When registering a new route with the registerRoute function, to render the route under a parent route, specify a parentPath option that matches the parent route's path option:

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Page } from "./Page.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        path: "/layout/page-1",
        element: <Page />
    }, { 
        parentPath: "/layout" // Register the route under an existing route having "/layout" as its "path".
    });
};

Or a parentId option that matches the parent route's $id option:

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Page } from "./Page.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        path: "/page-1",
        element: <Page />
    }, { 
        parentId: "error-boundary" // Register the route under an existing route having "error-boundary" as its "$id".
    });
};

Routes can also be nested by registering multiple routes in a single registration block:

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Layout } from "./Layout.tsx";
import { Page } from "./Page.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        path: "/layout",
        element: <Layout />,
        children: [{
            path: "/layout/page-1",
            element: <Page />
        }]
    });
};

A single registration block routes can also be define routes with relative paths (rather than starting with a /):

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Layout } from "./Layout.tsx";
import { Page } from "./Page.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        path: "layout",
        element: <Layout />,
        children: [{
            path: "page-1",
            element: <Page />
        }]
    });
};

# Register an hoisted route

Unlike a regular route, a hoisted route is added directly at the root of the router. This gives it full control over its rendering, as it is not nested under any root layouts. To mark a route as hoisted, include the hoist option in the route configuration:

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Page } from "./Page.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerRoute({
        path: "/page-1",
        element: <Page />
    }, {
        hoist: true
    });
};

Package managers supporting workspaces such as Yarn and NPM call this mechanism "hoisting", which means "raise (something) by means of ropes and pulleys". This is exactly what we are trying to achieve here.

Squide has a built-in hoist functionality capable of raising module routes marked as hoist at the root of the routes array, before the root layout declaration. Thus, an hoisted route will not be wrapped by the root layout component (or any components) and will have full control over its rendering.

In this example, if we defined both an authentication boundary and a root layout, page-1 would become a sibling of the authentication boundary rather than one of its children:

root
├── Page 1   <---------------- Raise the page here
├── Authentication boundary
├────── Root layout

# Register a public route

When registering a route, a value can be provided indicating whether the route is "public" or "protected". This is especially useful when dealing with code that fetches global data for protected routes (e.g. a session). Although a route definition accepts a $visibility value, we recommended using the runtime registerPublicRoute function to register a root public route instead.

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Page } from "./Page.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerPublicRoute({
        path: "/page-1",
        element: <Page />
    });
};

To define a nested route as public, use the $visibility option:

import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { Layout } from "./Layout.tsx";
import { Page } from "./Page.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerPublicRoute({
        path: "/layout",
        element: <Layout />,
        children: [
            {
                $visibility: "public",
                path: "/page-1",
                element: <Page />,
            }
        ]
    });
};

# Register a not found route

A no-match route can be defined to catch invalid or unknown URLs. To do this, register a route with * as the path:

import { type ModuleRegisterFunction, type FireflyRuntime } from "@squide/firefly";
import { NotFoundPage } from "./NotFoundPage.tsx";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    runtime.registerPublicRoute({
        path: "*",
        element: <NotFoundPage />
    });
};