Case file A8

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. Move outward only when latency, privacy, or autonomy demand it.

ByAdam LewisPublished3 May 2026Revised13 Oct 2026Reading12 minVersionv1.3ConfidenceHigh
§0b

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 what should be obvious (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 larger rule survives every shift in fashion. Push the work toward the source of truth. On the web that means React Server Components, Phoenix LiveView, HTMX, Hotwire, Astro: seven independent frameworks1 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, while using its own SDUI system for notifications and interstitials.6 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 and the same idea Jeremy Keith reframed as “resilient web design” in 2016.4 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, bundle the 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
Copy for paste
§0c

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.

§0d

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; return 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 runs the logic; 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 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 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?
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

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: send the function to the data rather than the data to the function, because data is heavy and code is light.11 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 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 all of it as a posture: assume the network fails, the JS fails, the user agent is unfamiliar, design so the document still works.4 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 and 'use client' is the explicit opt-in.1 Phoenix LiveView (2018) treats the server as the source of truth and pushes diffed UI updates over a WebSocket.2 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.5 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 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 The native default is the inverse of the web default: bundle the 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.

§2

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.

React RFC #0188 · Server Components (2020)

Elixir applications don’t need to arrange ensembles of libraries and transfer formats and lifecycles to build dynamically updating UI; dynamic server rendering is built in.

Chris McCord · How We Got to LiveView (2021)

Identify core functionality. Make that functionality available using the simplest possible technology. Enhance!

Jeremy Keith · Resilient Web Design (2017)

Since local-first applications store the primary copy of their data in each device’s local filesystem, the user can read and write this data anytime, even while offline.

Martin Kleppmann et al. · Local-first software (2019)
§3

Evidence

16 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.

  1. 01
    Sebastian Markbåge, Dan Abramov, Andrew Clark, Joseph Savona · 2020
    The original RFC. Server Components default. The `'use client'` directive is the explicit opt-in. Server proximity to data, fewer secrets shipped, smaller bundles.
  2. 02
    Chris McCord · Phoenix team · 2024
    Stateful templates rendered on the server, diffed updates pushed over a WebSocket. The Elixir tradition's expression of the same default.
  3. 03
    DHH · 37signals · 2020
    Puts HTML back in the response body. Small JavaScript primitives (Turbo Drive, Frames, Streams) swap fragments rather than maintaining a parallel client state tree.
  4. 04
    Carson Gross · HTMX · 2022
    Extend HTML with a small attribute vocabulary so the server returns fragments. The modern continuation of the original web architecture.
  5. 05
    Astro team · 2021
    Zero-JavaScript-by-default as the literal build behaviour. Interactive “islands” hydrated only where the page author opts in.
  1. 06
    Misko Hevery · Builder.io · 2022
    Argues re-running on the client work the server already did is duplication. Qwik's resumability is a sharper version of the same claim.
  2. 07
    Jeremy Keith · 2017
    Assume the network fails, the JS fails, the user agent is unfamiliar. Server-default is one tactic that satisfies the larger resilience posture.
  3. 08
    Aaron Gustafson · 2011
    Formalised the three layers of progressive enhancement: HTML for content, CSS for presentation, JavaScript for behaviour. The book-length statement of the case.
  4. 09
    Remix team · 2022 onwards
    First-class principle. HTTP and HTML are the base API. Native `<form>` for mutations. Loaders default to server.
  5. 10
    Addy Osmani · 2018 onwards
    Annual measurement series. JavaScript is the most expensive byte-for-byte resource on the page on mid-range mobile. The economic argument for server-default.
  6. 11
    Alex Russell · Infrequently Noted · 2024
    A baseline Android device on a P75 network can afford ~1.3 MiB of compressed content to get interactive in five seconds, no more than 650 KiB of it JavaScript; at a three-second target the budget shrinks to ~730 KiB, with no more than 365 KiB of JavaScript.
  7. 12
    Ryan Brooks · Airbnb Engineering · 2021
    The Ghost Platform: server returns a component tree, client renders it. The native expression where the truth still lives on the server.
  8. 13
    Aubrey Haskett · 2025
    Multi-company comparison. Documents Netflix's deliberate opt-out of SDUI for its core browse experience after measuring first-paint cost.
  9. 14
    Ink & Switch (Kleppmann, Wiggins, van Hardenberg, McGranaghan) · 2019
    The collaborative-editor counter-tradition. The source of truth is the client's local state. The server is a sync engine. CRDTs as the data structure.
  10. 15
    Josh W. Comeau · 2020
    Concrete walkthrough of the hydration model and its costs. The class of bug the server-default architecture sidesteps entirely.
  11. 16
    Zaharia, Chowdhury, Das et al. · 2012
    The distributed-systems case for sending computation to the data, not data to the computation. Apache Spark's foundational paper. The same instinct at a different scale.

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 hold the cross-stack honesty (the principle inverts on native and games). The sole opposer is the local-first tradition, which disputes the assumption that authority lives on the server at all. The honest argument is between the four traditions on where the boundary should sit, not between supporters and sceptics.

§4

Examples

Viewing: TypeScript.
Avoid

Before client fetches data the server already has. Two renders, one truth.

Fileapp/hedgehogs/page.tsx
"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} />;
}
Prefer

