# Thinking in Backtick

What runs on your server, what runs on the phone, and what crosses between them.

A Backtick screen is one file with code for two places: your server and the
phone. Three rules say which code runs where:

1. **A server component runs on your server,** for every request.
2. **A client script, `` cs`…` ``, runs on the phone.**
3. **A splice, `$name`, is the only way a value crosses** from one to the
   other.

## One screen, both sides

```tsx title="server/Home.tsx"
import { cs } from "@backtickjs/core";
import { useState } from "@backtickjs/react";
import { Pressable, ScrollView, Text } from "@backtickjs/react-native";
import { db, type Order, type User } from "./db.js";

// A client component: it runs on the phone, with state of its own.
const ReorderButton = cs`(props: { order: Order }) => {
  const [added, setAdded] = $useState(false);
  return (
    <$Pressable onPress={() => setAdded(true)}>
      <$Text>{added ? "Added ✓" : "Reorder " + props.order.name}</$Text>
    </$Pressable>
  );
}`;

// A server component: it runs on your server, for every request.
export async function Home({ user }: { user: User }) {
  const usual = await db.usualOrder(user.id);

  return cs`(
    <$ScrollView>
      <$Text>Good morning, {$user.name}</$Text>
      <$ReorderButton order={$usual} />
    </$ScrollView>
  )`;
}
```

Read it by where each part runs:

- **On your server: `Home`, and `await db.usualOrder(...)`.** It reads its
  data where the data lives. Queries, secrets and server-only packages stay on
  your server.
- **Crossing as data: `$user` and `$usual`.** Each splice is a value
  `Home` has, written into the screen. In the script, it has its server type.
- **On the phone: the script `Home` returns, and `ReorderButton`.** They draw
  the screen, keep state and handle taps.

## What the phone receives

For a request from Sam, the server sends this bundle. It's generated from the
screen above by the docs' tests, so it's exactly what the bundler writes:

```js title="server/Home.bundle.js"
"use strict";
const { ScrollView: $i0 } = require("react-native");
const { Text: $i1 } = require("react-native");
const { useState: $i2 } = require("react");
const { Pressable: $i3 } = require("react-native");
const $module0 = require("react/jsx-runtime");
const $modules = {
"25wlsdb7ez0q4:20: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, $splice4) => ($ScrollView => /*#__PURE__*/ (0, jsx_runtime_1.jsxs)($ScrollView, {
    children: [($Text => /*#__PURE__*/ (0, jsx_runtime_1.jsxs)($Text, {
            children: ["Good morning, ", $splice2().name]
        }))($splice1()), ($ReorderButton => /*#__PURE__*/ (0, jsx_runtime_1.jsx)($ReorderButton, {
            order: $splice4()
        }))($splice3())]
}))($splice0());
},
"25wlsdb7ez0q4:7:22": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
exports.default = ($splice0, $splice1, $splice2) => props => {
    const [added, setAdded] = $splice0()(false);
    return ($Pressable => /*#__PURE__*/ (0, jsx_runtime_1.jsx)($Pressable, {
        onPress: () => setAdded(true),
        children: ($Text => /*#__PURE__*/ (0, jsx_runtime_1.jsx)($Text, {
            children: added ? "Added ✓" : "Reorder " + props.order.name
        }))($splice2())
    }))($splice1());
};
},
};
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("25wlsdb7ez0q4:20:9").default;
const $cs1 = $require("25wlsdb7ez0q4:7:22").default;
module.exports = ($cs0(() => ($i0), () => ($i1), () => ({ id: "u-7", name: "Sam" }), () => ($cs1(() => ($i2), () => ($i3), () => ($i1))), () => ({ id: "o-1042", name: "Flat white" })));
```

- **The scripts are compiled to plain JavaScript,** one module each. Their JSX
  is React's `jsx` calls.
- **The splices are the last line:** Sam's name and order, as data. Nothing of
  `Home` is there, and `db` isn't either: the phone gets results, never the
  code that produced them.
- **React and React Native come from the app** through `require`. The
  app's `evaluate` provides its own copies, so a screen needs nothing the app
  doesn't ship.

## What can cross

A splice can carry what can be written as data, and scripts:

- Strings, finite numbers, booleans, `null` and `undefined`
- Plain objects and arrays of those
- Scripts: `` cs`…` `` values, client components and client functions
- What the adapters export: `$View`, `$useState` and the rest

Functions and class instances can't cross, because they're code and state on
your server. TypeScript stops them where you write the splice:

