Server-Side Pages
Sometimes you don't need real-time reactivity. You need a page that renders on the server, fetches some data, and sends complete HTML to the browser. SEO, initial page loads, static-ish content — that's what server-side pages are for.
Server Shell
The <server-shell> element defines the HTML document wrapper for all <server-page> elements. It provides the <head> content — title, meta tags, stylesheets, scripts — and optional CSS classes for <html> and <body>. Only one per forest.
<forest>
<server-shell html-class="no-js" body-class="server-rendered">
<title>My Application</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/style.css" />
<script src="/app.js"></script>
</server-shell>
</forest>
| Attribute | Purpose |
|---|---|
html-class |
CSS class(es) added to the <html> element |
body-class |
CSS class(es) added to the <body> element |
Child elements are extracted by tag: <title>, <meta>, <link>, and <script> go into <head>.
Server Page
<server-page> elements define server-rendered pages with optional authentication:
<forest>
<server-shell>
<title>My Application</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/style.css" />
</server-shell>
<server-page uri="/dashboard" authenticate="identity">
<h1>Welcome</h1>
<remote-inline method="GET" url="/api/stats" />
</server-page>
</forest>
A request to /dashboard produces a complete HTML document:
<!DOCTYPE html>
<html>
<head>
<title>My Application</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<h1>Welcome</h1>
<!-- resolved remote-inline content -->
</body>
</html>
If a <server-page> provides a title override, it replaces the shell's default title.
Authentication
The authenticate attribute on <server-page> requires a valid identity. When SSR detects no authenticated principal, it returns a 302 redirect to the login page. No half-rendered pages leaking out to unauthenticated users.
Remote Inline
The <remote-inline> element fetches data from a URL during server-side rendering and inlines the result:
<server-page uri="/api/status" authenticate="identity">
<remote-inline method="GET" url="/api/health" />
</server-page>
Redirects from Remote Inline
When the backend @web handler returns a forward or redirect response, the SSR pipeline converts it into an HTTP redirect sent to the browser before any page HTML is rendered:
<server-page uri="/go/$code:text">
<remote-inline path="/resolve" />
</server-page>
@web get /resolve {
let code = @parameters.str("code", "");
if (code == "home") {
return { forward: "/dashboard" };
}
return { html: "<p>Unknown code</p>" };
}
The forward field produces a 302 (temporary redirect) and redirect produces a 301 (permanent redirect). The redirect happens before any HTML reaches the client — clean and fast.