# @wrnexus/ssr

> Server-side rendering: wraps a page's HTML body in a complete HTML document with a metadata-driven `<head>`.

Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.

## Overview

Pages in WrNexus return an HTML string for the body. `@wrnexus/ssr` takes that body and produces a full HTML document — building the `<head>` from page metadata and global SEO defaults, resolving canonical/Open Graph/Twitter tags, and injecting module preloads and `<script type="module">` tags. It is deliberately server-only: nothing in this package touches the DOM or ships to the browser, keeping server code genuinely server-only. Reach for it on the server when turning a rendered page body into a response document.

## Installation

```bash
bun add @wrnexus/ssr
```

> Private package — the machine must be authenticated to the `wrnexus` npm org
> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported).

## API

The package has a single export.

### `renderDocument(opts: RenderOptions): string`

Renders a complete HTML document as a string, beginning with `<!doctype html>`. All metadata is HTML-escaped (via `escapeHtml` from `@wrnexus/core`), so a malicious title or description cannot break out of its element or attribute. The body is placed inside `<div id="app">`.

#### `RenderOptions`

| Field          | Type        | Description                                                                                                                                    |
| -------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `meta`         | `PageMeta`  | Page metadata for the document head (required).                                                                                                |
| `body`         | `string`    | Rendered HTML for the body, placed inside `#app` (required).                                                                                   |
| `seo`          | `SeoConfig` | Global SEO defaults, typically from `wrnexus.config.ts`.                                                                                       |
| `url`          | `URL`       | Current request URL, used to resolve canonical/Open Graph URLs.                                                                                |
| `scripts`      | `string[]`  | URLs of `<script type="module">` tags to load (e.g. per-island chunks or the reactive runtime). Each also gets a `<link rel="modulepreload">`. |
| `defaultTitle` | `string`    | Default document title used when `meta.title` is absent.                                                                                       |
| `extraHead`    | `string`    | Raw HTML injected at the end of `<head>` (trusted, framework-controlled — not escaped).                                                        |
| `extraBody`    | `string`    | Raw HTML injected at the end of `<body>` (trusted, framework-controlled — not escaped).                                                        |
| `htmlAttrs`    | `string`    | Attributes for the `<html>` element, e.g. ` data-theme="dark"` (trusted).                                                                      |

`PageMeta` and `SeoConfig` come from `@wrnexus/core`. `PageMeta` is an alias of `SeoConfig`, whose fields are all optional:

```ts
type SeoConfig = {
  title?: string;
  titleTemplate?: string; // e.g. "%s — My Site"; %s is replaced with the page title
  description?: string;
  canonical?: string;
  canonicalBase?: string; // origin used to absolutize canonical/image URLs
  robots?: string;
  keywords?: string | string[];
  image?: string;
  siteName?: string;
  type?: string; // Open Graph type; defaults to "website"
  locale?: string;
  twitterCard?: string; // defaults to "summary"
  twitterSite?: string;
  themeColor?: string;
};
```

#### Metadata resolution

`renderDocument` merges page metadata (`meta`) over global defaults (`seo`), field by field, so per-page values win. Notable behavior:

- **Title**: uses `meta.title`, else `seo.title`, else `defaultTitle`, else `"WrNexus"`. When the page sets its own title and `seo.titleTemplate` contains `%s`, the template is applied.
- **Canonical / image URLs**: resolved against `canonicalBase` (or the request `url`'s origin) into absolute URLs when possible.
- **Keywords**: an array is joined with `", "`.
- **Emitted tags**: `<title>`, and as applicable `description`, `robots`, `keywords`, `theme-color`, and `canonical` link, plus Open Graph (`og:title`, `og:description`, `og:type`, `og:url`, `og:site_name`, `og:locale`, `og:image`) and Twitter (`twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`, `twitter:site`) meta tags. The document always includes `charset`, `viewport`, and a `/favicon.ico` icon link.

## Usage

### Render an SEO-ready application page

```ts
import { renderDocument } from "@wrnexus/ssr";

const html = renderDocument({
  meta: {
    title: "About Us",
    description: "Learn more about our team.",
  },
  seo: {
    titleTemplate: "%s — Acme",
    siteName: "Acme",
    canonicalBase: "https://acme.example",
    twitterSite: "@acme",
  },
  url: new URL("https://acme.example/about"),
  body: "<h1>About Us</h1>",
  scripts: ["/_wrn/runtime.js", "/_wrn/islands/about.js"],
  htmlAttrs: ' data-theme="dark"',
});

return new Response(html, {
  headers: { "content-type": "text/html; charset=utf-8" },
});
```

The produced document has `<title>About Us — Acme</title>`, the SEO/Open Graph/Twitter tags derived from the merged metadata, a `modulepreload` link and module `<script>` for each entry in `scripts`, and the body wrapped in `<div id="app">`.

### Add trusted framework assets and boot data

Use `extraHead` and `extraBody` only for HTML generated by your application or the
framework. User-provided values belong in `meta`, where they are escaped.

```ts
const html = renderDocument({
  meta: { title: "Dashboard", robots: "noindex" },
  body: dashboardHtml,
  url: ctx.url,
  extraHead: '<link rel="stylesheet" href="/_wrnexus/admin.css">',
  extraBody: `<script type="application/json" id="boot">${JSON.stringify(bootData).replaceAll("<", "\\u003c")}</script>`,
});

return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
```

## Requirements / Notes

- **Server-only.** This module never imports or touches the DOM and is safe to keep out of client bundles.
- **Depends on [`@wrnexus/core`](../core)** for `escapeHtml` and the `PageMeta` / `SeoConfig` types.
- **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime (Node is not supported).
