Understand Client-Side Routing in LWR on Node.js
With LWR’s client-side routing capabilities, you can create a Single Page Application (SPA).
For simple routing needs, LWR’s server-side routing may be all you need.
If you examine the files for your static site project, inside the node_modules
directory you find @lwrjs/router
. Inside @lwrjs/router
are modules for client-side routing (lwr/router
) and navigation (lwr/navigation
). These modules export APIs to create a router, navigate, generate URLs, and subscribe to navigation events.
The @lwrjs/router
package is unrelated to the routes
array used in configuring server-side routes.
This page describes the concepts, architecture, and essential steps in using client-side routing with LWR.
A router is a piece of code that manages client-side navigation changes. All navigation events flow through a router for processing. You must create a router to use client-side LWR routing.
To create a router, you can either:
- use the Router Module Provider to generate a router based on a static JSON file (good for most basic use cases)
or
- use the
createRouter()
API to create and initialize the router (for more sophisticated use cases)
This section describes the use of the Router Module Provider to create a router. “Create a Router” in Simple LWR Client-Side Routing provides an example of creating a router with the createrRouter()
API.
Because static configuration can be easier to author and to maintain, you can use the Router Module Provider to generate a router based on a static JSON file. A generated router consumes its configuration from a portable JSON file rather than a JavaScript module. This approach is most helpful for straightforward use cases.
Router Module Provider isn’t a default module provider, so it must be added to the project configuration. Learn more in Package and Module Configuration in LWR on Node.js.
In the package.json
file at the root of your project, add "@lwrjs/router/module-provider": "x.y.z"
as a dependency.
In the preceding, "x.y.z" refers to the current version number. To find the current version number, examine the @lwrjs/router
package.json
file.
Next, register the Router Module Provider in lwr.config.json
.
When registering the module provider, you can optionally configure the directory location of the router JSON files.
If a configuration isn’t specified when registering the Router Module Provider, it uses this default configuration:
The Router Module Provider generates a router module based on JSON configuration of the type LwrRouterConfig. Example:
After you’ve specified the JSON configurations, you can import and use the generated router.
The generated router module provides a createRouter()
function that is identical to the static createRouter()
function, except that it doesn’t take a routes
array, since the routes are configured in the JSON file instead. If basePath
or caseSensitive
is specified in both the JSON file and the createRouter()
call, then the createRouter()
call takes precedence.
The generated router module specifier is of the form: @lwrjs/router/<name of the JSON config file>
A router processes incoming navigation events and uses its route definitions to determine if a location is valid. If the location is valid, the navigation event is accepted, and the browser updates the displayed content.
The router receives navigation events either as URIs or page references.
- URI: A location specified in string form, as in a browser's address bar, and captured by the router via the
popstate
event. - Page Reference: A location specified in JSON form. You pass a PageReference object to the router via the
navigate()
API.
The router converts locations between forms. For example, the following URI and page reference are equivalent:
A router uses route definitions to verify and process incoming location changes. A location is only valid if it can be matched to a RouteDefinition
. If given an invalid location, the application fails to navigate.
The following considerations affect whether a page reference or URI is a valid match.
For navigating by pageReference
, the following rules apply:
- every property specified must be defined
- literal values must be equal
- every pattern must be matched
- extra state properties are allowed
- extra attribute properties cause the match to fail
When specifying query parameters in a URI, the route matches ONLY if the input URL contains the same query parameter names. For example /path?:actionName
ONLY matches if the URL contains the actionName
query key, for example: /path?actionName=<anything>
.
If the query parameters provided in the URI contain a literal string for the value (e.g., ?foo=Foo
), it ONLY matches if the URL likewise contains that exact literal, e.g., /path?foo=Foo
. This matching can be case insensitive if caseSensitive
isn’t specified in the RouteDefinition
.
The order of the query parameters in the URL doesn’t matter.
Here’s an example RouteDefinition
for a page in a recipe website:
This URI and page reference match the page’s RouteDefinition
:
This URI and page reference don’t match the RouteDefinition
:
When the router matches an incoming location to a RouteDefinition
, it accesses RouteDefinition.handler
to determine the associated "view". A view is the component to display when the application navigates to a location.
RouteDefinition.handler
contains a Promise for your RouteHandler
module. (In LWR, modules are always provided via promises. Promises allow the module code to be lazily loaded, improving application performance.) For more on the RouteHandler
interface, see LWR Client-Side Routing Reference.
Route handler modules, which are used in client-side routing, aren’t the same as route handler functions, which are used in server-side routing.
Given information on the current location (a RouteInstance
), the RouteHandler
module provides a set of views via a callback from its update()
function:
Simple Client-Side Routing includes examples of additional route definition handlers, including for branching logic.
In order to use a router in an application, it must be attached to the DOM with a router container. Router containers are provided by the lwr-router-container
component.
A router container provides “navigation context”. This means that the router container is responsible for processing all navigation wires and events from its descendants in the DOM (my-nav
and lwr-outlet
in the example following).
For more information:
- “Create a Router” in Simple Client-Side Routing demonstrates attaching a router instance to a router container.
- Nested Client-Side Routing provides an example of a router container with a nested child router.
A view is the component to display when the application navigates to a location. It’s an outlet's job to dynamically render those view components. The lwr-outlet
Lightning web component uses the CurrentView
wire to get the current view component, then displays it in the DOM.
A route handler module can return multiple views. You can use multiple outlets to display all the current view components by setting different view-name
values:
lwr-outlet
in LWR Client-Side Routing Reference provides more details on using the lwr-outlet
component.
The @lwrjs/router
package provides an implementation of lightning/navigation
. The lightning/navigation
module is an alias for the lwr/navigation
module, so it includes the same wires and APIs, along with NavigationMixin
. This allows you to write a component once and use it anywhere that supports the lightning/navigation
contracts.