React Server Components (RSC) extension for @webtypen/webframez-core.
@webtypen/webframez-react provides:
- seamless
webframez-coreintegration viainitWebframezReact(Route) - file-based routing for
pages/**/index.tsx - layout/error handling with
RouteChildren - client-side navigation (
Link,Redirect,useRouter) and cookies (useCookie) - server-rendered initial HTML plus RSC streaming
- a small CLI for the standard server/client build pipeline
- Node.js
>= 20 @webtypen/webframez-core- React Experimental (matching the package peer dependencies)
npm i @webtypen/webframez-react @webtypen/webframez-core react react-dom react-server-dom-webpack
npm i -D typescript webpack webpack-cli ts-loader// src/server.ts
import path from "node:path";
import { BaseKernelWeb, Route, WebApplication } from "@webtypen/webframez-core";
import { initWebframezReact } from "@webtypen/webframez-react";
class Kernel extends BaseKernelWeb {
static controller = {};
static middleware = {};
}
const ReactRoute = initWebframezReact(Route);
const app = new WebApplication();
app.boot({
kernel: Kernel,
routesFunction: () => {
ReactRoute.renderReact("/react", {
distRootDir: path.resolve(process.cwd(), "dist"),
});
},
});Notes:
"/react"is automatically registered as a catch-all route (/react/*).basePath,assetsPrefix,rscPath, andclientScriptUrlare derived automatically from the mount path unless you override them.initWebframezReact(Route)returns the extended route facade, which gives you reliable editor autocompletion forrenderReact(...)even in monorepos or symlinked development setups.- The package also ships module augmentation for
Route.renderReact(...), but using the returnedReactRoutevariable is the most robust TypeScript setup.
Signature:
Route.renderReact(path, options)Example:
Route.renderReact("/app", {
distRootDir: path.resolve(process.cwd(), "dist"),
method: "GET",
routeOptions: {
middleware: ["auth"],
},
});distRootDir
- Required.
- Directory containing the built client assets and generated manifests.
pagesDir
- Optional.
- Directory containing the compiled
pages/**output. - Default:
${distRootDir}/pages
manifestPath
- Optional.
- Path to the React client manifest.
- Default:
${distRootDir}/react-client-manifest.json
assetsPrefix
- Optional.
- Public URL prefix used to serve built client assets.
- Auto-derived from
path. - Example for
"/react":/react/assets/
rscPath
- Optional.
- Public URL for the RSC endpoint.
- Auto-derived from
path. - Example for
"/react":/react/rsc
clientScriptUrl
- Optional.
- Public URL of the browser client entry bundle.
- Auto-derived from
path. - Example for
"/react":/react/assets/client.js
basePath
- Optional.
- Basename mounted in front of all file-router paths.
- Auto-derived from
pathwhenpath !== "/".
liveReloadPath
- Optional.
- Enables dev live reload on a custom path or disables it explicitly with
false. - Automatically disabled in production mode.
method
- Optional.
- HTTP method or methods used to register the route.
- Supported values:
"GET" | "POST" | "PUT" | "DELETE" - Default:
"GET"
routeOptions
- Optional.
- Additional route options forwarded to
webframez-core. - Typical use case: middleware.
pages/
layout.tsx
errors.tsx
index.tsx
about/index.tsx
src/
server.ts
client.tsx
components/
dist/Compared to a standard @webtypen/webframez-core project, the server TypeScript config usually needs a few changes:
- enable JSX via
"jsx": "react-jsx" - include
pages/**/*.tsx - include
src/components/**/*.tsx - include your server entry (
src/server.tsorsrc/server.tsx) - exclude the browser client entry (
src/client.tsxby default) - add
pathsmappings for the@webtypen/webframez-reactpackage and its subpaths
When you use the CLI (webframez-react build:server / watch:server), it generates a temporary .webframez-react.tsconfig.server.json that extends your project tsconfig.server.json. That means:
- your own
tsconfig.server.jsonstays the source of truth - the CLI only injects the resolved server entry and the standard RSC include/exclude rules
- you only need to customize the base config when your project structure differs from the defaults
A good starting point is the shipped default config:
@webtypen/webframez-react/defaults/tsconfig.server@webtypen/webframez-react/defaults/tsconfig.server.example
Example:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "Node",
"baseUrl": ".",
"paths": {
"@webtypen/webframez-react": [
"./node_modules/@webtypen/webframez-react/dist/index.d.ts"
],
"@webtypen/webframez-react/types": [
"./node_modules/@webtypen/webframez-react/dist/types.d.ts"
],
"@webtypen/webframez-react/router": [
"./node_modules/@webtypen/webframez-react/dist/router.d.ts"
],
"@webtypen/webframez-react/client": [
"./node_modules/@webtypen/webframez-react/dist/client.d.ts"
],
"@webtypen/webframez-react/navigation": [
"./node_modules/@webtypen/webframez-react/dist/navigation.d.ts"
],
"@webtypen/webframez-react/webframez-core": [
"./node_modules/@webtypen/webframez-react/dist/webframez-core.d.ts"
]
},
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "."
},
"include": [
"src/server.ts",
"src/server.tsx",
"src/components/**/*.tsx",
"pages/**/*.tsx",
"src/types.d.ts"
],
"exclude": [
"src/client.tsx",
"dist",
"node_modules"
]
}Recommended scripts:
{
"scripts": {
"build:server": "webframez-react build:server",
"build:client": "webframez-react build:client",
"build": "npm run build:server && npm run build:client",
"start": "NODE_OPTIONS='--conditions react-server -r @webtypen/webframez-react/register' node start-server.cjs",
"watch:server": "webframez-react watch:server",
"watch:client": "webframez-react watch:client",
"serve:watch": "NODE_OPTIONS='--conditions react-server -r @webtypen/webframez-react/register' node --watch start-server.cjs",
"watch": "sh -c 'npm run watch:server & npm run watch:client & npm run serve:watch & wait'",
"dev": "sh -c 'npm run watch:server & npm run watch:client & npm run serve:watch & wait'"
}
}Notes:
buildcompiles the server output (pages,server.ts) and the browser client bundle (client.tsx+ RSC manifests).startruns the built app in React Server mode.watch/devkeep TypeScript and webpack in watch mode and restart Node automatically when server output changes.@webtypen/webframez-react/registeractivates the React Server module register, so"use client"modules are treated correctly in Node and in the package's SSR worker.
If you run an existing webframez-core app directly with ts-node or node, use the same preload:
{
"scripts": {
"start": "NODE_OPTIONS='--conditions react-server -r @webtypen/webframez-react/register' ts-node ./app.ts",
"watch:app": "nodemon --exec \"NODE_OPTIONS='--conditions react-server -r @webtypen/webframez-react/register' ts-node ./app.ts\""
}
}Or via the shipped wrapper command:
{
"scripts": {
"start": "TS_NODE_FILES=true webframez-react exec -- ts-node ./app.ts",
"watch:app": "nodemon --exec \"TS_NODE_FILES=true webframez-react exec -- ts-node ./app.ts\""
}
}The CLI first checks project override files and then falls back to the package defaults:
tsconfig.server.jsonwebpack.client.cjswebpack.server.cjs
If you do not want to create your own webpack config just to move client.tsx or server.ts, you can use a small project config file:
// webframez-react.config.mjs
export default {
clientEntryPath: "src/app/client.tsx",
serverEntryPath: "src/app/server.tsx",
};Supported file names:
webframez-react.config.mjswebframez-react.config.cjswebframez-react.config.jswebframez-react.config.json
You can also override the entry paths per command:
webframez-react build:client --client-entry=src/app/client.tsx
webframez-react build:server --server-entry=src/app/server.tsx
webframez-react watch:client --client-entry=src/app/client.tsx
webframez-react watch:server --server-entry=src/app/server.tsxNotes:
- The default client entry is
src/client.tsx. - The default server entry is
src/server.ts, with automatic fallback tosrc/server.tsxif present. build:server:webpackandwatch:server:webpackalso respectserverEntryPath.build:serverandwatch:servergenerate a temporary.webframez-react.tsconfig.server.jsonso custom server entry paths also work with the TypeScript compiler.
Example:
pages/
layout.tsx
errors.tsx
index.tsx
about/index.tsx
accounts/[username]/index.tsxpages/layout.tsx:
"use server";
import React from "react";
import { RouteChildren } from "@webtypen/webframez-react/router";
export default function Layout() {
return (
<main>
<nav>...</nav>
<RouteChildren />
</main>
);
}RouteChildren marks where the currently resolved page should be rendered.
Every server page gets abort() via RouteContext.
"use server";
import type { PageProps } from "@webtypen/webframez-react/types";
export default function AccountPage({ params, abort }: PageProps) {
if (params.username !== "jane") {
abort({
status: 404,
message: `Account \"${params.username}\" not found`,
payload: { attemptedUsername: params.username },
});
}
return <section>...</section>;
}Behavior:
- default without options:
404+"Page not found" - rendered through
pages/errors.tsx pathnameis provided automatically by context- optional
payloadis forwarded toerrors.tsx
// src/client.tsx
import { mountWebframezClient } from "@webtypen/webframez-react/client";
mountWebframezClient();Optional:
mountWebframezClient({
rootId: "root",
rscEndpoint: "/react/rsc",
});"use client";
import React from "react";
import { Link, Redirect } from "@webtypen/webframez-react/navigation";
export function Nav() {
return (
<>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</>
);
}
export function Guard({ loggedIn }: { loggedIn: boolean }) {
if (!loggedIn) {
return <Redirect to="/login" replace />;
}
return null;
}Notes:
LinkandRedirectautomatically use the basename fromRoute.renderReact().- You can override it per usage via
basename.
"use client";
import React from "react";
import { useCookie, useRouter } from "@webtypen/webframez-react/client";
export default function LoginAction() {
const cookie = useCookie();
const router = useRouter();
return (
<button
onClick={() => {
cookie.set("logged_in", "1", { path: "/react", sameSite: "Lax" });
router.refresh();
}}
>
Login
</button>
);
}@webtypen/webframez-reactinitWebframezReactcreateNodeRequestHandlercreateFileRoutercreateHTMLShellsendRSCcreateRSCHandler
@webtypen/webframez-react/webframez-coreinitWebframezReact
@webtypen/webframez-react/routerRouteChildren
@webtypen/webframez-react/clientmountWebframezClient,useRouter,useCookie
@webtypen/webframez-react/navigationLink,Redirect
@webtypen/webframez-react/typesRouteContext,PageProps,ErrorPageProps,AbortRouteOptions, ...
Build the package itself:
npm run buildWatch mode for package development:
npm run build:watch