@nativedom/server
v0.1.2
Published
Server-side rendering and streaming for NativeDOM components
Maintainers
Readme
@nativedom/server
Server-side rendering for NativeDOM components. Render full HTML documents or stream them chunk-by-chunk — no client JavaScript required for the initial paint.
Install
npm install @nativedom/core @nativedom/serverQuick start
import { component } from '@nativedom/core';
import { streamPage } from '@nativedom/server';
import * as http from 'node:http';
// Register components server-side
import './components/my-app.js';
http.createServer(async (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
for await (const chunk of streamPage('my-app', {}, {
title: 'My App',
scripts: ['/dist/client.js'],
})) {
res.write(chunk);
}
res.end();
}).listen(3000);API
streamPage(component, props, options?)
Async generator that yields the HTML document in three chunks: <head>, component body, closing tags. The shell arrives at the browser immediately so asset loading starts before the component finishes rendering.
import { streamPage } from '@nativedom/server';
// Node.js http
for await (const chunk of streamPage('app-root', { user: 'alice' }, {
title: 'My App',
head: '<link rel="stylesheet" href="/app.css">',
scripts: ['/dist/client.js'],
})) {
res.write(chunk);
}
res.end();
// Web standard (Fetch / Bun / Deno / Cloudflare Workers)
const stream = ReadableStream.from(streamPage('app-root', props, options));
return new Response(stream, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });renderPage(component, props, options?)
Renders the full HTML document to a string in one call.
import { renderPage } from '@nativedom/server';
const { html, status, headers } = renderPage('app-root', { user: 'alice' }, {
title: 'My App',
scripts: ['/dist/client.js'],
});
res.writeHead(status, headers);
res.end(html);renderFragment(component, props?)
Renders a single component to an HTML string fragment — useful for partial rendering or edge functions.
import { renderFragment } from '@nativedom/server';
const html = renderFragment('product-card', { id: '42' });
// → '<product-card id="42">...</product-card>'Options
All three functions accept the same RenderOptions:
| Option | Type | Description |
|--------|------|-------------|
| title | string | <title> content |
| head | string | Raw HTML injected into <head> (CSS links, meta tags, component styles) |
| scripts | string[] | Module script src paths, injected before </body> |
| storeSnapshot | Record<string, unknown> | Serialized store state injected as window.__NF_STORE__ |
| lang | string | <html lang=""> attribute (default: 'en') |
Injecting component styles
For shadow: false components, collect and inject their @scope CSS via getComponentStyles() from @nativedom/core:
import { getComponentStyles } from '@nativedom/core';
import { streamPage } from '@nativedom/server';
// Import all components first so they register their styles
import './components/my-card.js';
import './components/my-nav.js';
for await (const chunk of streamPage('app-root', {}, {
head: `<style>${getComponentStyles()}</style>`,
scripts: ['/dist/client.js'],
})) {
res.write(chunk);
}SSR islands
Components marked island: true emit a nf-ssr attribute. The client runtime detects this and skips re-rendering — the SSR HTML is used as-is with zero JavaScript:
component('product-card', ({ id }) => {
const product = db.findById(id);
return `<article>...</article>`;
}, { island: true, shadow: false });Store hydration
Serialize server-side store state and rehydrate it on the client:
// server
import { createStore } from '@nativedom/store';
const store = createStore({ theme: 'dark', user: req.user });
renderPage('app-root', {}, {
storeSnapshot: store.snapshot(),
});
// client
import { createStore } from '@nativedom/store';
const store = createStore({ theme: 'light', user: null });
store.rehydrate(window.__NF_STORE__);Related packages
@nativedom/core— Signals, components, html``@nativedom/router— Signal-backed client-side router@nativedom/store— Global reactive store
License
MIT
