Server elements
Gemkit can split a page into parts that are rendered on the server, loaded on demand, or only run in
the browser. The decorators come from
gem-ssr and are re-exported by
@mantou/gemkit.
@server()
A class marked with @server() is only rendered on the server. Its code and dependencies are not
bundled into the client, so it can use Node.js APIs and server-only packages. The HTML is kept in the
page, and the client neither loads the code nor hydrates it.
@customElement('app-stars')
@server()
class AppStarsElement extends GemElement {
@template()
#content = () => html`<span>${countStars()}</span>`;
}There is no client-side behavior after it is rendered. Use it for content that never changes in the browser and is expensive to send to every user.
@server() can also decorate a method or field. It runs during server rendering and its state is
serialized with the element, so hydration can use the result and skip the first execution. The value
must be JSON-serializable.
@lazy()
A @lazy() element is rendered on the server, and its HTML is used until the client loads its code
and hydrates it. The code is bundled into a separate client chunk, so it must be isomorphic.
@customElement('app-like-button')
@lazy({ on: 'visible' })
class AppLikeButtonElement extends GemElement {}on |
When the code is loaded |
|---|---|
'interaction' |
On the first user interaction, the default. |
'load' |
After the page is loaded, when the browser is idle. |
'visible' |
When the element enters the viewport. |
When on is 'interaction', the event that triggered the load is replayed on the original target
(pointerdown, click, focusin, keydown or input). This keeps the first click from being lost.
Use it for parts that are rarely used, such as a like button or a comment box, to keep the initial client bundle small.
@client()
A @client() element is only rendered on the client. The server outputs the tag only, including its
attributes and child content, and the client renders it normally without hydration. The module can use
browser-only code, even at the top level:
@customElement('app-editor')
@client()
class AppEditorElement extends GemElement {}Because the module is replaced with an empty module in the server build, it can't contain other exports or elements.
How client navigation gets the HTML
On the first load, server element HTML is already in the document. When the page is created on client navigation, Gemkit fetches the HTML:
- from the history cache of the current entry, when going back or forward
- from
<page path>/__elements.json, which is written when prerendering - with one batched
POST /__gemkit_elementrequest for all elements rendered in the same round
The on-demand request is not available on a static host. If a page with server elements is not prerendered, client navigation to it needs a server.
__elements.json is requested even when a page has no server elements, because the client only knows
after rendering. When prerendering is disabled, the client skips this request and renders elements on
demand.
Prefetch
When a <gem-link> is hovered or focused, Gemkit prefetches the target page modules, its layouts, the
server data (__data.json) and the server element HTML (__elements.json). The results are used once
within 30 seconds. Back and forward navigation restores the state of the history entry without a
request.
See
Security
Server element requests contain a URL, attributes and properties chosen by the client, and any element
can be requested with any URL. Path checks in handle don't protect server elements. Check the
permission inside the element:
@customElement('app-admin-stats')
@server()
class AppAdminStatsElement extends GemElement {
@template()
#content = () => (getRequestEvent().locals.user?.admin ? html`...` : html``);
}Stores
Module-level stores updated while rendering the server element are sent to the client, and hydration uses the server values. Store state must be JSON-serializable.
