Push to the Source of Truth
Run the work where the data lives.
Computation has a natural home: wherever the data, the authority, or the trust already lives. Default to running work there. Ship outward only when latency, privacy, or autonomy demand it.
Opinion
I've spent enough years building single-page apps that fetched JSON to render the same HTML the server could have returned to know how expensive the round trip is. Server-First became the web's name for the obvious thing (render where the data lives), but as a tenet it dies the moment the system is not a browser. A native iOS app is “client-first” by definition. A CLI has no server. A real-time game must own its render loop. The slogan collapses into nonsense when held parochial.
The deeper rule survives every paradigm shift. Push the work toward the source of truth. On the web that means React Server Components, Phoenix LiveView, HTMX, Hotwire, Astro: seven independent frameworks1“Server Components,” React RFC #0188 (Sebastian Markbåge, Dan Abramov, Andrew Clark, Joseph Savona). Server Components default; the `'use client'` directive is the explicit opt-in. all converging on the same default because the network is slow and the client device is variable. On native it means data over the wire, not UI: Netflix deliberately opted out of Server-Driven UI for its core browse experience after building the technology that made SDUI famous.6“Server-Driven UI: What Airbnb, Netflix, and Lyft Learned.” Documents Netflix's deliberate opt-out of SDUI for its core browse experience after measuring the first-paint cost. On distributed systems it means push compute to the data, not data to the compute, the lesson Hadoop and Spark taught a generation by losing money the other way.
The tenet is what databases have always known: “predicate pushdown” is the same idea Steven Champeon and Nick Finck named “progressive enhancement” in 2003,3“Inclusive Web Design for the Future” (SXSW Interactive 2003). The talk where the term “progressive enhancement” was coined. Start with a well-marked-up document, then layer up. and the same idea Jeremy Keith reframed as “resilient web design” in 2017.4Resilient Web Design (2017, free online). The deeper argument: assume the network fails, the JS fails, the user agent is unfamiliar. Server-default falls out as one tactic that satisfies it. Pair it with A2 Three-Tier Hoisting: hoisting works because a helper's “source of truth” is the highest tier its consumers share. Pair it with A11 Dependency Inversion: abstractions point inward toward the authoritative layer, never outward toward the transport.
Copy a note and link
Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.
Run the work where the data lives. On the web that's Server Components by default; client interactivity is the explicit opt-in. On native, ship UI in the binary; fetch only data. Opt outward only when latency, privacy, or autonomy demand it. /tenet/push-to-the-source-of-truth/A8
AI eyes only
Rule: run the work where the data lives. Do not recompute on a different tier.
Reject: client fetches that re-derive data the server already shaped. Reject: server actions that re-validate what a database constraint enforces. Reject: round-trips that exist only to format.
Generate: server-rendered or server-computed first. The client transports state; it does not author it. Database constraints over application-layer guards when the constraint is expressible.
Diagnostic: for each computation, name the tier that owns the source data. If the computation runs on a different tier, push it down.
Why?
- Removes the duplicate-business-logic-on-both-sides smell. A validation that lives next to the database is one validation; the same rule restated in the client and the server is two rules with one bug per drift.
- On the web: smaller JS bundles, less hydration, fewer waterfalls. Every byte of computation kept on the server is a byte the client doesn't download, parse, compile, execute and re-execute.
- Generalises the database engineer's predicate-pushdown instinct. Filter at the source; ship the smallest result set across the wire; the same rule that makes a SQL query fast makes a feature fast.
- Names the authority. The component that owns the truth is the component that does the work; every other layer is a transport. Pairs with A11 Dependency Inversion — abstractions point inward toward the source.
- Edge functions, edge caching, regional databases — all native expressions of the same rule at a different scale. Push the work toward the data's region; pay only the latency the user actually sees.
- Survives the platform change. The slogan reads the same on web (RSC), on mobile (data-not-UI), on distributed systems (compute-to-data), on libraries (smallest exposed surface). The web-specific tactic was parochial; the underlying rule is not.
- Latency, privacy, autonomy are the three named opt-out conditions. Real-time games go client-side because of latency; regulated data stays local because of privacy; offline-first apps push outward because of autonomy. The exceptions are finite and named.
- Agents default to symmetry — client fetches and re-renders what the server already rendered. The rule is the asymmetry your AGENTS.md needs to teach: where does the truth live, and is the work running there?
Origins
The instinct is older than the web. In database engineering it is “predicate pushdown” — push the filter into the storage engine where the rows live, instead of dragging the rows across the wire to filter them in application memory. The same instinct drives the Hadoop / MapReduce / Spark generation: ship the function to the data, not the data to the function, because data is heavy and code is light.11“Resilient Distributed Datasets,” Zaharia et al. (NSDI 2012). The foundational case for shipping computation to the data, not data to the computation, in distributed systems. The lesson Hadoop taught a generation. Every distributed-system architect of the last twenty years has internalised it; the web is just the surface where the conversation became a tenet.
The web ancestry is precise. Steven Champeon and Nick Finck named “progressive enhancement” at SXSW Interactive in 2003 — start with a well-marked-up document, then layer up.3“Inclusive Web Design for the Future” (SXSW Interactive 2003). The talk where the term “progressive enhancement” was coined. Start with a well-marked-up document, then layer up. Aaron Gustafson's Adaptive Web Design (2011) formalised the three layers: HTML for content, CSS for presentation, JavaScript for behaviour. Jeremy Keith's Resilient Web Design (2017) reframed the whole thing as a posture: assume the network fails, the JS fails, the user agent is unfamiliar — design so the document still works.4Resilient Web Design (2017, free online). The deeper argument: assume the network fails, the JS fails, the user agent is unfamiliar. Server-default falls out as one tactic that satisfies it. The current web revival is the same argument with new names.
Seven framework families converged on the server-default in 2018 onward. React Server Components landed as an RFC in 2020 and shipped in Next.js 13 in 2022 — Server Components are the default; 'use client' is the explicit opt-in.1“Server Components,” React RFC #0188 (Sebastian Markbåge, Dan Abramov, Andrew Clark, Joseph Savona). Server Components default; the `'use client'` directive is the explicit opt-in. Phoenix LiveView (2018) treats the server as the source of truth and pushes diffed UI updates over a WebSocket.2Phoenix LiveView 1.0 release (2024). Stateful templates rendered on the server, diffed updates pushed over a WebSocket. The Elixir tradition's expression of the same default. Hotwire (2020) puts HTML back in the response body. HTMX extends HTML with a small attribute vocabulary so the server returns fragments. Astro Islands make zero-JavaScript-by-default the literal build behaviour. Qwik attacks the hydration tax directly with resumability. Remix treats HTTP and HTML as the base API.5Remix documentation, “Progressive Enhancement.” First-class principle in Remix; HTTP and HTML are the base API; native `<form>` for mutations. Independent stacks, same default.
Native mobile got there from the opposite direction. Airbnb's 2017 Ghost Platform was the first widely-known production Server-Driven UI system — the server returns a component tree, the client renders it.7“A Deep Dive into Airbnb's Server-Driven UI System.” The Ghost Platform — server returns a component tree, client renders it. The first widely-known production SDUI architecture. Lyft, Spotify (HubFramework, since deprecated) and Netflix followed; Netflix then deliberately opted out of SDUI for its core browse experience after measuring the first-paint cost.6“Server-Driven UI: What Airbnb, Netflix, and Lyft Learned.” Documents Netflix's deliberate opt-out of SDUI for its core browse experience after measuring the first-paint cost. The native default is the inverse of the web default: ship UI in the binary, fetch only what changes. Both are expressions of the same underlying rule — the work runs where the truth already lives — reaching opposite tactics because the truth lives in different places.
Quotes
Server Components allow developers to build apps that span the server and client, combining the rich interactivity of client-side apps with the improved performance of traditional server rendering.
We get all the simplicity of server-rendered HTML, plus the engagement and rich interactive experience users expect from modern web applications.
Identify core functionality. Make that functionality available using the simplest possible technology. Enhance.
In local-first software, the availability of another computer should never prevent you from working.
Evidence
Twenty external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.
- 01The original RFC. Server Components default; the `'use client'` directive is the explicit opt-in. Server proximity to data, fewer secrets shipped, smaller bundles.
- 02Phoenix LiveView 1.0SupportsStateful templates rendered on the server, diffed updates pushed over a WebSocket. The Elixir tradition's expression of the same default.
- 03Hotwire — HTML Over The WireSupportsPuts HTML back in the response body. Small JavaScript primitives (Turbo Drive, Frames, Streams) swap fragments rather than maintaining a parallel client state tree.
- 04“Hypermedia-Driven Applications”SupportsExtend HTML with a small attribute vocabulary so the server returns fragments. The modern continuation of the original web architecture.
- 05Astro Islands ArchitectureSupportsZero-JavaScript-by-default as the literal build behaviour. Interactive “islands” hydrated only where the page author opts in.
Sixteen sources spanning four traditions. The web canon at the top: React Server Components, Phoenix LiveView, Hotwire, HTMX, Astro Islands. Native, distributed-systems and historical ancestry sit further down. The qualifiers carry the cross-stack honesty (the principle inverts on native and games); the opposers are inside-the-tent critics of the SPA default the tenet is reacting to. The honest argument is between the four traditions on where the boundary should sit, not between supporters and sceptics.
Examples
// Before: client fetches data the server already has. Two renders, one truth."use client";import { useEffect, useState } from "react";export default function HedgehogList() { const [hedgehogs, setHedgehogs] = useState<Hedgehog[]>([]); useEffect(() => { fetch("/api/hedgehogs").then((r) => r.json()).then(setHedgehogs); }, []); return <HedgehogTable rows={hedgehogs} />;}
// After: server renders where the data lives. One render, no round-trip.import { loadHedgehogList } from "@/tracking/load-hedgehog-list";export default async function HedgehogList() { const hedgehogs = await loadHedgehogList(); return <HedgehogTable rows={hedgehogs} />;}
Enforcement
Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.
| Rule | Tool | Catches |
|---|---|---|
| @next/next/no-async-client-component | @next/eslint-plugin-next | async functions wearing `'use client'`. The combination breaks the boundary; async data fetching belongs in Server Components. |
| no-restricted-imports (next/dynamic ssr:false) | ESLint core | client-only dynamic imports added by reflex rather than necessity. Server Components are the default; ssr-disabled dynamic imports need a justified reason. |
| no-restricted-syntax (fetch in useEffect) | ESLint core | fetch calls inside useEffect — the SPA-default pattern that recreates on the client work the server should have done. |
| react/no-direct-mutation-state | eslint-plugin-react | direct mutation of state. The truth flows through the controlled boundary; mutation breaks the data-flow contract. |
| experimental.ppr (Partial Prerendering) | Next.js framework | the architectural shape itself. PPR makes the static / streaming / dynamic split first-class; the boundary becomes lint-able by design rather than by convention. |
eslint.config.mjsconfiguration snippet
import tseslint from 'typescript-eslint';
import nextPlugin from '@next/eslint-plugin-next';
import reactPlugin from 'eslint-plugin-react';
export default tseslint.config({
files: ['**/*.{ts,tsx}'],
plugins: { '@next/next': nextPlugin, react: reactPlugin },
rules: {
'@next/next/no-async-client-component': 'error',
'react/no-direct-mutation-state': 'error',
'no-restricted-imports': ['error', {
patterns: [
{
group: ['next/dynamic'],
importNames: ['default'],
message: 'Prefer Server Components over dynamic client imports unless ssr: false is required.',
},
],
}],
'no-restricted-syntax': ['error', {
selector: 'CallExpression[callee.name="useEffect"][arguments.0.body.body.0.expression.callee.property.name="fetch"]',
message: 'Fetch in useEffect duplicates server work — move to a Server Component or a server action.',
}],
}
});AI rules
.cursor/rules/a8-push-to-the-source-of-truth.mdc---
description: Prickles A8 — Push to the Source of Truth
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---
## Prickles A8 — Push to the Source of Truth
Push the work toward the source of truth. The component that owns the data does the work; every other layer is a transport.
On the web: Server Components by default. `'use client'` is the explicit opt-in. Validation, authorisation and rendering live next to the database.
On native: ship UI in the binary, fetch only data. Server-Driven UI is a deliberate opt-in with documented downsides; the default is the inverse of the web default.
Opt outward — toward the client, the edge, the consumer — only when latency, privacy, or autonomy demand it. The exceptions are finite and named.Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.
Counter-argument
The strongest steelman is the local-first / offline-first counter.9“Local-first software” (2019). The collaborative-editor counter-tradition: the source of truth is the client's local state, the server is for sync. CRDTs as the data structure. Figma, Linear, Notion and the whole CRDT-shaped lineage took the opposite bet: the source of truth is the client's local state, and the server is a sync engine, not an authority. Martin Kleppmann's 2019 paper makes the case explicitly: the latency, autonomy and resilience properties of local-first applications are categorically better than anything a server-pushed model can offer.10“Local-first software: You own your data, in spite of the cloud,” Onward! 2019. The principled case that latency, autonomy and resilience properties of local-first applications are categorically better than server-pushed equivalents. The implication for “Push to the Source of Truth” is sharp: if the truth lives on the client, then this tenet says ship the work there, which is correct but inverts every example in the case file. The slogan survives; the framing on the web has to be read as the assumption that authority lives on the server, which is parochial.
Counter-argument retort
The local-first counter is correct on its own terms and the slogan absorbs it without strain.9“Local-first software” (2019). The collaborative-editor counter-tradition: the source of truth is the client's local state, the server is for sync. CRDTs as the data structure. If the source of truth is a CRDT in the browser, then by this tenet the computation belongs in the browser; the server is a sync engine. Figma, Linear, Notion and the whole local-first lineage are applications of “Push to the Source of Truth”, not exceptions to it. The error in the original Server-First framing was not the slogan; it was assuming the truth always lives on the server. Generalised, the rule is stack-agnostic; web-specific, it inverts on native; client-truth-specific, it inverts on collaborative editors. One slogan, three branches, one underlying rule.
The performance objection — “sometimes you really do want to ship work to the client for latency” — is also absorbed. Latency, privacy and autonomy are the named opt-out conditions in the rule. A real-time game ships work to the client because the client owns the render loop and a 30ms server round trip is a 30ms input lag. A privacy-bound computation runs locally because regulated data must not cross the network. An offline-first app runs locally because the network is unreliable. The opt-outs are explicit and finite; the default is the source of truth.
The hardest objection is the “not language-agnostic” flag.8“Server-First” (the previous tenet name) was flagged as not language-agnostic. The reframing to “Push to the Source of Truth” addresses the objection by naming the underlying rule rather than its web-shaped expression. A CLI has no UI; a backend service has no client. The reply: the rule operates at the architectural seam — wherever there is a boundary between two layers, the work goes on the side of the boundary that owns the truth. CLIs do this trivially (no boundary, no tension). Backend services do this between database and application code (predicate pushdown, stored procedures-or-not, the “business logic in the database” debate). Libraries do this between exposed surface and consumer (don't make the consumer reimplement what the library knows). Pair it with A11 Dependency Inversion and the seam becomes the abstraction; the rule still applies.
The genuine residue is naming. “Server-First” was the wrong name because it made the rule sound web-only. “Push to the Source of Truth” is the rule with the web-specificity removed. The web tactic remains the canonical example because it is the most cited and the best evidenced; the principle is the deeper instinct database engineers have always had.
Notes
- [1]React core team — “Server Components,” React RFC #0188 (Sebastian Markbåge, Dan Abramov, Andrew Clark, Joseph Savona). Server Components default; the `'use client'` directive is the explicit opt-in.
- [2]Chris McCord — Phoenix LiveView 1.0 release (2024). Stateful templates rendered on the server, diffed updates pushed over a WebSocket. The Elixir tradition's expression of the same default.
- [3]Steven Champeon & Nick Finck — “Inclusive Web Design for the Future” (SXSW Interactive 2003). The talk where the term “progressive enhancement” was coined. Start with a well-marked-up document, then layer up.