Skip to main content

Performance Foundations

(Making speed a feature-level responsibility)

Senior rule:

Performance is not something you sprinkle on after the UI is done.

In Atlas, users open the dashboard many times a day. Slow first load damages trust. Slow filters damage productivity. Unstable layout makes charts feel broken. Senior frontend engineers treat these as product correctness issues.

Conceptual Model

Performance has three layers:

LayerQuestion
LoadingHow quickly does useful content appear?
InteractionHow quickly does the UI respond after input?
StabilityDoes the page move, block, or surprise users while work happens?

Core signals:

  • LCP: main content visibility.
  • INP: interaction responsiveness.
  • CLS: layout stability.
  • TTFB: server and network responsiveness.
  • JavaScript cost: parse, compile, execute, hydrate, and re-render.

Resource Loading

Senior engineers classify resources before optimizing them:

ResourceExample in AtlasLoading strategy
Critical CSSdashboard shell layoutrender-blocking but minimal
LCP image or hero chart shelldashboard overviewpreload only if truly critical
Below-fold widgetssecondary reportslazy load
Likely next routereport detail pageprefetch after idle or intent
Cross-origin APIanalytics domainpreconnect if high confidence
// app/dashboard/layout.tsx
import Script from "next/script";

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<Script
src="https://analytics.example.com/client.js"
strategy="afterInteractive"
/>
</>
);
}

Use beforeInteractive only for scripts that must run before the app becomes interactive, such as consent or bot protection. Most product analytics should not compete with the first meaningful render.

Images and Layout Stability

import Image from "next/image";

export function CustomerLogo() {
return (
<Image
src="/customers/acme.png"
alt="Acme"
width={96}
height={32}
sizes="96px"
priority={false}
/>
);
}

Senior habits:

  • give images dimensions
  • avoid lazy-loading the LCP image
  • use responsive sizes for grid images
  • avoid shipping 2MB images into 96px containers
  • reserve space for charts and tables before data arrives

Code Splitting and Dynamic Imports

Atlas has a heavy cohort chart used by only one tab. Do not ship it in the initial dashboard bundle.

import dynamic from "next/dynamic";

const CohortChart = dynamic(() => import("./CohortChart"), {
loading: () => <div className="chart-skeleton" aria-label="Loading chart" />,
});

export function CohortTab() {
return <CohortChart />;
}

Code splitting helps transfer and execution cost, but it can create loading gaps. Pair dynamic imports with stable skeletons and clear boundaries.

Compression and Minification

Compression and minification solve different problems:

TechniqueReducesDoes not reduce
Minificationsource text sizeruntime complexity
gzip or Brotlitransfer sizeparse and execution cost
Tree shakingunused module code when statically detectableside-effectful dependencies

Senior engineers check both network transfer and runtime execution. A minified 400KB chart library can still block the main thread after it arrives.

Virtualization

Large dashboard tables should not render thousands of rows.

type Row = { id: string; customer: string; revenue: number };

export function VisibleRows({
rows,
start,
end,
}: {
rows: Row[];
start: number;
end: number;
}) {
return (
<tbody>
{rows.slice(start, end).map((row) => (
<tr key={row.id}>
<td>{row.customer}</td>
<td>{row.revenue.toLocaleString()}</td>
</tr>
))}
</tbody>
);
}

In production, use a proven virtualization library for complex tables. The senior skill is knowing when DOM size has become a performance problem and ensuring accessibility is not lost.

Caching

Performance caching is a contract about freshness.

// Server Component fetch with explicit revalidation intent.
async function getDashboardSummary() {
const response = await fetch("https://api.example.com/dashboard/summary", {
next: { revalidate: 60 },
});

if (!response.ok) throw new Error("Failed to load dashboard summary");
return response.json() as Promise<DashboardSummary>;
}

Ask:

  • who owns freshness?
  • what invalidates the data?
  • what stale state is acceptable?
  • can the UI say "showing cached data" when needed?

Third-Party Script Budget

Before adding a script to Atlas:

type ThirdPartyScriptReview = {
name: string;
owner: string;
purpose: string;
loadingStrategy: "beforeInteractive" | "afterInteractive" | "lazyOnload";
consentRequired: boolean;
expectedRoutes: string[];
rollbackPlan: string;
};

Scripts need owners because they affect performance, privacy, reliability, and debugging.

Mini Case Study: The Dashboard Got Slower After Analytics

Atlas added a session replay tool. LCP barely changed, but INP worsened on low-end Android devices.

