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, and 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 is 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
, and 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 as a single object rather than 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 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, so visitors download the finished value rather than 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, so 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