werkbank
    Preparing search index...

    Getting Started: Schema-First Router

    The Werkbank Router is a type-safe, schema-first routing library for React that treats URL paths and query parameters as validated data structures.

    • React: 18+
    • Valibot: Required for schema definition.
    • TypeScript: Highly recommended for type inference.

    The Werkbank Router inverts the traditional routing model. Instead of defining routes as strings and parsing them inside components, you define Schemas for your routes first.

    1. Schema Definition: You define the shape of your URLs (paths and search parameters) using valibot.
    2. Type Inference: The router infers TypeScript types from these schemas.
    3. Validation: URLs are validated before your components ever see them.
    4. Preloading: Data and code requirements are declared alongside the route definition.

    Start by defining the structure of your application's URLs. A route definition consists of a Path Pattern and optional Search Parameters.

    • Path Pattern: An array of static strings and Valibot schemas.
    • Search Parameters: A Valibot object schema.
    import { string, number, object } from "valibot";

    // Define the static configuration
    const routeConfig = {
    // Simple static route: /dashboard
    dashboard: ["/dashboard"],

    // Dynamic route with parameter: /users/:userId
    userProfile: ["/users/", string()],

    // Route with search parameters: /search?q=query&page=1
    search: {
    path: ["/search"],
    searchParams: object({
    q: string(),
    page: number(),
    }),
    },

    // Nested routes: /settings/profile
    settings: {
    path: ["/settings"],
    children: {
    profile: ["/profile"],
    },
    },
    } as const; // 'as const' is CRITICAL for type inference

    For each route defined in step 1, you must provide an implementation. This includes the Component, and optionally a Preload function, Loading component, and Error boundary.

    const Dashboard = () => <h1>Dashboard</h1>;
    

    The preload function receives the parsed parameters, search parameters, and your global context.

    Note: The preload function is designed to initiate data fetching (e.g., priming a cache or store). It does not pass data directly to the component. Your component should subscribe to the store or use a hook to access the data.

    import type { PreloadFunction } from "@werkbank/router";

    const UserProfile = ({ params }) => (
    <div>User: {params[0]}</div>
    );

    // Preload data before rendering
    const preloadUser: PreloadFunction<
    [string], // Path params type (inferred automatically in real usage)
    never, // Search params type
    [], // Parent params
    void // Context (optional)
    > = async ({ params }) => {
    // params[0] is typed as string because of the schema
    console.log("Preloading user:", params[0]);
    };

    You can lazy load components to split your bundle.

    const SearchPage = () => import("./pages/Search");
    

    Combine your configuration and implementations using createRouter.

    import { createRouter } from "@werkbank/router";

    const router = createRouter(routeConfig, {
    dashboard: {
    component: Dashboard,
    // Simple inline preload
    preload: () => {
    console.log("Dashboard preloading...");
    },
    },
    userProfile: {
    component: UserProfile,
    preload: preloadUser,
    },
    search: {
    component: SearchPage, // Automatically handled as lazy
    loading: () => <div>Loading Search...</div>,
    },
    settings: {
    component: ({ children }) => <div><h1>Settings</h1>{children}</div>,
    children: {
    profile: {
    component: () => <div>Profile Settings</div>,
    },
    },
    },
    });

    Use createLinks to generate a helper object for creating URLs. This ensures you can never generate a broken link or miss a required parameter.

    import { createLinks } from "@werkbank/router";

    const links = createLinks(routeConfig);

    // Usage
    const dashboardUrl = links.dashboard();
    // -> "/dashboard"

    const userUrl = links.userProfile({ params: ["user_123"] });
    // -> "/users/user_123"

    const searchUrl = links.search({
    searchParams: { q: "react", page: 1 }
    });
    // -> "/search?q=react&page=1"

    Render the Router component at the root of your application, passing the created router configuration.

    import { Router } from "@werkbank/router";
    import { createRoot } from "react-dom/client";

    const root = createRoot(document.getElementById("root"));

    root.render(
    <Router config={router} />
    );

    Finally, use the Link component and useNavigation hook in your application.

    The Link component is fully type-safe based on your router configuration.

    import { Link } from "@werkbank/router";

    // Type-safe navigation
    <Link
    to={links.userProfile({ params: ["user_123"] })}
    >
    View Profile
    </Link>

    Use the useNavigation hook to navigate imperatively.

    import { useNavigation } from "@werkbank/router";

    const MyComponent = () => {
    const { navigate } = useNavigation();

    const handleLogin = () => {
    navigate(links.dashboard());
    };

    return <button onClick={handleLogin}>Login</button>;
    };