Skip to content

Route Table Basics ​

Problem ​

Routing logic scattered across event handlers leads to duplicated path strings and no single place to add cross-cutting concerns like middleware or data loading.

Solution ​

Define all routes in one declarative table passed to createRouter(). Named routes eliminate duplicated path strings everywhere else in the app.

ts
import { createRouter } from '@vielzeug/wayfinder';

const routes = {
  home: {
    path: '/',
  },
  dashboard: {
    path: '/dashboard',
    children: {
      index: {
        index: true,
      },
      settings: {
        path: 'settings',
        data: async () => fetchSettings(),
      },
    },
  },
  userDetail: {
    path: '/users/:id',
    data: async ({ params }) => fetchUser(params.id),
  },
};

const router = createRouter({
  routes,
  notFound: { data: () => ({ message: 'Not found' }) },
});

// React to navigation:
router.subscribe((state) => {
  const leaf = state.matches.at(-1);
  render(leaf?.name, leaf?.data);
});

await router.navigate({ name: 'userDetail', params: { id: '42' } });
await router.navigate({ name: 'dashboard.settings' });
const href = router.url('userDetail', { id: '42' }, { tab: 'profile' });

Pitfalls ​

  • Object key order controls match precedence. Use the notFound option in createRouter for the not-found page; path: '*' routes still work but are matched in key order.
  • Child route path values are relative to the parent. Omit the leading slash: path: 'settings', not path: '/settings'.
  • A route key that contains a dot (e.g., 'user.detail') will clash with nested compound names. Avoid dots in top-level keys.