# 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:
- Improves the perceived startup performance by making a page viewable faster, also known as reducing the time to first contentful paint.
- Improves SEO performance as it allows search engines to easily index URLs that would otherwise be rendered on the client as part of a web app.
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 doesn't allow asynchronous operations. Since processing must be done synchronously, asynchronous data dependencies for a component subtree must be retrieved before rendering the component and passed via public properties for it to be rendered.
- You can't execute the
renderedCallback()lifecycle hook on the server. - SSR doesn't use error boundaries nor invoke
errorCallback()even when an error is thrown in component code. - Wire adapters are currently not supported on the server.
# 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.
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:
- The LWC class for use as the root component.
- The already-existing DOM node where the root component should be attached.
- Any props for the root component as part of initial client-side rendering. The props must be identical to those passed to
renderComponentduring 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:
- Attach event listeners and non-serializable props
- Resolve inconsistencies in text nodes in favor of client-side values
- Resolve other unexpected inconsistencies in favor of the client-side VDOM
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.
connectedCallback()is called when the component is inserted into the DOM.disconnectedCallback()is called when the hydrated component is removed from the DOM.render()is called when the component is hydrating and on subsequent renders.renderedCallback()is called after the component is hydrated.
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.