Type-safe routing

As an optional enhancement, Routescape supports progressive schema-based route type safety.

Type-safe routing is enabled by Routescape's support of routes created with a type-safe URL builder like url-shape coupled with a schema created with zod or yup. This approach allows for gradual or partial adoption of type-safe routing in an application.

import {A, useRoute} from 'routescape';
import {createURLSchema} from 'url-shape';
import {z} from 'zod';

const {url} = createURLSchema({
    '/': null, // goes without parameters
    '/sections/:id': {
        params: z.object({
            id: z.coerce.number(),
        }),
    },
    '/search': {
        query: z.object({
            term: z.string(),
            lang: z.optional(z.enum(['current', 'all'])),
        }),
    },
});

let App = () => {
    let {withRoute} = useRoute();

    // `withRoute(routePattern, x, y)` acts similarly to
    // `matchesRoutePattern ? x : y`
    return (
        <>
            <header className={withRoute(url('/'), 'full', 'compact')}>
                <h1>App</h1>
                <nav>
                    <A href={url('/')}>
                        Intro
                    </A>
                    {' | '}
                    <A href={url('/sections/:id', {params: {id: 1}})}>
                        Start
                    </A>
                </nav>
            </header>
            {withRoute(url('/'), (
                <main>
                    <h1>Intro</h1>
                </main>
            ))}
            {withRoute(url('/sections/:id'), ({params}) => (
                <main>
                    <h1>Section {params.id}</h1>
                </main>
            ))}
        </>
    );
};

Live demo

🔹 The url() function is a type-safe URL builder. It creates a URL with a URL pattern defined in the schema and typed parameters that are prevalidated against the given schema: typos and type mismatches are highlighted in a type-aware code editor.

🔹 For more details on the output of the createURLSchema(), such as url(), match(), validate(), and the null-schema mode, see the description of url-shape.

🔹 A URL schema doesn't have to cover the entire app. Standalone portions of an app can have their own URL schemas.

🔹 Stricter type safety can be achieved by disallowing URLs and URL patterns other than provided by the URL builder (the url() function in the example above) throughout the app:

declare module 'routescape' {
    interface Config {
        strict: true;
    }
}

Adding this type declaration to an app effectively disallows using string and RegExp values for routes and route patterns (such as in the route link href prop, route.assign(location), and the ternary route-matching function withRoute(routePattern, x, y)), only allowing values returned from the URL builder with the same routing APIs.

🔹 A URL builder pattern (like url('/sections/:id')) can also be used with useRouteMatch(pattern) and useRouteState(pattern) to manipulate URL parameters in a type-safe manner.

Typed URL parameters state demo