Fix:

  • load the script after interaction
  • disable replay on low-value routes
  • sample sessions
  • defer initialization until consent is known
  • track long tasks by route and script version

The issue was not the tool alone. The issue was ungoverned runtime cost.

Common Failure Modes

  • Lazy-loading the LCP image.
  • Splitting code so aggressively that every interaction waits.
  • Treating compression and minification as substitutes for removing code.
  • Measuring only Lighthouse while ignoring real-user data.
  • Optimizing transfer size but ignoring JavaScript execution.
  • Adding third-party scripts without ownership.
  • Rendering large tables without virtualization or pagination.

Review Checklist

  • Is the LCP element known?
  • Are critical resources prioritized deliberately?
  • Are non-critical widgets deferred?
  • Are images sized and compressed?
  • Are compression, minification, and tree shaking verified in production output?
  • Is JavaScript execution measured, not just bundle size?
  • Are large lists virtualized or paginated?
  • Are third-party scripts reviewed and owned?
  • Is production performance segmented by route and device?

Senior Deep Dive: Performance As Product Correctness

A senior frontend engineer does not ask "is the page fast?" as a single question. They break performance into user promises:

PromiseUser interpretationEngineering signal
"I can see what I came for"the page is useful quicklyLCP, TTFB, critical CSS, image priority
"The product reacts to me"taps, typing, filters, and drag actions feel immediateINP, long tasks, React commit time, event timing
"The UI is not lying"content does not jump, disappear, or show stale certaintyCLS, skeleton stability, stale-state labels
"The app respects my device"it does not drain battery or freeze lower-end phonesJavaScript cost, memory growth, background requests
"The next screen is not a reset"navigation preserves context and feels continuousroute transition time, cache reuse, prefetch usefulness

This framing matters because senior engineers often inherit vague complaints: "the dashboard feels slow," "filters are laggy," or "mobile is bad." The senior move is to translate those complaints into a performance hypothesis that can be measured and acted on.

The hidden cost stack

The frontend pays for more than transferred bytes:

CostCommon sourceSenior-level control
Network round tripsrequest waterfalls, third-party origins, uncached API callsroute data design, BFF shaping, cache ownership
Transferimages, fonts, bundles, JSON overfetchcompression, responsive media, payload trimming
Parse and compilelarge JavaScript bundlesdependency review, route splitting, server/client boundaries
Executionhydration, render, analytics, data transformsmain-thread budgeting, idle work, worker/server offload
Layout and paintunbounded DOM, missing dimensions, layout reads/writesstable dimensions, virtualization, CSS discipline
Memory and GCretained stores, huge lists, cached objectsdisposal contracts, bounded caches, profiling

The weakness this corrects: mid-level engineers often optimize the visible symptom while leaving the cost stack unchanged. A senior engineer asks which layer created the delay and whether the fix reduces actual user wait.

Performance budget design

A useful feature-level budget is specific:

Budget itemExample senior target
Route/dashboard/reports on mobile and desktop separately
User cohortp75 logged-in users in top three regions
Device profilemid-range Android and normal business laptop
Initial JavaScriptroute budget plus owner for every large dependency
Main-thread workno interaction-blocking long task during filter apply
Mediaexplicit dimensions, format policy, no unbounded hero/chart assets
Datamaximum initial requests and no duplicate route fetches
Exception processowner, expiry date, and product reason

Budgets are not punishment. They are a way to make tradeoffs visible before a feature quietly adds permanent cost.

Performance review prompts

Use these during PR or design review:

  • What is the user's first useful moment?
  • What is the largest resource and why is it necessary now?
  • Which code runs before the user can interact?
  • Which work repeats on every navigation or every keystroke?
  • Which dependency was added and what route pays for it?
  • Which third-party script runs before consent or before interaction?
  • What happens on a mid-range phone with CPU throttling?
  • Which production metric will confirm the change was safe?

If the author cannot answer these, the feature is not necessarily wrong, but the performance consequences are not yet understood.

Exercises

Exercise 1 - LCP Audit

Pick a route and identify:

  • LCP element
  • critical CSS and JS
  • critical image or data dependency
  • one thing delaying useful content

Exercise 2 - Main Thread Budget

Open DevTools Performance and find the longest task. Classify it as:

  • required now
  • deferrable
  • removable
  • movable to a worker

Exercise 3 - Script Review

Choose one third-party script and write a review record with owner, loading strategy, consent, performance risk, and removal path.

Further Reading