Server components

Functions your server runs for every request: they read your data and return the screen.

server/ProductScreen.tsx
import { cs } from "@backtickjs/core";
import { Text, View } from "@backtickjs/react-native";
import type { Catalog } from "./catalog.js";
// A server component: a function of its props, run on your server for every
// request. Its props stay on your server, so they can be anything.
export async function ProductScreen({
id,
catalog,
}: {
id: string;
catalog: Catalog;
}) {
const product = await catalog.find(id);
if (product === undefined) {
return cs`<$Text>This product is gone.</$Text>`;
}
const name = product.name;
const inStock = product.stock > 0;
return cs`(
<$View style={{ padding: 24, gap: 8 }}>
<$Text style={{ fontSize: 28, fontWeight: "bold" }}>{$name}</$Text>
<$Text>{$inStock ? "In stock" : "Sold out"}</$Text>
</$View>
)`;
}

A server component is a function your server runs for every request. It takes props, reads whatever it needs, and returns a client script: the screen the phone draws. It can be async, and usually is.

Draw one from a route

Your server draws a server component the way React draws any component, with JSX, and hands it to the bundler:

server/index.tsx
import { createServer } from "node:http";
import { bundler } from "@backtickjs/bundler";
import { Catalog } from "./catalog.js";
import { ProductScreen } from "./ProductScreen.js";
const packageVersions = { react: "19.2.3", "react-native": "0.86.3" };
const catalog = new Catalog();
createServer(async (request, response) => {
// `/products/p1` draws product p1.
const id = request.url?.match(/^\/products\/(\w+)$/)?.[1];
if (id === undefined) {
response.statusCode = 404;
response.end();
return;
}
const bundle = await bundler.build({
input: <ProductScreen id={id} catalog={catalog} />,
packageVersions,
});
response.setHeader("content-type", "text/javascript");
response.end(bundle.generate({ format: "cjs" }).code);
}).listen(3000);

bundler.build runs ProductScreen with these props, then bundles the script it returns. Each request gets its own run, so each user can get their own screen.

Props stay on your server

A server component's props never cross to the phone, so they can be anything: a database client, a class instance like Catalog, a request, a function. Only what you splice into the script crosses.

Decide on your server

A server component can return a different script depending on what it found, as ProductScreen does for a product that's gone. The decision runs on your server, and the phone gets only the screen it led to.

Ship only what the screen needs

The product has a cost, what you paid for it, and the screen shouldn't send it to every phone. ProductScreen splices name and inStock, so they're all that crosses:

What the phone receives
server/ProductScreen.bundle.js
"use strict";
const { View: $i0 } = require("react-native");
const { Text: $i1 } = require("react-native");
const $module0 = require("react/jsx-runtime");
const $modules = {
"1qlemy4wvzr85:23:9": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
exports.default = ($splice0, $splice1, $splice2, $splice3) => ($View => /*#__PURE__*/ (0, jsx_runtime_1.jsxs)($View, {
style: {
padding: 24,
gap: 8
},
children: [($Text => /*#__PURE__*/ (0, jsx_runtime_1.jsx)($Text, {
style: {
fontSize: 28,
fontWeight: "bold"
},
children: $splice2()
}))($splice1()), ($Text => /*#__PURE__*/ (0, jsx_runtime_1.jsx)($Text, {
children: $splice3() ? "In stock" : "Sold out"
}))($splice1())]
}))($splice0());
},
};
const $exports = {
"react/jsx-runtime": $module0,
};
const $require = (id) => {
if (!(id in $exports)) {
const module = { exports: {} };
$exports[id] = module.exports;
$modules[id](module, module.exports, $require);
$exports[id] = module.exports;
}
return $exports[id];
};
const $cs0 = $require("1qlemy4wvzr85:23:9").default;
module.exports = ($cs0(() => ($i0), () => ($i1), () => ("Ceramic dripper"), () => (true)));

The last line is everything the phone learns about the product: its name, and whether it's in stock. Splices explains what crosses, and how to keep a value from crossing by accident.

State belongs on the phone

A server component runs once per request and returns, so it has no state, and React's hooks aren't available to it. The hooks from @backtickjs/react are client values, and calling one on your server is a type error:

server/Counter.tsx
import { cs } from "@backtickjs/core";
import { useState } from "@backtickjs/react";
import { Text } from "@backtickjs/react-native";
export function Counter() {
// @ts-expect-error: hooks are client code, and this runs on your server.
const [count, setCount] = useState(0);
return cs`<$Text>{$count}</$Text>`;
}
This expression is not callable.
Type 'Client<…>' has no call signatures.

State goes in a client component, which a server component can draw.