```tsx title="server/Signup.tsx"
import { cs } from "@backtickjs/core";
import { Pressable, Text } from "@backtickjs/react-native";

export async function Signup() {
  const opensAt = new Date(2026, 9, 6);
  const track = () => console.log("signup");

  return cs`(
    // @ts-expect-error: a function on your server can't cross to the phone.
    <$Pressable onPress={$track}>
      {/* @ts-expect-error: nor can a class instance, a Date included. */}
      <$Text>Opens {$opensAt.toDateString()}</$Text>
    </$Pressable>
  )`;
}
```

```text
Argument of type '() => void' is not assignable to parameter of type 'Spliceable'.
Argument of type 'Date' is not assignable to parameter of type 'Spliceable'.
```

The fix is to cross what each one is for: the date as a string, and the
handler as a client function, which runs on the phone.

```tsx title="server/Signup.tsx" added="5-6,10"
import { cs } from "@backtickjs/core";
import { Pressable, Text } from "@backtickjs/react-native";

export async function Signup() {
  const opensAt = new Date(2026, 9, 6).toDateString();
  const track = cs`() => console.log("signup")`;

  return cs`(
    <$Pressable onPress={$track}>
      <$Text>Opens {$opensAt}</$Text>
    </$Pressable>
  )`;
}
```

## Optional values

Each `$offer` in a script is its own splice, so TypeScript doesn't narrow one
by checking another. In `{$offer && <$Banner offer={$offer} />}`, the second
`$offer` could still be `null` as far as TypeScript knows. Read the splice
once into a constant, and check that:

```tsx title="server/Promo.tsx"
import { cs } from "@backtickjs/core";
import { Text } from "@backtickjs/react-native";

type Offer = { title: string };

// Not every user has an offer.
async function offerFor(userId: string): Promise<Offer | null> {
  return userId === "u-7" ? { title: "2 for 1 cold brew" } : null;
}

const Banner = cs`(props: { offer: Offer }) => (
  <$Text>{props.offer.title}</$Text>
)`;

export async function Promo({ userId }: { userId: string }) {
  const offer = await offerFor(userId);

  return cs`{
    const offer = $offer;
    return offer && <$Banner offer={offer} />;
  }`;
}
```

## Where code goes

| You need                                                      | Write it as                                                     |
| ------------------------------------------------------------- | --------------------------------------------------------------- |
| Data from a database or an API, secrets, server-only packages | Code in a server component                                      |
| State, effects, taps, animation, device APIs                  | Code in a client script                                         |
| A server value on the phone                                   | A splice: `$name`, or `${expression}`                           |
| A piece of UI with its own props and state                    | A client component: `` cs`(props) => …` ``, used as `<$Name />` |
| A function the phone calls                                    | A client function: `` cs`(n: number) => …` ``                   |
| A piece of UI built on your server, inside a script           | A server component, spliced: `{${<Name />}}`                    |

## How a screen gets to the phone

Your server bundles a screen when the app asks for it:

```tsx title="server/index.tsx"
import { createServer } from "node:http";
import { bundler } from "@backtickjs/bundler";
import { db } from "./db.js";
import { Home } from "./Home.js";

// The versions of React and React Native your app ships.
const packageVersions = { react: "19.2.3", "react-native": "0.86.3" };

createServer(async (request, response) => {
  if (request.url === "/screens/home") {
    const user = await db.userFor(request.headers.authorization);
    const bundle = await bundler.build({
      input: <Home user={user} />,
      packageVersions,
    });
    const { code } = bundle.generate({ format: "cjs" });
    response.setHeader("content-type", "text/javascript");
    response.end(code);
    return;
  }
  response.statusCode = 404;
  response.end();
}).listen(3000);
```

Your app fetches the bundle, runs it with its own React and React Native, and
draws what it returns:

```tsx title="App.tsx"
import { evaluate } from "@backtickjs/react-native-client";
import * as React from "react";
import { Suspense, use, useState } from "react";
import * as JSXRuntime from "react/jsx-runtime";
import * as ReactNative from "react-native";
import { ActivityIndicator } from "react-native";

// What a screen may require: the packages your app is built with.
const modules = {
  react: React,
  "react/jsx-runtime": JSXRuntime,
  "react-native": ReactNative,
};

async function fetchScreen(url: string): Promise<React.ReactNode> {
  const response = await fetch(url);
  return evaluate(await response.text(), modules) as React.ReactNode;
}

export default function App() {
  const [screen] = useState(() =>
    fetchScreen("https://api.example.com/screens/home"),
  );
  return (
    <Suspense fallback={<ActivityIndicator />}>
      <Screen screen={screen} />
    </Suspense>
  );
}

function Screen({ screen }: { screen: Promise<React.ReactNode> }) {
  return use(screen);
}
```

Both are what `create-backtick-app` sets up, without its development extras.