After server renders where the data lives. One render, no round-trip.

Fileapp/hedgehogs/page.tsx
import { loadHedgehogList } from "@/tracking/load-hedgehog-list";

export default async function HedgehogList() {
  const hedgehogs = await loadHedgehogList();
  return <HedgehogTable rows={hedgehogs} />;
}
§4b

Enforcement

Viewing: TypeScript.

Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.

RuleToolCatches
@next/next/no-async-client-component@next/eslint-plugin-nextasync functions wearing 'use client'. The combination breaks the boundary; async data fetching belongs in Server Components.
no-restricted-imports (next/dynamic ssr:false)ESLint coreclient-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 corefetch calls inside useEffect: the SPA-default pattern that recreates on the client work the server should have done.
react/no-direct-mutation-stateeslint-plugin-reactdirect mutation of state. The truth flows through the controlled boundary; mutation breaks the data-flow contract.
cacheComponents (Partial Prerendering)Next.js frameworkthe architectural shape itself. Cache Components make the static / streaming / dynamic split first-class; the boundary becomes lint-able by design rather than by convention.
eslint.config.mjsconfiguration snippet
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import nextPlugin from '@next/eslint-plugin-next';
import reactPlugin from 'eslint-plugin-react';

export default defineConfig({
  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.',
    }],
  }
});
§4c

AI rules

File.cursor/rules/a8-push-to-the-source-of-truth.mdc
---
alwaysApply: true
---

## Prickles A8: Push to the Source of Truth

Push the work toward the source of truth. The component that owns the data runs the logic. 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: bundle the 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.

§5

Counter-argument

Counter

The strongest steelman is the local-first / offline-first counter.9 Automerge and the whole CRDT-shaped local-first lineage took the opposite bet: the source of truth is the client's local state and the server is a sync engine rather than 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 The implication for “Push to the Source of Truth” is sharp: if the truth lives on the client, then this tenet says send the work there, which is correct but inverts every example in the case file. The slogan survives. The form it takes on the web has to be read as the assumption that authority lives on the server, which is parochial.

§6

Counter-argument retort

Reply

The local-first counter is correct on its own terms and the slogan absorbs it without strain.9 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 original Server-First tenet erred not in the slogan but in assuming the truth always lives on the server. Generalised, the rule is stack-agnostic. Web-specific, it inverts on native, and client-truth-specific, it inverts on collaborative editors. One slogan with three branches and a single rule underneath.

The performance objection, “sometimes you really do want to push 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 sends 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 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 larger instinct database engineers have always had.

§7

Notes

  1. [1]React core team: “Server Components,” React RFC #0188 (Sebastian Markbåge, Dan Abramov, Andrew Clark, Joseph Savona). Server Components default, with the `'use client'` directive as the explicit opt-in.
  2. [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. [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.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-10-13