Customize the project setup and server configuration for your LWR project.
A typical directory structure for a basic JavaScript-based LWR project looks like this:
src/
├── assets/ // static assets (images, css, etc)
│ └── logo.png
├── content/ // content templates
│ └── help.html
├── data/ // global data for content and layout templates
│ └── global.json
├── layouts/ // layout templates
│ ├── main.html
│ └── site.njk
└── modules/ // lwc modules
│ └── namespace/
│ └── name/
│ ├── name.css
│ ├── name.html
│ └── name.js
└── index.ts // create and start a LWR server
lwr.config.json // lwr configuration
package.json // npm packaging configuration
To use LWR, include it and LWC as dependencies in package.json.
// package.json
{
"devDependencies": {
"lwc": "~2.2.0",
"lwr": "0.6.1"
}
}Add any other project dependencies you need to package.json, such as client-side routing. See the Simple Routing recipe for an example.
Then write a script to create and start the LWR server.
// src/index.ts
import { createServer } from 'lwr';
createServer()
.listen(({ port, serverMode }) => {
// Success callback
console.log(`Lwr Application listening on port ${port} in ${serverMode} mode\n`);
})
.catch((e) => {
// Error callback
console.error(e);
process.exit(1);
});Or use the lwr serve CLI command to start your project.
// package.json
{
"name": "my-lwr-project",
"scripts": {
"dev": "lwr serve",
"start": "lwr serve --mode prod",
"start:compat": "lwr serve --mode compat",
"start:prod-compat": "lwr serve --mode prod-compat"
}
}The LWR server is configured in lwr.config.json, at the root of the project.
Alternatively, you can pass the JSON configuration into
createServer(). If you include both configurations, they are shallowly merged and the passed object takes precedence. You may also dynamically alter the configuration at server startup using a hook.
To include LWC modules in your project, install the LWC package using npm and place LWC configuration inside lwr.config.json. See the Base SLDS recipe for an example using base LWCs and the Salesforce Lightning Design System.
npm install lightning-base-components --saveThe package.json now includes the lightning-base-components package.
// package.json
{
"devDependencies": {
"lightning-base-components": "^1.9.0-alpha",
"lwc": "~2.2.0",
"lwr": "0.6.1"
}
}The lwc.modules key in lwr.config.json accepts an array of module records from these sources:
- Alias module record: A file path where a module can be resolved.
- Directory module record: A folder path where modules can be resolved.
- NPM package module record: An NPM package that exposes one or more modules.
// lwr.config.json
{
"lwc": {
"modules": [
{
"name": "ui/button",
"path": "src/modules/ui/button/button.js"
},
{
"dir": "$rootDir/src/modules"
},
{
"npm": "lightning-base-components"
}
]
}
}The resolver iterates through the modules array and returns the first module that matches the requested module specifier. LWR automatically replaces any instances of $rootDir with the path to the root directory of the LWR project.
See the LWC documentation for more details on how LWC module resolution works.
If a recipe is running in prod or prod-compat mode, LWR bundles modules before sending them to the client. Depending on your project setup, the same module dependency may get pulled into more than one bundle. This does not cause a problem for most modules, but some must be treated as singletons.
Examples of singleton modules are lwc, lwr/navigation, and @lwc/synthetic-shadow. Since these are framework modules, LWR automatically puts them into their own, shareable bundles. If an application contains additional singleton modules, exclude them from bundling as well:
// lwr.config.json
{
"bundleConfig": {
"exclude": ["my/singleton", "do/notBundle"]
}
}Each server-side route includes these properties:
id(required): unique identifier for the routepath(required): unique URI path from which the route is servedrootComponent: top-level LWC that LWR bootstraps into the HTML output for the route. Each route must have either arootComponentor acontentTemplate, but not both.contentTemplate: path to a static template which renders page contentlayoutTemplate: path to a static template which renders a page layoutproperties: JSON object which gets passed to the templates as contextrouteHandler: path to a route handlercache: cache settings for the routing, including:ttl: number, in seconds, or a time string to use as themax-ageon theCache-Controlheader
bootstrap: specifies the client options that shape how an application page is bootstrapped. See an example in the services recipe.syntheticShadow: set totrueto turn on lwc synthetic shadow, default isfalseservices: an array of lwc modules to run when the app is bootstrapping (i.e. on page load), see the Metrics and Services recipes
// lwr.config.json
{
"routes": [
{
"id": "example",
"path": "/",
"rootComponent": "example/app",
"layoutTemplate": "$layoutsDir/main.html",
"properties": { "staticParam": "This is the Home page" },
"cache": { "ttl": 60 },
"bootstrap": {
"syntheticShadow": true,
"services": ["example/service", "example/loaderHooks"]
}
},
{
"id": "docs",
"path": "/help",
"contentTemplate": "$contentDir/readme.md",
"routeHandler": "$rootDir/src/routeHandlers/docs.js",
"cache": { "ttl": "7d" }
}
]
}To learn how to work with templates and route handlers, see the Templating recipe. To learn how to work with client-side routes, see the Simple Routing recipe.
Optionally, set up routes that LWR serves if a 404 or 500 error is encountered during bootstrap of a route. The error routes take a status code for the error route instead of a path.
// lwr.config.json
{
"errorRoutes": [
{
"id": "not_found",
"status": 404,
"rootComponent": "example/notFound"
},
{
"id": "server_error",
"status": 500,
"contentTemplate": "$contentDir/not-found.html"
}
]
}To see an error route example, see the Templating recipe.
By default, LWR uses this configuration for assets.
{
"alias": "assetsDir",
"dir": "$rootDir/src/assets",
"urlPath": "/public/assets"
}You can configure one or more files or directories to serve static assets in lwr.config.json. For each file or directory, include these properties:
alias: a name for this path to use in content/layout templatesurlPath: the URI path from which the asset(s) is serveddirorfile: the file system path containing the asset(s)
// lwr.config.json
{
"assets": [
{
"alias": "imageDir",
"dir": "$rootDir/src/images",
"urlPath": "/images"
},
{
"file": "$rootDir/logo.png",
"urlPath": "/logo"
}
]
}The static assets can then be referenced in content or layout templates using the alias:
<!-- /src/layouts/main.html -->
<html>
<head>
<link rel="icon" href="$imageDir/logo.svg" />
<title>My Website</title>
</head>
<body>
<!-- ... -->
</body>
</html>Or in LWC templates using the urlPath:
<!-- /src/modules/my/component/component.html -->
<template>
<img src="/logo" alt="logo" />
<img src="/images/home.png" alt="home" />
</template>LWR automatically includes a set of default module providers, so you don't need to list them in lwr.config.json unless your app requires one or more additional module providers. The moduleProviders array overwrites the default one provided by LWR, so you must list all module providers needed by the application, including those owned by LWR. The latest default module provider list is in the LWR source code here.
// lwr.config.json with a custom module provider, the Label Module Provider, and the LWR default module providers
{
"moduleProviders": [
"$rootDir/src/services/my-module-provider.ts",
"@lwrjs/label-module-provider",
"@lwrjs/app-service/moduleProvider",
"@lwrjs/lwc-module-provider",
"@lwrjs/npm-module-provider"
]
}For more examples, see the Module Provider and Labels recipes.
LWR also offers the following optional configuration:
port: The port from which to serve the LWR application. The default isprocess.env.PORT || 3000.serverMode: The mode in which the server should run. The default is"dev". See available modes here. Typically, mode is set on the command line. See thescriptspackage.json.serverType: The type of underlying web server LWR should utilize. Supported values are"express"(default) ||"koa"but more server types may be supported in the future.rootDir: The root directory of the LWR project. The default is the current working directory (ie:.).cacheDir: LWR caches LWC modules that it has compiled and stores them in a cache directory. The default is"$rootDir/__lwr_cache__".contentDir: The content templates directory. The default is"$rootDir/src/content".layoutsDir: The layout templates directory. The default is"$rootDir/src/layouts".globalDataDir: The directory of global data for templating. The default is"$rootDir/src/data".hooks: A list of configuration hooks, used to dynamically alter the LWR Configuration at server startup. See more information in the templating recipe.locker: Allows you to enable Lightning Locker and optionally configure a list of trusted components. By default, locker is disabled. If locker is on, components from LWC and LWR are automatically trusted. See the locker recipe here.
// lwr.config.json
{
"port": 3333,
"serverMode": "prod",
"serverType": "express",
"rootDir": "/Users/me/lwr/projects/awesome",
"cacheDir": "$rootDir/build/cache",
"contentDir": "$rootDir/templates",
"layoutsDir": "$rootDir",
"globalDataDir": "$rootDir/src/templateContext",
"hooks": ["$rootDir/src/hooks/docs-app-hooks.js"],
"locker": {
"enabled": true,
"trustedComponents": ["my/trustedCmp"]
}
}The following table maps available LWR packages to recipes so you can see how they're used. If the package is a module provider, add it to the list of module providers in lwr.config.json.
| Package Name | Description | Is Module Provider? | Recipes that Use Package |
|---|---|---|---|
| label-module-provider | Pulls labels from JSON files and returns them as ES modules for localization | Yes | labels |
| router | Uses the router API to add navigation capabilities to your app | No | nested routing, simple routing, routing-extended-metadata |
| shared-utils | Helpers used for advanced functionality, like writing custom module providers | No | module provider |