# Chapter 19: SSR, Hydration, and Deployment

> ⚠ **Planned — not in Loup 1.25.** This chapter describes the design in the Language Reference (§2-4.7 for server-side rendering, hydration and state serialization, §1-16.6.7–§1-16.6.8 for `SafeHtml`, §1-20.6 for the browser build and bootstrap). The toolchain does not implement it yet, so its listings are design sketches and are not verified.

The Reference describes a path to production: render the first page on the server, send HTML the browser can show at once, then *hydrate* it so the existing DOM becomes reactive without being rebuilt. This chapter starts with what you can build for the browser today.

## Building for the browser today

Loup 1.25 compiles a web project to a WebAssembly module, and nothing more. Unlike the rest of this chapter, this section's listing runs today and is checked.

Create a project with `loup-bootstrap init shop`, set `entry = "src/main.lpweb"` under `[build]` in `shop/shop.lppkg`, and write `src/main.lpweb`:

````loup
@mode(web.compiled)

func productCard(name: string, stock: int): string {
    let card = ```(html):(none)
    <article class="product">
      <h2>{$ name $}</h2>
      <p>{$ stock $} in stock</p>
    </article>
    ```;
    return card.render({});
}

func main(): void {
    println(productCard("Desk lamp", 3));
}
````

*Listing 19-1: A `web.compiled` entry that renders an `html` fence*

```sh
$ loup-bootstrap build --target browser
Resolving dependencies...
Compiling 1 module(s)...
Generating code...
Build complete: …/shop/build/shop.wasm (20311 bytes)
…/shop/build/shop.wasm
$ ls build
shop.wasm
$ loup-launcher --run-bare build/shop.wasm
<article class="product">
  <h2>Desk lamp</h2>
  <p>3 in stock</p>
</article>
```

That bare module is the whole output:

- `--delivery` is the current spelling of `--target`; both work, and the shape is `browser` (`--target web` is an unknown delivery shape). `--release`/`--debug` pick the profile; `-o` names the output file.
- There is no `dist/`, `index.html`, `loup-rt.js` or source map (not even with `--debug`), no `loup serve`, and `--inspect` is an unknown flag. No browser can load the module yet; `loup-launcher --run-bare` runs it and calls `main()` (ruling ENTRY-1).
- Compiled Wasm is experimental and not audited; for example, an `i"…"` string currently traps under the launcher (Chapter 1).
- On Linux x86-64 `loup` is the self-hosted driver, which refuses Wasm builds; use `loup-bootstrap build`. On macOS and Windows they are the same program.

The rest of the chapter is the planned design.

## Server-side rendering

`ui.web.renderToString(view, options)` renders a component tree to HTML on the `service` target and returns `SafeHtml` (§2-4.7), which an outer `html` fence inserts without escaping it again (§1-16.6.8):

````loup,ignore
@mode(web.compiled)

from ui import (View, signal);
from ui.web import (renderToString, RenderToStringOptions);

pub func ProductPage(name: string, stock: int): View {
    let count = signal(stock);
    return ```(html):(none)
    <main>
      <h1>{$ name $}</h1>
      <p>In stock: {$ count.get() $}</p>
    </main>
    ```;
}

pub func renderProductPage(name: string, stock: int): SafeHtml {
    let options = RenderToStringOptions(stateIntegrity = true);
    return renderToString(ProductPage(name, stock), options);
}
````

*Listing 19-2: Rendering a component on the server (design sketch)*

`SafeHtml` itself does not exist yet (Chapter 14). The Reference also does not settle which `@mode` a server module declares: `ui.web` is listed for the `browser` and `service` targets, but `service` belongs to the host modes, whose UI backend is `ui.desktop` (§1-2.5.2, §1-2.6).

## State serialization and hydration

The server writes the initial signal values into a JSON block for the client:

```html
<script id="__loup_state__" type="application/json" data-integrity="9f86d081884c7d65…">
  {"count": 3, "note": "a </script> inside a string is harmless"}
</script>
```

Every `<` and `>` is written as `<` and `>`, so the block never contains `</` and a string cannot close the `<script>` element. With `stateIntegrity = true` the block carries the hex SHA-256 of its content, which `hydrate` checks before using the state.

On the client, `hydrate(view, selector)` attaches handlers and signals to the server-rendered DOM instead of creating nodes, and returns a `MountHandle`. If the markup does not match, the runtime warns and falls back to full client rendering.

```loup,ignore
@mode(web.scripted)

from ui.web import (hydrate);
from product import (ProductPage);

let handle = hydrate(ProductPage("Desk lamp", 3), "#root");
```

*Listing 19-3: The client entry hydrates the page (design sketch)*

## Content Security Policy

The minimum policy for a Loup browser app (§2-4.7) is:

```text
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'wasm-unsafe-eval';
  connect-src 'self';
```

`'wasm-unsafe-eval'` lets the page compile Wasm without allowing JavaScript `eval()`. `'unsafe-inline'` is unnecessary because event handlers become `data-loup-ev` attributes (Chapter 16) and state travels as JSON. Set the header in your server or CDN.

## The planned build output

The Reference's non-normative description of `loup build --target browser` (§1-20.6) is a `dist/` directory with `index.html`, a versioned `loup-rt.js`, `app.wasm` with `.br` and `.gz` copies, `app.wasm.map` under `--debug`, and `assets/`. On page load `loup-rt.js` would run: instantiate → initialize → register custom elements → wire events → hydrate → mount.

To deploy it, serve `.wasm` as `application/wasm` (or streaming compilation is lost), serve the compressed variants, preload `app.wasm`, cache the versioned files as `immutable`, and serve source maps as `application/json` with `Access-Control-Allow-Origin: *`. `loup serve` would rebuild on change and, with `--inspect`, start the browser debug bridge, which must never reach production.

## Summary

- Today `loup-bootstrap build --target browser` (alias `--delivery`) writes one `build/<name>.wasm` that runs under `loup-launcher --run-bare`; there is no `dist/`, HTML shell, `loup-rt.js`, source map, `loup serve` or `--inspect`.
- Planned: `renderToString` turns a `View` into `SafeHtml` on the `service` target; `hydrate` attaches reactivity to that HTML on the client.
- Server state travels in a `__loup_state__` JSON block with `<` and `>` escaped, optionally guarded by a SHA-256 `data-integrity` hash.
- The minimum CSP needs `'wasm-unsafe-eval'` and no `'unsafe-inline'`.
- The planned `dist/` layout, bootstrap order and serving rules are in Language Reference §1-20.6.
