Timing: client, server, compiler
Studio writes this format for you. The Timing field on a data source's editor ( Data sources ) sets the values below — this page documents what each one means.
The
timing
key on a state entry declares
where
its value is resolved. There are three values, and each moves the work to a different machine:
| Value | When it resolves |
|---|---|
"client" |
At runtime, in the visitor's browser (the default) |
"server" |
At runtime, on the server, called over an RPC boundary |
"compiler" |
At build time — the result is baked into the emitted HTML |
The smallest complete server-timed entry — a function that runs on the server, whose return value lands in state:
{
"state": {
"metrics": {
"$src": "./dashboard.server.js",
"$export": "fetchMetrics",
"timing": "server"
}
}
}
Client
"client"
is the default — omit
timing
and the entry resolves in the browser. All the Web-API
data prototypes
(
Request
,
LocalStorage
,
IndexedDB
, …) are client-timed unless told otherwise.
Server: the RPC function boundary
timing: "server"
designates a cross-process function call. The entry names an async export in a server-side module via
$src
and
$export
— no
$prototype
is used. The function receives
(args, env)
: the caller's arguments object, and the platform's environment bindings (Cloudflare Workers
env
, a Node
process.env
wrapper, …):
export async function fetchMetrics(args, env) {
const db = env.DB; // e.g. a Cloudflare D1 binding
const { data } = await db.prepare("SELECT * FROM metrics").all();
return data;
}
A function that needs no bindings simply ignores the second parameter.
Arguments
An optional
arguments
field passes named parameters — a single object, not a positional list. Values may be static or reactive
$ref
s; any reactive value makes the call re-run when it changes:
{
"metrics": {
"$src": "./dashboard.server.js",
"$export": "fetchMetrics",
"timing": "server",
"arguments": {
"userId": { "$ref": "#/state/userId" },
"filter": "active"
}
}
}
The security boundary
Server credentials stay in the server process. The
env
parameter gives the function its platform bindings — databases, KV namespaces, secrets, email workers — and none of it crosses to the client: the browser receives only the function's serialized return value.
The boundary is enforced by the compiled output. During development, the runtime may execute a server entry client-side (falling back to the dev server's proxy when the module can't load in a browser), so don't treat dev behavior as proof that a secret is hidden — build and deploy to exercise the real boundary.
Compiler
timing: "compiler"
resolves the entry during the build. The result is baked into the emitted HTML and the entry is stripped from the shipped state — visitors download the finished value, not the machinery that produced it. This is the natural timing for content that changes only when you rebuild, and the content prototypes (
MarkdownFile
,
MarkdownCollection
,
ContentCollection
) use it by design:
{
"posts": {
"$prototype": "MarkdownCollection",
"src": "./content/posts/*.md",
"timing": "compiler"
}
}
How it works
For each
timing: "server"
entry, the
compiler
emits two artifacts: a client-side
POST /_jx/server/<export>
fetch that stores the JSON response in a signal (wrapped in an effect when any argument is reactive), and a server-side Hono handler that imports the export from
$src
and serves that route.
When
build.adapter
is set in
project.json
, every server entry across the whole site is collected, deduplicated by export name, and bundled into a single worker (
dist/_worker.js
plus a
_routes.json
limiting invocation to
/_jx/*
on Cloudflare Pages). Without an adapter, a standalone per-document handler is generated instead. During development the
dev server
stands in for both.
Compiler-timed entries never produce runtime artifacts at all — the site build resolves them, bakes the values into the static HTML, and strips the entries from the document.
Rules
"client"is the default whenevertimingis absent.A server entry uses
$src+$exportwith no$prototype, and the export must be an async named export.The server function's signature is
(args, env);argumentsis one named-values object.A reactive
$refinargumentsmakes the RPC call reactive.envand everything reachable through it stay server-side; only the return value is serialized to the browser.timing: "compiler"values are fixed at build time — rebuild the site to refresh them.
Related
Data prototypes — the entries
timingapplies toFunctions and sidecars — client-side functions by contrast
The build — where server bundling and baking happen
Dev server — server timing during development
Data sources in Studio — the Timing field in the editor