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 Route
s to render based on the
current URL.
A simple use looks like this:
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
Route
s can also have nested child Route
s which will resolve their path
relative to the parents path
.
Different to GoRouter in Flutter, nested Route
s 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 Route
s 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.
LazyRoute
s 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.