Performance-First Frontend Architecture
Performance isn't a feature you add at the end. It's an architectural decision you make at the beginning — or pay for later.
I've worked on projects where the "we'll optimize later" approach led to months of painful refactoring. And I've worked on projects where performance was baked into every decision from day one. The difference in outcome is dramatic.
The Performance Budget
Before writing any code, I establish a performance budget. This isn't abstract — it's concrete numbers:
- First Contentful Paint: < 1.2s - Largest Contentful Paint: < 2.5s - Total Blocking Time: < 200ms - Cumulative Layout Shift: < 0.1 - JavaScript bundle: < 200KB gzipped (initial load)
These numbers drive every technical decision downstream.
Code Splitting Done Right
The single biggest performance win in most React applications is intelligent code splitting. Not just route-based splitting (which Next.js gives you for free), but component-level splitting for heavy dependencies.
// Don't import Three.js in your main bundle
const Scene3D = dynamic(() => import('./Scene3D'), {
loading: () => <div className="scene-placeholder" />,
ssr: false,
})I follow a simple rule: if a dependency is over 50KB and isn't needed above the fold, it gets dynamically imported.
The Rendering Pipeline
React's rendering model is powerful but can be wasteful. Here's my approach to keeping re-renders in check:
Memoize at component boundaries. Not everywhere — that's premature optimization. But at the boundaries between fast-changing state and expensive render trees, `React.memo` is invaluable.
Colocate state. The number one cause of unnecessary re-renders is state that lives too high in the tree. If only one component needs a piece of state, that's where it should live.
Virtual scrolling for long lists. Rendering 10,000 DOM nodes is never the answer. Libraries like TanStack Virtual render only what's visible, plus a small overscan buffer.
Image Strategy
Images are typically the largest assets on any page. My strategy:
1. Next/Image for automatic optimization, WebP/AVIF conversion, and responsive sizing 2. Blur placeholders generated at build time for perceived performance 3. Intersection Observer for lazy loading below-the-fold images 4. CDN with aggressive caching — images rarely change, so cache them forever
The 60fps Imperative
Animations must run at 60fps or they do more harm than good. This means every animation frame must complete within 16.6ms. My rules:
- CSS transforms and opacity only for hardware-accelerated animations - `will-change` sparingly and only on elements about to animate - GSAP ticker instead of multiple requestAnimationFrame calls - Debounce scroll handlers to 16ms (one frame)
Performance isn't about making fast things faster. It's about finding and eliminating the slow things. Measure first, optimize second, and always keep the user's experience as the north star.
© 2025 Bilal
All posts