Simple LWR Client-Side Routing Example

You can use the @lwrjs/router package to create a single page LWR application (SPA) with client-side routing. Important exports from the package include:

  • lwr/router
  • lwr/routerContainer
  • lwr/outlet
  • lwr/navigation

For additional information:

Example Project Setup 

While an LWR app contains more files than shown here, in this recipe the following example module files are most important:

1simple-routing
2├── src
3│   └── modules
4│       └── example
5│           ├── app
6│           │   ├── app.css
7│           │   ├── app.html
8│           │   └── app.js
9│           ├── homePageHandler
10│           │   └── homePageHandler.js
11│           ├── namedPageHandler
12│           │   └── namedPageHandler.js
13│           └── pageLink
14│                   └── pageLink.html
15│                   └── pageLink.js
16├── lwr.config.json
17└── package.json

Set the Root Component of Your Application 

The first step in creating a router is to set up the root component of your SPA. You specify a rootComponent value in a routes object in your app’s lwr.config.json file. Check out the following example and “Routing Properties” for more information.

The routes defined in lwr.config.json are server-side. They differ from the RouteDefinition array that you set up in the next step for the client-side router.

Note

This file is lwr.config.json.

1{
2  "lwc": { "modules": [{ "dir": "$rootDir/src/modules" }] },
3  "routes": [
4    {
5      "id": "app",
6      "path": "/",
7      "rootComponent": "example/app"
8    }
9  ]
10}

Create a Router 

After you set up your root component, create a router for it. This example uses the createRouter() function to generate a router.

1// src/modules/example/app/app.js
2
3import { LightningElement } from "lwc";
4import { createRouter } from "lwr/router";
5
6// Route definition array
7const routes = [
8  {
9    id: 'home',
10    uri: '/',
11    handler: () => import('example/homePageHandler'),
12    page: {
13      type: 'home',
14    },
15  },
16  {
17    id: 'namedPage',
18    uri: '/:pageName',
19    handler: () => import('example/namedPageHandler'),
20    page: {
21      type: 'namedPage',
22      attributes: {
23        pageName: ':pageName',
24      },
25    },
26  },
27];
28
29export default class SimpleRoutingApp extends LightningElement {
30  router = createRouter({ routes });
31
32  homeReference = { type: 'home' }; // Home page reference for the page-link
33}

In your root component’s html file, attach the router instance to lwr/routerContainer, as in this example:

1<!-- src/modules/example/app/app.html -->
2
3<template>
4    <lwr-router-container router={router}>
5        <example-page-link label="Home" page-reference={homeReference}></example-page-link>
6        <lwr-outlet>
7            <div slot="error">This content failed to display.</div>
8        </lwr-outlet>
9    </lwr-router-container>
10</template>

Create a Route Handler 

In the previous step you created a router in your root component file. That router includes a promise in RouteDefinition.handler to a route handler module that’s called when a location matches a RouteDefinition. Route handler modules determine the associated “view”, which is the component to display when the application navigates to a location.

In LWR, route handler modules are dynamically imported, so they are provided via promises. Promises allow the module code to be lazily loaded, improving application performance.

Note

A simple route handler for a home page:

1// src/modules/example/homePageHandler/homePageHandler.js
2
3export default class HomePageHandler {
4  callback;
5
6  constructor(callback) {
7    this.callback = callback;
8  }
9
10  dispose() {
11    /* noop */
12  }
13
14  update() {
15    this.callback({
16      viewset: {
17        default: () => import('example/home'),
18      },
19    });
20  }
21}

Route handler modules allow you to define enhanced routing rules such as branching and pivoting logic based on data and metadata values. Here’s another route handler, this time with branching logic:

