106 lines
6.0 KiB
Markdown
106 lines
6.0 KiB
Markdown
# @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
|
|
|
|
```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: ["/_wire/runtime.js", "/_wire/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">`.
|
|
|
|
## 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).
|