@cognivo/ssr
Server-side rendering for Cognivo Lit components. A thin wrapper over @lit-labs/ssr that emits declarative shadow DOM inline — eliminating FOUC in Next.js, Astro, and Remix.
Installation
pnpm add @cognivo/ssr @cognivo/components Peer deps (lit, @lit-labs/ssr) ship as direct dependencies of the package — you do not need to install them explicitly. Requires Node 20+.
Quick start
import { renderToString, html } from '@cognivo/ssr';
import '@cognivo/components';
const rendered = renderToString(html`
<cg-card>
<cg-button variant="primary">Hello SSR</cg-button>
</cg-card>
`);
// rendered contains <template shadowrootmode="open">…</template> blocks
// that modern browsers attach automatically on parse. API Reference
| Export | Signature | Description |
|---|---|---|
renderToString | (t: TemplateResult) => string | Sync render to HTML. Throws if the template contains async directives. |
renderToStringAsync | (t: TemplateResult) => Promise<string> | Async variant — supports Promises, until, streaming children. |
renderToStream | (t: TemplateResult) => ReadableStream<string> | Stream HTML chunks. Pair with new Response(stream) for progressive delivery. |
html | tagged template | Re-exported from lit so consumers do not need a direct Lit dependency. |
TemplateResult | type | Re-exported Lit template type. |
Subpath entries @cognivo/ssr/next and @cognivo/ssr/astro re-export the same helpers for convenience and discoverability.
Framework Examples
Next.js App Router
// app/page.tsx — React Server Component
import { renderToString, html } from '@cognivo/ssr/next';
import '@cognivo/components';
export default function Page() {
const rendered = renderToString(html`
<cg-card>
<cg-button variant="primary">Hello from RSC</cg-button>
</cg-card>
`);
return <div dangerouslySetInnerHTML={{ __html: rendered }} />;
} Import @cognivo/components once from a 'use client' root layout so the custom elements register on the client and hydrate the declarative shadow roots.
Astro
---
import { renderToString, html } from '@cognivo/ssr/astro';
import '@cognivo/components';
const rendered = renderToString(html`
<cg-card><cg-button>Hello Astro</cg-button></cg-card>
`);
---
<Fragment set:html={rendered} />
<script>import '@cognivo/components';</script> Remix — streaming loader
// app/routes/_index.tsx
import { renderToStream, html } from '@cognivo/ssr';
import '@cognivo/components';
export async function loader() {
const stream = renderToStream(html`<cg-dashboard></cg-dashboard>`);
return new Response(stream, {
headers: { 'Content-Type': 'text/html' },
});
} Caveats
- Declarative shadow DOM is supported natively in Chromium 111+, Safari 16.4+, and Firefox 123+. Older browsers render the flat HTML but skip shadow boundaries — styles will leak until the client bundle defines the custom elements.
- For legacy browser support, ship the
@webcomponents/template-shadowrootpolyfill before registering components on the client. renderToStringthrows on async directives (until, Promises in templates). UserenderToStringAsyncorrenderToStreamin those cases.- You still need to import
@cognivo/componentson the client for interactivity — SSR gives you styled markup, not event handlers.