1// src/modules/example/namedPageHandler/namedPageHandler.js
2
3export default class NamedPageHandler {
4  callback;
5
6  constructor(callback) {
7    this.callback = callback;
8  }
9
10  dispose() {
11    /* noop */
12  }
13
14  update({ attributes }) {
15    let viewGetter;
16
17    // Get the "pageName" from the incoming page reference
18    switch (attributes.pageName) {
19      case "products":
20        viewGetter = () => import('example/products');
21        break;
22      case "recipes":
23        viewGetter = () => import('example/recipes');
24        break;
25      case "contact":
26        viewGetter = () => import('example/contact');
27        break;
28      default:
29        return;
30    }
31
32    this.callback({
33      viewset: {
34        default: viewGetter,
35      },
36    });
37  }
38}

Navigate 

In a Lightning web component, use the NavigationContext wire and navigate() function from lwr/navigation to navigate.

For example, in this snippet, the NavigationContext wire obtains a ContextId value. It assigns the ID to the navContext property, where it can be used by the navigate() API.

1import { LightningElement, wire } from 'lwc';
2import { NavigationContext, navigate } from 'lwr/navigation';
3
4export default class HomeLink extends LightningElement {
5    @wire(NavigationContext)
6    navContext;
7
8    navigateHome(event) {
9        // Navigate when the button is clicked
10        event.preventDefault();
11        if (this.navContext) {
12            navigate(this.navContext, {
13                type: 'home',
14                attributes: { format: 'list' },
15                state: { 'dark-mode': 'true' },
16            });
17        }
18    }
19}
1<template>
2  <button onclick="{navigateHome}">Home</button>
3</template>

You can also use generateUrl() to generate a URL from a page reference, like this:

src/modules/example/pageLink/pageLink.js
1import { LightningElement, api, track, wire } from 'lwc';
2import { NavigationContext, generateUrl, navigate } from 'lwr/navigation';
3
4export default class PageLink extends LightningElement {
5  @api label;
6  @api pageReference;
7  @track path;
8
9  @wire(NavigationContext)
10  navContext;
11
12  async connectedCallback() {
13    // Add an href to the link anchor
14    if (this.pageReference && this.navContext) {
15      this.path = generateUrl(this.navContext, this.pageReference) || undefined;
16    }
17  }
18
19  handleClick(event) {
20    // Navigate when the link is clicked
21    event.preventDefault();
22    if (this.pageReference && this.navContext) {
23      navigate(this.navContext, this.pageReference);
24    }
25  }
26}
src/modules/example/pageLink/pageLink.html
1<template>
2    <a onclick={handleClick} href={path}>{label}</a>
3</template>

Handle Errors 

Sometimes the router navigation completes, but the component’s new view contains an error. This situation is considered a successful navigation, so an errornavigate event doesn’t fire. To handle this situation:

  1. Display an error:

    Via an error slot in an lwr-outlet component:

    1<!-- app.html -->
    2
    3<lwr-outlet>
    4  <div slot="error">This content failed to display</div>
    5</lwr-outlet>

    Or via a custom component in the error slot:

    1<!-- app.html -->
    2
    3<lwr-outlet>
    4  <div slot="error">
    5    <c-my-error></c-my-error>
    6  </div>
    7</lwr-outlet>
  2. Handle the viewerror event that lwr-outlet dispatches:

    1<template>
    2    <lwr-router-container router={router}>
    3        <lwr-outlet onviewerror={onViewError}></lwr-outlet>
    4    </lwr-router-container>
    5</template>
    1onViewError(viewErrorEvent: CustomEvent) {
    2    // handle viewerror
    3    const error = viewErrorEvent.detail.error;
    4    const stack = viewErrorEvent.detail.stack;
    5    console.error(`error rendering view component: '${error.message}' from:\n${stack}`);
    6}

Read more about LWR outlets.

Run Your SPA 

Run the following terminal commands from the root of your project. Read Get Started for details on LWR NPM commands.

1npm install
2npm run build
3npm run start

Open the site at http://localhost:3000.

Developer Preview Feature

Feature is available as a developer preview. Feature is not generally available unless or until Salesforce announces its general availability in documentation or in press releases or public statements. All commands, parameters, and other features are subject to change or deprecation at any time, with or without notice. Do not implement functionality developed with these commands or tools.