# Server-Side Routing in LWR on Node.js

To set server-side routes for your app with LWR, use the `lwr.config.json` project configuration file. Server-side routes can be specified in `lwr.config.json` by providing either:

- a static path for each page
- a JavaScript file that defines a hook to dynamically generate server-side routes for pages at server startup (this is an advanced option)

Both static and dynamic server-side routes can also have route handler functions, which let you customize the page response at runtime.

Read on for details. We start with a basic static routing example.

## Static Server-Side Routing

Let’s add a page to our site and do some basic (and fast!) routing.

1. Now that you’ve got your [`StaticSite` project](/docs/platform/lwr/guide/lwr-get-started.md), make a copy of the `about.md` file. Rename the new file `explore.md` and leave it in the `content` directory.
2. Edit the `explore.md` file. Change the first line from `# About LWR` to `# Explore LWR`. Save the file.
3. Open the `main_layout.njk` file. On a new line, add `<li><a href="/explore">Explore</a>` under the `about` line. Save the file. ![Code snippet showing the Explore href addition](https://a.sfdcstatic.com/developer-website/sfdocs/lwr-node/media/addingExploreButton.png)
4. Finally, open the `lwr.config.json` file. Copy the entire block of code for `about`, including the curly brackets, and paste it below the `about` block. Change `about` to `explore` throughout, and don’t forget to add the comma after the closing bracket in the `about` block. Save the file. ![Code snippet showing the Explore block added](https://a.sfdcstatic.com/developer-website/sfdocs/lwr-node/media/addingExploreRouting.png)
5. If you're still in the `StaticSite` directory from when you [first made your site](/docs/platform/lwr/guide/lwr-get-started.md), just type `npm run start` in the terminal to see your changes. Your updated `StaticSite` project runs at <http://localhost:3000/>, with a new **Explore LWR** page and a working **Explore** button. ![Screenshot of updated site with Explore LWR page](https://a.sfdcstatic.com/developer-website/sfdocs/lwr-node/media/siteWithNewExploreRouting.png)

:::tip
If you get a "Port 3000 is already in use" error after running `npm run start`, close any terminal tabs that run your site preview. Then, open a new terminal tab and run `npm run start`.
:::

What just happened is that you:

- created a Markdown page (`explore.md`) and gave it new heading text ("Explore LWR")
- updated the site navigation in the Nunjucks layout (`main_layout.njk`) file to include a new **Explore** button
- updated the JSON routing configuration file (`lwr.config.json`) to let LWR know how to route to the new page using a [**route**](https://developer.salesforce.com/docs/platform/lwr/references/ss-routing-reference/route.md)
  - you gave an `id` for the page (`Explore`)
  - you provided the `path` for the page (`/explore`)
  - you provided a `contentTemplate` for the page (`explore.md`)
  - you told LWR the `layoutTemplate` to use for the page; in this case, you used `main_layout.njk`, which is the same as for other pages in this example, though that’s not required

## Error Routes

You can set up routes to serve if LWR encounters a `404` or `500` error during the bootstrap of a route. Error routes take a `status` code value instead of a `path` value.

```sfdocs-code {"lang":"json", "title": "lwr.config.json", "src": "" }
{
  "errorRoutes": [
    {
      "id": "not_found",
      "status": 404,
      "rootComponent": "example/notFound"
    },
    {
      "id": "server_error",
      "status": 500,
      "contentTemplate": "$contentDir/not-found.html"
    }
  ]
}
```

## Dynamic Server-Side Routing with Configuration Hooks

:::note
This is an advanced topic.
:::

Configuration hooks let you dynamically generate server-side routes for your application. On server startup, they update the [configuration](/docs/platform/lwr/guide/lwr-configure-project.md) and [global data](/docs/platform/lwr/guide/lwr-compile-data.md#global-data) for your app.

To set up a configuration hook, start by creating a `hooks` section in your `lwr.config.json` file. In there, add the filepath that points to the hook.

```sfdocs-code {"lang":"json", "title": "lwr.config.json", "src": "" }
{
  "hooks": ["$rootDir/src/hooks/my-config-hook.ts"]
}
```

The following is an example of a configuration hook:

```sfdocs-code {"lang":"javascript", "title": "src/hooks/my-config-hook.ts", "src": "" }
import path from 'path';
import { DEFAULT_LWR_BOOTSTRAP_CONFIG } from '@lwrjs/config';
import { slugify } from '@lwrjs/shared-utils';
import type { HooksPlugin, NormalizedLwrGlobalConfig, NormalizedLwrRoute, GlobalData } from '@lwrjs/types';

const DEFAULT_MAIN_LAYOUT = 'main_layout.njk';
const CACHE_TTL = '30m';
interface GuideItem {
  label: string;
  content: string;
}
interface GuideSidebarItem {
  label: string;
  id: string;
  url: string;
}
interface SiteGlobalData extends GlobalData {
  site?: {
    guide: GuideItem[];
      sidebar: GuideSidebarItem[];
  };
}

function generateGuideSidebar(guide: GuideItem[]): GuideSidebarItem[] {
  return guide.map(({ label, content }) => {
    return {
      label,
      id: `guide_${slugify(label)}`,
      url: `/${content.replace('.md', '')}`,
    };
  });
}

function generateGuideRoutes(
  guide: GuideItem[],
  { contentDir, layoutsDir }: NormalizedLwrGlobalConfig,
): NormalizedLwrRoute[] {
  return guide.map(({ label, content }) => {
    return {
      id: `guide_${slugify(label)}`,
      path: `/${content.replace('.md', '')}`,
      contentTemplate: path.join(contentDir, content),
      layoutTemplate: path.join(layoutsDir, DEFAULT_MAIN_LAYOUT),
      cache: { ttl: CACHE_TTL },
      bootstrap: DEFAULT_LWR_BOOTSTRAP_CONFIG,
    };
  });
}

// Export an Application Configuration hook
// Configured in lwr.config.json[hooks]
export default class MyAppHooks implements HooksPlugin {
  initConfigs(lwrConfig: NormalizedLwrGlobalConfig, globalData: SiteGlobalData): void {
    if (!globalData.site) {
      throw 'Expected site global data to be defined';
    }

    // The guide is an ordered list of files we want to display
    // Hardcoded here: src/data/site/guide.json
    const guide = globalData.site.guide;

    // Generate sidebar add it to the globalData object
    // The data is accessed in src/layouts/partials/guide-sidebar.njk
    globalData.site.sidebar = generateGuideSidebar(guide);

    // Dynamically add a new route for each guide
    // Note: other routes are statically declared in lwr.config.json[routes]
    lwrConfig.routes.push(...generateGuideRoutes(guide, lwrConfig));
  }
}
```

Dynamic server-side routes can use [route handler functions](#route-handler-functions) to customize the page response at runtime.

## Route Handler Functions

:::note
This is an advanced topic.
:::

Route handler functions are part of the [LWR context object](/docs/platform/lwr/guide/lwr-compile-data.md). They’re a server-side way to alter the current route and customize the page response at runtime. You can use route handler functions with both [static](#static-server-side-routing) and [dynamic](#dynamic-server-side-routing-with-configuration-hooks) server-side routing.

:::tip
Route handler **functions**, which are used in LWR's server-side routing, aren’t the same as [route handler **modules**](/docs/platform/lwr/guide/lwr-route-handler-modules.md), which are used in client-side routing.
:::

There are a couple of differences between [configuration hooks](#dynamic-server-side-routing-with-configuration-hooks) and route handler functions that are worth noting:

- A configuration hook is called once on server startup, while a route handler is triggered with each incoming page request.
- A configuration hook applies to your entire app, whereas each route handler applies only to its specified path.

You provide the path to a route handler function in `lwr.config.json`, like this:

```sfdocs-code {"lang":"json", "title": "Route with a handler in lwr.config.json", "src": "" }
{
  "id": "custom_route_handler",
  "path": "/custom/:param",
  "routeHandler": "$rootDir/src/routeHandlers/custom.ts"
}
```

A route handler function follows this syntax. For more information about syntax and properties of [`RouteHandlerFunction`](https://developer.salesforce.com/docs/platform/lwr/references/ss-routing-reference/routehandlerfunction.md) and the [`RouteHandlerViewReponse`](https://developer.salesforce.com/docs/platform/lwr/references/ss-routing-reference/routehandlerviewresponse.md) that it returns, see the [Server-Side Routing Reference](https://developer.salesforce.com/docs/platform/lwr/references/ss-routing-reference/ss-routing-reference.md).

```sfdocs-code {"lang":"typescript", "title": "RouteHandlerFunction Syntax", "src": "" }
type RouteHandlerFunction = (
    viewRequest: LocalizedViewRequest,
    handlerContext: HandlerContext,
) => Promise<RouteHandlerViewResponse>;
```

Things to note:

- **Cache size.** All the `ViewDefinitionResponse.viewParams` are added to the cache key for a page view response. To control cache size, monitor the number of items added.
- **Static route properties.** The `ViewDefinitionResponse.viewParams` replace the [static route properties](/docs/platform/lwr/guide/lwr-compile-data.md#static-route-properties), so if the static route properties are needed, you must merge them into the `viewParams` in the route handler function.
- **Markdown.** The dynamic `ViewDefinitionResponse.viewParams` are available in Markdown content templates. This is notable because in general [context](/docs/platform/lwr/guide/lwr-compile-data.md#understand-context-objects) isn't passed into Markdown templates, *unless* you use a custom route handler.
- **View.** LWR merges the `ViewDefinitionResponse.view` with the `id` and `bootstrap` values from the current route.
- **Time-to-live.** The `CacheResponse.ttl` is a number, in seconds, or a [time string](https://github.com/vercel/ms#examples) to use as the `max-age` on the [`Cache-Control`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header.
- **Supported languages**. You can use both TypeScript and JavaScript to create your route handler.

### Customized Page Response Example

The following is an example of using a route handler function to customize a page response. The LWR server constructs the page response from this function.

```sfdocs-code {"lang":"javascript", "title": "", "src": "" }
import { HandlerContext, RouteHandlerViewResponse, ViewRequest } from '@lwrjs/types';

// Return customized input, from which the LWR server constructs a response
// viewRequest = { url, requestPath, params?, query? }
// handlerContext = { route, viewApi: { hasViewResponse, getViewResponse } }
export default function echoRouteHandler(
  viewRequest: ViewRequest,
  handlerContext: HandlerContext,
): RouteHandlerViewResponse {
  const routeProperties = handlerContext.route.properties || {};
  const message = viewRequest.params.message;

  // return a "ViewDefinitionResponse"
  return {
    // Required: customize the current route by setting:
    // { rootComponent?, contentTemplate?, layoutTemplate? }
    view: {
      contentTemplate: '$contentDir/echo.html',
    },
    // Required: pass context to the templates
    viewParams: {
      message, // pass the "message" path param
      ...routeProperties, // pass the static route properties
    },
    // Optional: rendering options { skipMetadataCollection?, freezeAssets?, skipCaching? }
    renderOptions: {
      freezeAssets: true,
    },
    // Optional: caching options { ttl? }
    cache: {
      ttl: 200,
    },
  };
}
```

### Overridden Page Response Example

The following is an example of using a route handler function to completely override a page response. The LWR server constructs the page response from this function.

```sfdocs-code {"lang":"javascript", "title": "", "src": "" }
import { ViewRequest, ViewResponse } from 'lwr';

// Return a completely custom response
// containing some JSON data, based on the current path and query parameters
// viewRequest = { url, requestPath, params?, query? }
export default function jsonRouteHandler(viewRequest: ViewRequest): ViewResponse {
  const myPathParam = viewRequest.params.my;
  const someQueryParams = viewRequest.query?.some;

  // return a "ViewResponse": { status?, body, cache?, headers? }
  return {
    // Required: return the response body
    body: {
      my: myPathParam,
      some: someQueryParams,
    },
    // Optional: HTTP header map
    headers: {
      'Content-Type': 'application/json',
    },
  };
}
```
