Browse guides

Run a Valyrian.js browser application in Node.js

Import the application already used by the browser and render it to HTML in Node.js.

The example declares `App` once in `app.tsx`. The browser entry mounts it, while `server.tsx` imports the same `App`, compiles the server entry through `inline` and prints the HTML returned by `render`.

Table of contents

Main APIs

mount
Starts a Valyrian.js application on a DOM target.
render
Executes a Valyrian.js application in Node.js and serializes its rendered output to HTML.
isNodeJs
Reports whether Valyrian.js is running in a Node.js environment.
inline
Bundles build input for server-side tooling.

1. Export the shared application from app.tsx

tsx
export function App({ path }: { path: string }) {
  return (
    <main>
      <h1>Hello SSR at {path}</h1>
    </main>
  );
}

2. Create browser-entry.tsx and mount the application

tsx
import { mount } from "valyrian.js";
import { App } from "./app";

mount("body", <App path={window.location.pathname} />);

3. Render the shared application in server.tsx

Import `render` from `valyrian.js/node`, import the existing `App` from `app.tsx` and pass that same application to `render`.

tsx
import { render } from "valyrian.js/node";
import { App } from "./app";

const html = render(<App path="/users/42" />);
console.log(html);

4. Keep environment-specific behavior inside the shared application with `isNodeJs`

Update `app.tsx` so the shared `App` uses `isNodeJs` to select the branch required by the current environment without creating a server-specific component.

tsx
import { isNodeJs } from "valyrian.js";

export function App({ path }: { path: string }) {
  const runtime = isNodeJs ? "Node.js" : "browser";
  return (
    <main>
      <h1>Hello SSR at {path}</h1>
      <p>Running in {runtime}</p>
    </main>
  );
}

5. Create tsconfig.json

Configure the automatic TSX runtime documented by Valyrian.js.

json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "valyrian.js",
    "skipLibCheck": true,
    "types": ["node"]
  }
}

6. Create build-server.mjs

Compile server.tsx through inline as a Node ESM module while keeping installed packages external.

js
import fs from "node:fs";
import { inline } from "valyrian.js/node";

const result = await inline("./server.tsx", {
  esbuild: {
    format: "esm",
    packages: "external",
    platform: "node",
  },
});

fs.mkdirSync("./dist", { recursive: true });
fs.writeFileSync("./dist/server.mjs", result.raw);

7. Build and render

Install Valyrian.js, compile the TSX entry and execute the generated module with Node.

bash
npm install valyrian.js@9.1.13
npm install --save-dev @types/node@22.15.3
node build-server.mjs
node dist/server.mjs

Result

Node.js produced this HTML by executing the same `App` module mounted by the browser.

html
<main><h1>Hello SSR at /users/42</h1><p>Running in Node.js</p></main>

Practice

Apply this concept in Taskboard

API reference