# Server-Side Rendering (Developer Preview)

Server-side rendering (SSR) enables you to render a component on the server, providing several advantages over client-side rendering.

Note

Server-side rendering is available as a developer preview and it isn't available on the Salesforce Platform. See LWC Open Source.

SSR is widely used on web apps for these key reasons:

Server-side rendering is usually a two-step process. First, the server renders a component tree to HTML. The generated HTML is then sent to the client to be hydrated. Hydration involves recreating the LWC component tree while reusing the DOM elements already present on the client to add interactivity.

# Render a Component on the Server

To render a component on the Server, import the renderComponent method from @lwc/engine-server.

import { renderComponent } from '@lwc/engine-server';
import App from 'x/App';

// Example using express.js
app.get("/ssr-app", (req, res) => {
  const name = req.query.name || 'World';
  const ssrRenderedApp = renderComponent('x-app', App, { name });

  return res.send(ssrRenderedApp);
});

The example renders this markup on the server.

<x-app>
    #shadowRoot
    Hello <b>World</b>
    <button>Show greeting in modal</button>
</x-app>

# Key Differences with Client-Side Rendering

While there are clear advantages to server-side rendering as discussed in the beginning of this article, frequent server requests can be a drawback. Also, users can experience an overall slowdown in page rendering if the server has a large number of users or if the site is highly interactive. Contrastingly, client-side rendering (CSR) can be a better solution if your site has substantial interactivity, where a slower first load is offset by faster subsequent page loads.

Additionally, note these key differences with client-side rendering.

# No Access to Web Platform APIs

LWC doesn't polyfill web platform-specific APIs for server environments. Accessing any of those APIs as the component renders on the server results in a runtime exception. For example, if a server-rendered component wants to attach event listeners to the document on the client, it must check if the document object is present.

import { LightningElement } from 'lwc';

// Evaluates to true when running in a browser, or false otherwise.
const isBrowser = typeof document !== 'undefined';

export default class App extends LightningElement {
    connectedCallback() {
        if (isBrowser) {
            document.addEventListener('click', this.handleDocumentClick);
        }
    }

    disconnectedCallback() {
        if (isBrowser) {
            document.removeEventListener('click', this.handleDocumentClick);
        }
    }

    handleDocumentClick() { /* Do something */ }
}

# Synchronously Render the Component Tree

In SSR, the entire component tree is synchronously rendered in a single pass, matching the current behavior of the LWC engine. Connecting a component to a document triggers a synchronous rendering cycle, making reactivity redundant on the server. Disabling reactivity also improves the overall SSR performance by eliminating the use of JavaScript Proxies.

# Additional SSR and CSR Differences

Consider these differences when working with SSR and CSR.

# SSR hydration

SSR hydration involves recreating the LWC component tree while reusing the DOM elements already present on the client to add interactivity.

Note

While SSR hydration supports native shadow DOM and light DOM components, it doesn't support synthetic shadow DOM components.

When a user requests a page, the server fetches user-specific data and renders the HTML for the app using SSR. The server then sends the HTML back to the client, which proceeds to download the JavaScript. Contrastingly, when a user requests a page using client-side rendering, the page is blank when JavaScript is loading and viewable only after the browser downloads and executes the JavaScript.

Server-side rendering makes your page viewable faster.

On the client, the browser renders the SSR-generated HTML. But clicking the button reveals that the HTML is static. To make the application interactive, you must hydrate the SSR rendered application. During the hydration, LWC re-creates the component state, attaches event handlers, and reconsolidates state discrepancies between the server and client-generated HTML.

To hydrate an application, use the hydrateComponent API instead of createElement:

import { hydrateComponent } from 'lwc';
import App from 'x/App';

const elm = document.querySelect('x-app');
hydrateComponent(elm, App, { name: 'World' });

# hydrateComponent()

hydrateComponent() accepts three parameters:

  1. The LWC class for use as the root component.
  2. The already-existing DOM node where the root component should be attached.
  3. Any props for the root component as part of initial client-side rendering. The props must be identical to those passed to renderComponent during SSR.

After hydrateComponent() is called, LWC instantiates the root component and generates client-side VDOM.

Next, LWC patches SSR DOM with client-side VDOM, which includes:

LWC logs an error when the HTML generated on the server doesn't match the HTML generated in the client. For example, an error is logged when hydrating an element with mismatches on its styles, classes, and other attributes.

# Handling Mismatches during hydration

An unexpected mismatch can happen during hydration when the DOM rendered by the server is different than the Client VDOM. LWC will resolve mismatches by modifying the existing DOM to always match the one represented by the Client VDOM.

# Component Lifecycle

The component lifecycle hooks are invoked during hydration.

The hydration process handles template directives as expected. During hydration, stylesheets are attached to the DOM with several differences across native shadow and light DOM components.

On the rare occasion that an error is thrown while you invoke the connectedCallback() or another lifecycle hook, LWC assumes that an unexpected mismatch has occurred. The SSR DOM is then replaced with the CSR VDOM and LWC invokes the lifecycle hook again for the CSR DOM.

If an error is thrown during connectedCallback() after hydration succeeds where the component is now behaving like a CSR component, then the default behavior of errorCallback() takes place.

# SSR Considerations

SSR and hydration don't provide any security counter measures. You are responsible for testing and assessing the security of your SSR and hydration implementation.

For example, take precautions on cross-site scripting (XSS) attacks and code injections. Server-side JavaScript injection allows an attacker to execute malicious code on the server. We recommend validating your requests to prevent unwanted code or data on the rendered HTML. If you're passing in a JSON string, you can also sanitize your output to remove unsafe characters. See website security.

# SSR for Declarative Shadow DOM

Declarative shadow DOM enables you to bring shadow DOM to the server. Although most modern browsers support the shadow dom API, only Chrome supports declarative shadow DOM. To take full advantage of SSR beyond Chrome browsers, use the declarative shadow DOM polyfill.

# Declarative Shadow DOM Polyfill

To polyfill declarative shadow DOM, we recommend the approach that uses a single script to attach the shadow roots on all templates and move the template contents into each root. Using a single script optimizes the page for speed since the entire page is parsed first. See the Declarative ShadowDOM polyfill RFC: Single loop script shadow root attachment. Alternative approaches are described in the Declarative ShadowDOM polyfill RFC.

# Feedback and Comments

During this Developer Preview, we are continuously collecting usage metrics to better understand the behavior and performance of SSR and hydration in our customers' solutions. Please share your feedback, report bugs, or make feature requests in the LWC repo.