Runnable, self-contained apps that wire @webtides/element-js-ssr-renderer into a
specific meta-framework. Each one SSRs the same kinds of @webtides/element-js custom
elements — some authored locally, some from @webtides/element-library — to Declarative
Shadow DOM (and light DOM), then hydrates them in the browser.
| Framework | Directory | Status | SSR hook used |
|---|---|---|---|
| Node (Express) | node/ |
✅ available | Connect-style middleware (elementSSR) |
| Astro | astro/ |
✅ available | onRequest middleware (elementSSR) |
| Nuxt | nuxt/ |
✅ available | Nitro render:response hook (elementSSR) |
| SvelteKit | sveltekit/ |
✅ available | handle hook (transformPageChunk) |
| Vite | vite/ |
✅ available | transformIndexHtml hook (elementSSR, build-time) |
| Eleventy (11ty) | eleventy/ |
✅ available | addTransform hook (elementSSR, build-time) |
The Node/Astro/Nuxt/SvelteKit examples pre-render per request on a server. (The Node one is the bare-metal case — a plain Express server with no meta-framework; its middleware works on any
(req, res, next)server, seedocs/frameworks/node.md.) The Vite example is different: it's a plain, server-less MPA / static-HTML build that pre-renders the elements authored inindex.htmlat build time, emitting fully-rendered static HTML. Same renderer, sameresolvesources — just a build hook instead of a request hook.
Each example is fully self-contained — its own package.json, its own copy of the
local components, its own framework wiring — so it doubles as a copy-pasteable blueprint.
They link the renderer from this monorepo via a relative file: dependency (so the
sibling package must be present), and pull @webtides/element-library from npm.
The renderer is framework-agnostic: it takes an HTML string and returns one with the custom elements pre-rendered. So integrating it into any framework is always the same three moves — only the names of the hooks differ:
- Install the DOM shim first. Import
@webtides/element-js-ssr-renderer/dom-shimbefore any component module loads (component classes areclass … extends HTMLElement, evaluated at import time). In each example this is the first import in the file that owns the SSR hook. - Wrap the framework's HTML response. Take the rendered HTML the framework hands you
and run it through
await renderToString(html, { resolve }), then return the transformed HTML. Resolution composes component sources — element-library via its own shipped@webtides/element-library/catalogplus the project's own components viaimport.meta.glob(...)(or a generated catalog on Nitro). See Resolving components. - Load
defineon the client. Ship a<script>that imports each component's…/define(or calls a localdefine()), so the pre-rendered elements upgrade and hydrate in place rather than re-rendering.
Where each framework exposes those:
| Step | Astro | Nuxt (Nitro) | SvelteKit |
|---|---|---|---|
| Owns the SSR hook | src/middleware.js |
server/plugins/element-ssr.js |
src/hooks.server.js |
| Gets the HTML | (await next()).text() |
render:response body |
transformPageChunk({ html }) |
| Returns transformed | new Response(transformed, …) |
mutate response.body |
return the transformed chunk |
Step 2 is small and repetitive, so the package ships a thin adapter per framework that does
it for you, published as a stable subpath export and living under
src/adapters/:
| Adapter export | Status | What it gives you |
|---|---|---|
…/node |
✅ available | elementSSR(options) → a Connect-style (req, res, next) middleware |
…/astro |
✅ available | elementSSR(options) → an onRequest middleware |
…/nuxt |
✅ available | elementSSR(options) → a Nitro render:response handler |
…/sveltekit |
✅ available | elementSSR(options) → a handle hook (transformPageChunk) |
…/vite |
✅ available | elementSSR(options) → a Vite plugin (transformIndexHtml, build-time) |
…/eleventy |
✅ available | elementSSR(options) → an Eleventy transform (addTransform, build-time) |
Adapters that deal in Response objects (Astro, Nuxt) share one internal kernel,
transformHtmlResponse — content-type gate, read the
body, renderToString, re-wrap preserving status/headers. SvelteKit's transformPageChunk
hands you the HTML string directly, so its adapter just calls renderToString(html, opts)
and needs no Response kernel. The Node adapter is lower-level still: a plain Node server gives no
Response and no string hook, so it buffers res.write/res.end itself and transforms the
collected body on end. New adapters land with their example and a test — see the checklist
below.
- Create
examples/<framework>/with its ownpackage.json; depend on the renderer (file:../..),@webtides/element-library(from npm),@webtides/element-js, and the framework. - Add the framework adapter under
src/adapters/<framework>.js, export it as./<framework>in the packageexports, and cover it with a test (mirrortest/astro.test.js). ReusetransformHtmlResponseif the framework isResponse-based; otherwise callrenderToStringdirectly. Then implement the three moves above in the example's SSR hook, using that adapter. - Reuse the two local components from
astro/src/components(x-counter, a shadow/DSD component, andx-greeting, a light-DOM one) so every example demonstrates both render paths and stays comparable. - Add a short
README.md(run steps + "what to look for") and a row to both tables above. - If the framework bundles the server (Astro/SvelteKit/Nuxt all can), watch for the
import-order gotcha the Astro example documents: a bundler may hoist element-js' import
above the inlined DOM-shim side effect. The Astro example fixes it with
vite.ssr.noExternal; the equivalent escape hatch is preloading the shim (node --import @webtides/element-js-ssr-renderer/dom-shim …).