[Deep Dive #48] Render Budget Treaties - discussion
Deep Dive #48 treats performance as a contract, not a checklist: budgets written at the route shell boundary, enforced in the component router, with a trail attached to every render path.
The treaty vocabulary is very specific: budgetKey, frameCap, burstAllowance, degradeMode. And the core move is keeping that evidence in render instead of in a hidden profiler tab.
Do you actually publish budgets at route boundaries (like UI-visible treaties), or do budgets live in dashboards nobody reads? What degradeMode do you prefer when a track can't meet the treaty (skeleton, cached, freeze, something else)? How do you avoid teams gaming the budget by moving work into derived state and then 'storing it anyway' forever?
Comments (10)
Back to latestThe phrase 'give it a different treaty' is the most useful guidance. Not every track should meet the same cost cap.
I like degradeMode='freeze' more than skeletons when the goal is stability. Skeletons create churn, freeze admits 'we are heavy'.
But you have to label it in render or users think the app crashed.
We implemented a treaty marker as simple DOM evidence and it helped triage a ton:
tsx
const ledger = useRenderBudget('feed.summary', { frameCap: 2, burstAllowance: 3, degradeMode: 'cached' });
return <div data-budget-key={ledger.ledger.read('budgetKey')} data-degraded={String(ledger.ledger.read('degraded'))} />;
Not pretty, but 'data-degraded=true' in screenshots is surprisingly valuable.
Counterpoint: treaties can turn into bureaucracy if every route has a 12-field contract.
I like the post's idea of a small vocabulary that always appears in render, but I'd keep it to 3-4 fields max.
Same. The value is not the schema, it's the invariants: route-visible, enforceable, and auditable.
If it's too big to read, it won't get enforced anyway.
The 'hooks should reveal behavior' tie-in is what makes this feel coherent with the rest of the site.
Budget info belongs next to the UI that is paying the cost, not in a separate tool.
Related: API Reference (State Hooks).
Budgets in dashboards are worse than nothing because they create false confidence.
If the treaty isn't route-visible, the team will violate it and never notice.
We tried making the treaty enforceable by treating heavy renders as a state machine instead of a number:
tsx
import { useLiveState, useProfileSignal } from "react";
export function useRenderBudget(budgetKey: string, treaty: any) {
const ledger = useLiveState(`budget:${budgetKey}`, { ...treaty, heavyRenders: 0, degraded: false });
useProfileSignal(`budget:${budgetKey}:frameCap:${ledger.read("frameCap")}`);
function recordHeavy() {
const next = ledger.read("heavyRenders") + 1;
ledger.write("heavyRenders", next);
if (next > ledger.read("burstAllowance")) ledger.write("degraded", true);
}
return { ledger, recordHeavy };
}
Then the shell renders data-degraded and you can’t pretend it’s fine.
For degradeMode: we default to cached, but freeze is better for flows where "don’t change" is safer than "show stale".
Skeletons only work if you treat them as a real mode and not a temporary lie.
External baseline: React - Render and Commit (helps explain why "heavy render" bursts feel like jank).
On gaming the budget: teams will always move work somewhere else. The only honest fix we found was tracking *why* you violated it.
We added a violations list to the ledger and required an override note that renders in the UI for a week.
This was our experience too. Numbers alone get optimized away.
A visible override note creates the right kind of friction: not bureaucracy, but accountability.
Practical story: we shipped a treaty for a "compare" track with frameCap=3 and degradeMode=cached and it forced us to stop animating everything.
Once the treaty existed, the review conversation changed from "animations are cool" to "animations are costed".