Documentation for Redweb 0.13.0. Install that exact version when following these examples.
Render JSX without React
Build a two-page TypeScript site with shared navigation, a stylesheet and per-page metadata. JSX is a markup syntax here: Redweb renders it on Node.js, without React hooks, hydration, or a browser component runtime. This is useful for documentation, content sites and server-rendered pages that do not need browser-side component execution.
Explain it like I'm five
The page class is a recipe and the server is the kitchen. render() prepares HTML before it reaches the browser. A shared layout adds the same navigation around each page, like putting different meals on the same kind of plate. The browser receives the finished document, not the kitchen.
Follow the design
- The initializer supplies
redweb/tsconfig.jsoninheritance, TypeScript, the stylesheet and the entrypoint helper. Keep the file as.tsx; do not point its JSX settings atreact/jsx-runtime. defineSite()supplies one layout and CSS declaration. Its page decorators register/and/about, with metadata beside each page.- Each
render()returns ordinary TSX. Function components can share presentation; page-specific data remains in your server code. Text and attribute values are escaped, and URL protocols are restricted. createApp()combines both pages on one listener. The standalone entrypoint owns bounded shutdown through the shared helper; importing the module starts nothing.
Keep CSS in external files. The rendering reference covers components, templates, assets and static export. For interactive pages, start from the realtime counter: assignments to decorated state update the browser through Redweb's runtime. Non-live site pages do not acquire that behavior just because their markup is JSX.
Check that it works
Open http://localhost:8181/, follow About, and confirm the navigation and styling stay consistent while the title and content change. View the response source: it is server-rendered HTML, not an empty mount point. The shipped test checks actual HTTP responses, both pages, CSS and absence of the live-page runtime. The package gate repeats it with the source directory unavailable.
npm run build produces compiled code and copied assets; npm start serves that output. site.export() is a separate static-export workflow, not what the starter's default build does. Static export cannot replace protected or live application requests.
When to choose another approach
Redweb TSX is not React-compatible. Do not import React components or expect hooks, client effects, browser rendering or automatic support for browser-only libraries. Choose an appropriate browser framework when those are core requirements. Live Redweb applications require a Node host with long-lived listeners; exported static files have a different deployment model. See compatibility and release verification.
Build and run the complete application
npx --yes redweb@0.13.0 init my-site --template site
cd my-site
npm install --save-exact redweb@0.13.0
npm test
npm run devThe complete site recipe contains every generated file, its real acceptance tests, and deployment instructions. The source below is one of those files, not a standalone program; initialize the whole project before modifying it.
Source walkthrough: src/app.tsx
import { defineSite, start, type LiveHtmlStartOptions } from 'redweb';
import { runApp } from './run-app';
const site = defineSite({
css: 'app.css',
layout: content => <body><nav><a href="/">Home</a> · <a href="/about">About</a></nav>{content}</body>,
});
@site.page('/', { head: { title: 'My Redweb site', description: 'A server-rendered TypeScript site.' } })
export class HomePage {
render() {
return <main class="home"><h1>Your server-rendered app is ready.</h1><p>Edit src/app.tsx to make it yours.</p></main>;
}
}
@site.page('/about', { head: { title: 'About' } })
export class AboutPage {
render() { return <main class="home"><h1>About</h1><p>Shared layout, separate pages, no browser JavaScript.</p></main>; }
}
export function createApp(options: LiveHtmlStartOptions = {}) {
return start([HomePage, AboutPage], { port: Number(process.env.PORT ?? 8181), templateRoot: __dirname, ...options });
}
if (require.main === module) runApp(createApp);Complete setup, files and tests →