SSR and hydration
Render server-safe output without DOM access at module evaluation or cross-request runtime state.
Render real content on the server, then hydrate it in the browser. Keep browser registration and per-request state out of shared modules.
Setup steps#
- 1
Render on the server
Call
renderAeliqofrom@varendo/web/serverwith a host-authored Lit template. Keep element registration in a client-only entry. - 2
Isolate each request
Create authority and runtime state per request. Abort downstream work when the request ends.
- 3
Hydrate in order
Load Lit's hydrate support before importing
@varendo/web/appor registering elements. - 4
Verify the result
Check the HTML is useful without JavaScript and hydration adds no duplicates or resets.
Server boundary#
Use @varendo/web/server for server rendering and keep browser registration inside a client entry. Serialize initial data as separately escaped JSON, not interpolated HTML.
Render authorized content for the current request before sending HTML. A
placeholder that becomes useful only after a client effect is a client-rendered
surface, not meaningful SSR. The server may render Varendo custom elements
through renderAeliqo. A React local data surface also renders
read-only rows on the server when supplied valid initial data.
server-entry.ts
import { html } from 'lit';
import { renderAeliqo } from '@varendo/web/server';
// Render a trusted host-authored element tree for this request's data.
const markup = await renderAeliqo(html`<aeliqo-text-field label="Name"></aeliqo-text-field>`);
renderAeliqo accepts a host-authored Lit TemplateResult and returns the
HTML string. Install lit alongside the matching Varendo packages; keep this
import in a server-only module.
Request isolation#
- Create authority and runtime state per request or explicit application security context.
- Never store principal, ResultStore, Region, or action confirmation in a process-wide singleton.
- Abort downstream source work when the request ends.
- Do not import provider SDKs or browser-only registration into the server-rendered component path.
Hydration order#
Load @lit-labs/ssr-client/lit-element-hydrate-support.js before dynamically importing @varendo/web/app or registering Varendo elements. Loading a Lit element class first can cause the declarative shadow tree to be replaced instead of hydrated.
client-entry.ts
import '@lit-labs/ssr-client/lit-element-hydrate-support.js';
const { createAeliqoApp } = await import('@varendo/web/app');
// Now create the app, register elements, and mount Regions.
Hydration checks#
- With JavaScript disabled, the HTML contains the authorized label, rows, or values that the page promises.
- Declarative shadow roots hydrate without duplicate nodes or listeners.
- Existing input and dirty draft values are not reset.
- Focus order and form association remain correct.
- Initial browser data is reauthorized before reuse; do not serialize credentials, private rows, or executable values.
- A no-JavaScript or failed-hydration fallback stays readable and honest.
- Server and browser packages use the same exact version.
Use the repository's pinned Node.js 24.20.0 and pnpm 11.24.0 toolchain. From a checkout of the source revision you want to evaluate:
pnpm install --frozen-lockfile
pnpm build:platform
pnpm --filter @varendo/next-platform-fixture build
pnpm --filter @varendo/next-platform-fixture start
Open http://127.0.0.1:4174. The fixture in examples/next-platform renders a
text field and an adaptive People surface. Inspect these files in order:
app/layout.tsxestablishes the document and hydration boundary.app/page.tsxsupplies the server-rendered page.app/adaptive-people.tsxowns the People surface.app/hydrate.tsxprepares client hydration in the required order.
Disable JavaScript and reload: initial content must remain useful. Re-enable
JavaScript and verify interaction without duplicated elements or reset values.
Stop the server, install the required Playwright browser if needed, then run
pnpm test:next-platform from the repository root for the maintained automated
SSR and hydration checks.
If the build reports a missing Varendo package entry, run pnpm build:platform
first. If the page reports a browser-global error on the server, inspect its
client boundary. If shadow trees duplicate, check the hydration import order
above before changing the rendered data.
The separate request-scoped table fixture in examples/vnext/src/ssr.ts builds
authorized rows per request and escapes its public JSON snapshot. Both fixtures
use synthetic records. A real host authenticates each request and applies its
own cache policy; never reuse one user's rendered result for another user.
