Server components
Functions your server runs for every request: they read your data and return the screen.
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:
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
"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:
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.