Router

Handles routing between different pages both server- and client-side.

This component is part of the jaspr_router package. Make sure to add this to your dependencies before using the component.

Use can use the Router component to add routing to your website. It takes a list of Routes to render based on the current URL.

A simple use looks like this:

lib/app.dart
import 'package:jaspr_router/jaspr_router.dart';

import 'pages/home.dart';
import 'pages/about.dart' ;

class App extends StatelessComponent {
  @override
  Iterable<Component> build(BuildContext context) sync* {
    yield Router(routes: [
      Route(path: '/', builder: (context, state) => Home()),
      Route(path: '/about', builder: (context, state) => About()),
    ]);
  }
}

Router has a similar api than GoRouter from the go_router flutter package. If you know go_router a lot of the core concepts should feel familiar.

Defining Routes

To configure a Route, a path template and builder function must be provided:

Route(
  path: '/users/:userId',
  builder: (BuildContext context, RouteState state) {
    return const UserScreen();
  },
),

To navigate to this route, use context.push(). To learn more about how navigation works, see the Routing guide.

Path Parameters

To specify a path parameter, prefix a path segment with a : character, followed by a unique name, for example, :userId. You can access the value of the parameter by accessing it through the RouteState object provided to the builder function:

Route(
  path: '/users/:userId',
  builder: (context, state) => const UserScreen(id: state.pathParameters['userId']),
),

Similarly, to access a query string parameter (the part of URL after the ?), use RouteState. For example, a URL path such as /users?filter=admins can read the filter parameter:

Route(
  path: '/users',
  builder: (context, state) => const UsersScreen(filter: state.uri.queryParameters['filter']),
),

Child routes

Routes can also have nested child Routes which will resolve their path relative to the parents path.

Different to GoRouter in Flutter, nested Routes do not create a stack of pages. Instead, only the most nested matched route is rendered.

To define a nested child route, add it to the parent's routes list:

Route(
  path: '/users',
  builder: (context, state) => const UsersScreen(),
  routes: [
    Route(
      path: ':userId',
      builder: (context, state) => const UserScreen(id: state.pathParameters['userId']),
    ),
  ],
),

Navigating to /users will render the UsersScreen() and navigating to /users/abc will render the UserScreen().

Nested rendering

Some websites display content of pages in a subsection of the screen, for example, an app using a <nav> bar that stays on-screen when navigating between pages.

By using ShellRoute and providing a builder you can render any layout around your Routes content:

ShellRoute(
  builder: (context, state, child) {
    return div([
      child,
    ]);
  },
  routes: <RouteBase>[
    GoRoute(
      path: 'details',
      builder: (context, state) => const DetailsScreen(),
    ),
  ],
),

Lazy Routes

For larger websites, we don't want to load everything together, but rather split our pages into smaller chunks of code. LazyRoutes can be combined with Darts deferred imports to split the resulting javascript bundle into separate files for each LazyRoute:

import 'users_screen.dart' as users;

/* ... */

Route.lazy(
  path: '/users',
  load: users.loadLibrary,
  builder: (context, state) => users.UsersScreen(),
),

This will lazy load the needed .js file for the /users route only when navigating to it.

Navigating

Depending on your routing setup you can either use normal browser-based navigation through links (the <a> tag) or by using the Router.of(context) APIs.

The Router.of(context) is only available if you are using client-side routing, meaning the Router component is also built on the client. If its only built on the server you have to use normal links instead.

Navigating using Link component

The simplest way to add a navigation link to your site is by using the Link component. This is a drop-in replacement for the <a> tag and intelligently performs the correct navigation when clicked.

Navigating to a URL

To push a new route, call context.push() with a URL:

context.push('/users/123');

This is shorthand for calling Router.of(context).push('/users/123) and will navigate to the target URL.

push() will add a new entry to the browser history and enables use of the browsers back button. Alternatively you can use context.replace() to replace the current history entry with a new URL.

Navigating back

You can use context.back() (or Router.of(context).back()) to trigger the browsers back navigation.

Navigating to a named Route

Instead of navigating to a route based on the URL, a Route can be given a unique name. To configure a named route, use the name parameter:

Route(
   name: 'users',
   path: '/users/:userId',
   builder: /* ... */,
),

To navigate to a route using its name, call pushNamed() or replaceNamed():

context.pushNamed('users', params: {'userId': '123'});

Using extra

You can provide additional data along with navigation.

context.push('/123', extra: 'abc');

and retrieve the data from RouteState

final String extraString = RouteState.of(context).extra! as String;

The extra data will go through serialization when it is stored in the browser. You can only use primitive serializable values like String, bool, int, double or Lists and Maps for the extra data.

Redirection

Redirection changes the location to a new one based on application state. For example, redirection can be used to show a sign-in page if the user is not logged in.

A redirect is a callback of the type RouterRedirect. To change incoming location based on some application state, add a callback to either the Router or Route constructor:

redirect: (BuildContext context, RouteState state) {
  if (!AuthState.of(context).isSignedIn) {
    return '/signin';
  } else {
    return null;
  }
},

To display the intended route without redirecting, return null or the original route path.

Top-level vs route-level redirection

There are two types of redirection:

  • Top-level redirection: Defined on the Router constructor. Called before any navigation event.
  • Route-level redirection: Defined on the Route constructor. Called when a navigation event is about to display the route.

Redirecting to a named Route

You can use context.namedLocation() to look up the location for a named route. This is useful for redirecting to a named route:

redirect: (BuildContext context, RouteState state) {
  if (AuthState.of(context).isSignedIn) {
    return context.namedLocation('signIn');
  } else {
    return null;
  }
},

Considerations

You can specify a redirectLimit to configure the maximum number of redirects that are expected to occur in your site. By default, this value is set to 5. The Router will display the error screen if this redirect limit is exceeded.

Preloading Routes

When using Lazy Routes you can use Router.of(context).preload() to preload this route before actually navigating to it.

This is usually done as a performance improvement to estimate when the user is about to navigate to that route and already load in the needed .js files before it is visited.

yield button(
  events: {
    'mouseover': (event) {
      Router.of(context).preload('/about');
    },
    'click': (event) {
      Router.of(context.push('/about');
    },
  },
  [ /* ... */ ],
);

The above snippet will render a <button> that already preloads the target route when it is hovered, resulting in a potentially faster navigation when the user eventually clicks it.

The Link component has this behavior built in based on its preload parameter.