Skip to content
Components, switch product

Server rendering

The elements load in Node without a DOM, so pages rendered on the server can import them. Out of the box, a framework’s server render sends the gui-* tags empty, and the elements fill in once the browser upgrades them. The server-only @golemui/gui-components/ssr entry point renders their content on the server too: labels, inputs and values arrive in the HTML, styled before any JavaScript runs.

  • On the server, each element renders the same markup it renders in the browser, from the values your framework gives it. The rest of the page stays exactly as your framework rendered it.
  • In the browser, nothing extra is needed. The server marks the content it rendered with data-golemui-ssr, and each element replaces that content with its live render when it upgrades. Your framework hydrates the markup it rendered, as usual.

Server rendering needs @lit-labs/ssr 4.1 or later, an optional peer dependency, in the project that renders on the server. @golemui/gui-components/ssr, the entry point that renders the elements, is server-only: it doesn’t load in the browser. Load @golemui/gui-components/index.css at document level, so the server HTML is styled before any JavaScript runs.

The setup for React, Next.js, Angular, Analog, Vue, Nuxt, Lit and Astro is in Installation.

ElementsOn the server
The fields, the pickers and gui-buttonTheir whole content: label, hint, control and value.
The elements that wrap your markup: gui-tabs, gui-accordion, gui-alert and their partsYour children, as your framework rendered them. The roles, the ARIA links and the hidden panels come when the element upgrades.
gui-list and gui-multi-list, which render into a shadow rootTheir items. The list renders around them when it upgrades.

The live render in the browser generates the ids of each element’s parts again, and the element keeps its own label, hint and errors linked. Set a uid only when something outside the element points at those ids, such as your own aria-describedby, a CSS or test selector, or a link.

  • Vue, Nuxt and Astro read attributes only. A value bound as a property is missing from the server HTML and shows once the element upgrades. In Vue and Nuxt, bind arrays and objects with .prop all the same, such as :options.prop="plans": Vue’s production hydration skips the other bindings of a custom element when their value is a constant. A select bound that way shows no options until it upgrades. React and Angular pass properties to the server render.
  • Wrapper elements upgrade in the browser. Until then, every tab panel shows and the tabs have no roles. Put hidden on the inactive tab panels yourself to keep them out of sight in the server HTML: gui-tabs manages that attribute once it upgrades.
  • Next.js development warning. The development server logs Lit’s “Multiple versions of Lit loaded” warning. Next.js bundles a page’s Server Components and Client Components apart, so the same version of Lit loads twice. Production builds don’t log it.
  • Vue streaming. renderElementsInHtml works on HTML, so it fits renderToString. With renderToWebStream or renderToNodeStream, either buffer the stream and pass the whole page, or pass each chunk as it comes. Vue sends a chunk at each async component boundary. An element whose start and end tags land in different chunks stays empty and renders in the browser. Nuxt hands the module the whole page, so it isn’t affected.
  • Render errors. An element that fails to render on the server logs a warning and stays empty, and the browser renders it as usual.