Skip to content

Limitations

While most routing libraries prefer to copy all the functionality their competitors have and tie themselves tightly to their own ecosystem, Reactive Route keeps its focus on:

  • reactive storage of current page data in router.state[name]
  • async preparation for opening the next page
  • a convenient interface for redirects (because native window.pushState('/new-path?phone=1234') is not typed, runs synchronously, and does not work on the server or in widgets)
  • framework and reactivity-system independence

TypeScript is a positive trend today, so all functionality that is incompatible with static typing is absent. This includes optional path parameters, JSX declarations like <Route path="untyped[?-partial]-string/:id/:id/:id">, file routing like posts/$postId/$/$.tsx, and other practices that destroy type safety and structure.

Nested routes / Dynamic routes

Imagine a configuration that is valid from the perspective of a number of alternative routers, but that will never exist in Reactive Route:

ts
const configs = createConfigs({
  user: {
    path: '/user/:id',
    params: { id: validators.numeric },
    
    // imagine we have a nested route here
    children: {
      default: {
        // :id collision with parent
        path: 'default/:id',
        
        // validator schema collision with parent
        params: { id: validators.notNumeric },
        loader: () => import('./pages/user'),
      },
      
      // name collision with parent
      user: {
        // :id collision with parent
        path: 'view/:id',
        
        // validator schema collision with parent
        params: { id: validators.alphaNumeric },
        loader: () => import('./pages/user/view'),
      }
    }
  },
});

// try to extend the configs
configs.extend({
  parent: 'user.user',
  children: {
    default: {
      // path collision with parent path: 'view/:id'
      path: 'default/:id',

      // validator schema collision with parent
      params: { id: validators.notNumeric },

      // component collision with parent
      loader: () => import('./pages/user'),
    }
  }
}

Collisions of names and validators cannot be resolved at the static analysis level, and redirects become complex and unreliable, without a clear lifecycle.

In routers that use such patterns, you have to resolve collisions at runtime, keep the entire component structure and its dynamics in your head, and refactoring requires completely rewriting redirect logic based on partial path strings without TypeScript assistance.

It is also extremely difficult to make data loading and permission checks flow in a stable way. Will the second-level beforeEnter be called when third-level params or query change, and vice versa?

Naturally, DX suffers too: there is no support for "Find Usages" or fast IDE navigation, autocomplete support is limited, there are no hints when describing redirects, and when using AI during refactoring, you have to pass the entire project code because the route structure is built at runtime.

Thus, the code above immediately becomes legacy and requires expert knowledge of the project, radically complicating codebase evolution, parallel team work, and transparency.

Hash / History State

URL hash and History State are not supported in Reactive Route, and they are forcibly cleared during redirects. Using them complicates design and makes the application state fragmented.

The library has two powerful mechanisms for dynamic parameters — optional query and required params. They are typed, participate in lifecycles, are validated, and can effectively solve tasks for which hash was traditionally used.

Also, Reactive Route is not tied to the History API, which allows it to be used for embedded widgets or microfrontends with full async routing isolated from other parts of the application, on any framework.

Non-string params and query

Since the browser URL contains only string values, Reactive Route does not include utilities for automatic conversion to different data types.

ts
const configs = createConfigs({
  user: {
    path: '/user/:id',
    loader: () => import('./pages/user'),
    
    // validation is required and stable
    params: { id: validators.numeric },
    query: { phone: validators.numeric.length(6, 15) },
  },
});

// 100% safe casts

const { params, query } = router.state.user!;

const id = computed(() => Number(params.id));
const phone = computed(() => query.phone ? Number(query.phone) : null);

// URL can't have numbers, only strings.
// No hidden magic of type conversion

router.redirect({
  name: 'user',
  params: { id: '9999' },
  query: { phone: '123456' }
})

In this example, string values are checked with validators.numeric, which is either project-specific or taken from any of the hundreds of validation libraries. It is assumed that it has already checked the value for NaN, Infinite, -0 and confirmed that the string becomes a valid number when passed to Number(params.id).

But conversion itself to Number / Boolean / Object / Array is not built into the library and, as in the example above, is the developer's responsibility. This makes it possible to use structures of any complexity through custom deserialization mechanisms.

Some libraries have built-in utilities for converting values to a specific type, which creates the illusion that a URL can store and process more than just strings. Validation approaches become incorrect, which is especially noticeable when trying to make the router automatically convert to Object / Array / Date and parse complex structures.

Untyped beforeEnter / beforeLeave

TypeScript 5 still cannot recursively infer types for beforeEnter and beforeLeave, so the currentState, nextState, and redirect arguments have simplified types. You should describe logic in them carefully — TS will not highlight errors during refactoring.

This limitation only affects the lifecycle; in all other scenarios, full and strict typing remains. This compromise had to be made because moving lifecycle functions outside createConfigs causes page logic to spread out and worsens DX.

No AI participated in the development. MIT License