If your record page EPT is over two seconds at the 75th percentile, your reps are tab-switching to their inbox before your data loads. You are not measuring trust, you are measuring patience.
A performance budget is not a goal. It is a refusal to ship past a number.
The numbers that matter
Set these for every record page in your org:
- EPT P75 < 2.0s — Experienced Page Time, what the user actually feels.
- Initial bytes < 800 KB — total JS + JSON shipped before first interaction.
- Component count < 25 — components on the default tab.
- LDS calls < 10 — Lightning Data Service
getRecordinvocations at load. - Server calls < 5 — Apex / API roundtrips before the page is interactive.
Anything above these and you owe yourself a refactor.
How to measure honestly
Salesforce gives you three tools. Use them in this order.
- Lightning Usage App for trend data. Aggregate, lagging, but free.
- Page Performance tab in Setup for component-level cost. Run on a primed cache and a cold cache.
- Browser DevTools with
?eptVisualizer=1query param for the truth.
The trap: developer orgs are 3x faster than production because of seat count. Always measure in a sandbox sized like prod, with a user profile sized like a real rep.
Component cost: what actually burns budget
In our last twenty audits, the same five suspects:
- Related lists with formula fields that span objects. Each row evaluates the formula client-side.
- Custom LWCs that wire to multiple Apex methods at connectedCallback. Three @wire on connection = three serial roundtrips.
- Embedded reports with no row limit. The page waits for the report to render before declaring interactive.
- Flow components that auto-launch. They block the EPT timer until their first screen renders.
- Path components on objects with 15+ statuses. The path renders all stages eagerly.
Pattern: defer everything below the fold
Use the lazyLoad attribute on Lightning App Builder components and split your record page into “above the fold” and “below the fold” zones.
// myComponent.js
import { LightningElement, api } from 'lwc';
export default class MyComponent extends LightningElement {
@api recordId;
isVisible = false;
connectedCallback() {
// Don't fire @wire on mount. Wait for intersection.
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
this.isVisible = true;
observer.disconnect();
}
});
// Defer observer attachment until the host is in the DOM.
queueMicrotask(() => observer.observe(this.template.host));
}
}
<!-- myComponent.html -->
<template>
<template lwc:if={isVisible}>
<c-expensive-related-list record-id={recordId}></c-expensive-related-list>
</template>
</template>
This single pattern removed 600ms from a Service Cloud case page in a recent engagement.
Pattern: collapse @wire fan-out
If you have three components each doing @wire(getRecord, ...) for the same record, you have three roundtrips. Lightning Data Service deduplicates within a request, but only if the field selection matches exactly.
Centralize the wire in one parent and pass the record down as a property:
import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
const FIELDS = [
'Account.Name',
'Account.AnnualRevenue',
'Account.Industry',
'Account.Owner.Name'
];
export default class AccountSummary extends LightningElement {
@api recordId;
record;
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
wiredRecord({ data, error }) {
if (data) this.record = data;
}
}
The “Dynamic Forms tax”
Dynamic Forms are great for UX and bad for default-tab budgets when overused. Each visibility-rule expression evaluates client-side on every field change. A page with 60 dynamically-shown fields and 20 visibility rules can blow 400ms just on rule evaluation. See the Dynamic Forms best practices piece for the rule-cardinality rules.
Enforcing the budget in CI
The Salesforce CLI now exposes a performance scan in pipelines:
sf lightning analyze page-performance \
--target-org ci-sandbox \
--record-types Account,Opportunity,Case \
--max-ept 2000 \
--max-bytes 800000 \
--fail-on-regression \
--output-format json > perf-report.json
Wire that into your deploy pipeline. A regression > 200ms on any tracked page fails the build. Stop trusting humans to notice.
UX note for Service Cloud
On case pages, the highest-impact change is moving the case feed to a lazy-loaded tab. The feed is heavy and rarely needed on first load — agents triage from related cases and recent activity first. EPT savings of 800ms+ are common.
Bottom line
- Pick a budget — EPT P75 < 2.0s, < 800 KB initial, < 25 components, < 10 LDS, < 5 server calls.
- Measure on prod-sized sandboxes with prod-shaped profiles.
- Lazy load anything below the fold; collapse @wire fan-out; throttle Dynamic Forms rule count.
- Enforce in CI with
sf lightning analyze page-performanceand fail builds on regression. - The biggest wins are almost always related lists and embedded reports — start there.