Render Web Components on the server
This guide will show you how to serve server-side rendered content with Kopflos.
The problem with Web Components is that styles and Shadow DOM which they encapsulate will only be applied after the components are loaded. This means that the page will be rendered incomplete at first and only finish rendering later. This is known as the flash of unstyled content (FOUC).
By applying server-side rendering, we ensure that the content is available sooner to the user. This is especially important for search engine optimization.
These features are a work in progress.
Prerequisites
First, please make sure to follow the instructions in the Getting Started guide.
Then, install the following dependencies:
npm install @kopflos-cms/vite @kopflos-labs/lit lit
Loading web components in a page
Web Components are a powerful way to encapsulate your UI components, making pages dynamic without sacrificing the declarative nature of HTML.
Since they are just HTML, Kopflos can render any Web Components in the resulting HTML. However, not all Web Components are designed to be rendered on the server. Currently, Kopflos supports rendering lit components which follow the Authoring components for Lit SSR guide.
Let's swap some plain HTML with lit components in the /templates/page.html
file. For the sake of
an
example, these components will mainly encapsulate their styles.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Code in head unchanged -->
</head>
<body>
<template target-class="schema:Person">
<my-header level="1">{{ valueof schema:name }}</my-header>
<template property="schema:address">
<my-dl>
<dt>Street</dt>
<dd>{{ valueof schema:streetAddress }}</dd>
<dt>City</dt>
<dd>{{ valueof schema:addressLocality }}</dd>
<dt>Postal Code</dt>
<dd>{{ valueof schema:postalCode }}</dd>
</my-dl>
</template>
</template>
<script src="/page.js" type="module"></script>
</body>
</html>
In page.js
, we only import the components.
import './my-header.js';
import './my-dl.js';
Below are the components.
// my-header.js
import { html, css, LitElement } from 'lit';
class MyHeader extends LitElement {
static get styles () {
return css`
h1 {
color: red;
}
`;
}
static get properties () {
return {
level: { type: Number }
}
};
render () {
switch (this.level) {
case 1:
return html`<h1><slot></slot></h1>`;
case 2:
return html`<h2><slot></slot></h2>`;
case 3:
return html`<h3><slot></slot></h3>`;
default:
return html`<h4><slot></slot></h4>`;
}
}
}
customElements.define('my-header', MyHeader);
// my-dl.js
import { html, css, LitElement } from 'lit';
class MyDl extends LitElement {
static get styles () {
return css`
dl {
display: grid;
grid-template-columns: auto 1fr;
gap: 1em;
}
`;
}
render () {
return html`<dl><slot></slot></dl>`;
}
}
customElements.define('my-dl', MyDl);
Do refer to lit's documentation for more information on how to create components.
Preprocessing JS code
Loaded from installed node-modules
, Web Components cannot be imported directly in the browser.
Also, some components might rely on APIs that are not available in the browser. Kopflos provides the
package @kopflos-cms/vite
to preprocess the JS code before serving it
to the browser. It also provides a build functionality to produce optimized code for production.
To use it, first add a vite plugin following to your kopflos.config.js
:
export default {
// existing config
plugins: {
'@kopflos-cms/vite': {
root: 'templates',
entrypoints: ['templates/*.html'],
},
},
}
Then add a handler to the chain between <node:@kopflos-cms/serve-file#default>
and
<node:@kopflos-labs/handlebars#default>
. This handler will preprocess the HTML so that JavaScript
code is served by vite.
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX kl: <https://kopflos.described.at/>
PREFIX code: <https://code.described.at/>
<person-page>
# ... abridged
kl:handler
[
a kl:Handler ;
kl:method "GET" ;
code:implementedBy
(
[
a code:EcmaScriptModule ;
code:link <node:@kopflos-cms/serve-file#default> ;
code:arguments ( "templates/person.html" ) ;
]
[
a code:EcmaScriptModule ;
code:link <node:@kopflos-cms/vite/template.js#transform> ;
]
[
a code:EcmaScriptModule ;
code:link <node:@kopflos-labs/html-template#default> ;
code:arguments
(
[
a code:EcmaScriptModule ;
code:link <node:@kopflos-labs/handlebars#default> ;
]
[
a code:EcmaScriptModule ;
code:link <file:lib/templateData.js#describe> ;
]
"/person/${id}"^^code:EcmaScriptTemplateLiteral
) ;
]
)
] ;
.
Rendering on the server
Finally, add another handler to the end of the chain.
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX kl: <https://kopflos.described.at/>
PREFIX code: <https://code.described.at/>
<person-page>
# ... abridged
kl:handler
[
a kl:Handler ;
kl:method "GET" ;
code:implementedBy
(
# ... abridged
[
a code:EcmaScriptModule ;
code:link <node:@kopflos-labs/lit#ssr> ;
code:arguments
(
[ code:link <file:templates/my-header.js> ; a code:EcmaScriptModule ]
)
]
)
] ;
.
By passing modules to the ssr
function, Kopflos will render the components on the server. Here, we
only my-header
. The other component my-dl
will only be running on the client.