What it gives you

defineRoutes() takes a single route definitions object and returns three things:

  • routes — the routes object for <Router>
  • nav — navigation helpers (nav.X.push(params), nav.X.replace(params), nav.X.link(params)) with IDE autocomplete on route names and parameter names
  • paths — URL builders (paths.X(params)) for href attributes

Parameter names are extracted from the path pattern at the type level'/user/:id' infers { id: string | number }, and paths.user({ di: 1 }) (typo) fails at compile time. Calls registerRoutes() for you, so named navigation via push('routeName') still works.

Quick start

routes.js
 

Then in your app shell:

App.svelte
 

Compile-time safety

The big payoff is in TypeScript / TSX-strict JS. Mistyped route names and mistyped parameter names both become errors at the call site, not runtime undefineds.

What the compiler catches
 

Inferred parameter types come from the :slug tokens in the path pattern. '/orders/:orderId/items/:itemId' infers { orderId: string | number; itemId: string | number }.

Navigation helpers (nav)

Each entry in nav exposes push, replace, and link — the same operations you'd call manually on push/replace from the main module, but with autocomplete bound to that specific route.

 

Using link() in templates

 

Or, more concisely, just pass the params shape directly via the array shorthand the link action already supports — defineRoutes() doesn't replace that path, it just gives you a type-checked alternative.

Path builders (paths)

paths.X(params, query?) returns the URL string for that route. Useful for href attributes (the link action will pick up the path), analytics, deep-link logging, or anywhere you need the URL without navigating.

 

Smart optimization

defineRoutes() chooses the right runtime form per route:

  • Sync component, no options ({ path, component } only) — used directly, skipping wrap() overhead.
  • Async component (component: () => import(...)) or any option set (conditions, breadcrumbs, permissions, etc.) — automatically routed through createRoute() with the right configuration.

You don't need to think about it — pass the same definition shape regardless of complexity.

All supported route options

Every option you'd normally pass to wrap() or createRoute():

 

When to use it

✅ Use defineRoutes() when

  • You're starting a new app — the cleanest default for v5.2+
  • You want compile-time errors on route name / param typos
  • You're navigating programmatically in many places — autocomplete pays off
  • You'd otherwise be juggling separate const routes = … and registerRoutes({ … }) calls

⏸ Stick with plain routes + registerRoutes() when

  • You're maintaining an existing app and the type safety isn't worth a rewrite
  • Routes are mostly flat and you rarely call push(name) programmatically
  • You're already happy with createHierarchy() for tree-structured routes (the two compose — createHierarchy() output can sit alongside defineRoutes() output in the same routes prop)

See also

  • Named routes — the lower-level registerRoutes() API that defineRoutes() calls internally
  • push / replace / goBack — what nav.X.push() wraps
  • Route inheritance — combine with defineRoutes() for the best of both
  • wrap() — manual route configuration when you don't want the auto-detection