Performance and Best Practices
Why Performance Matters
A slow website loses users. Research consistently shows that a 100ms delay in page load reduces conversion rates. Google uses page speed as a ranking signal for search results. As a professional developer, performance is part of your craft, not an optional extra.
Image Optimisation
Images are typically the largest assets on a page. Optimise them:
Use Modern Formats
WebP images are around 25-35% smaller than JPEG at the same quality. Use them with a fallback for older browsers:
<picture>
<source srcset="hero.webp" type="image/webp" />
<img src="hero.jpg" alt="Team photo" />
</picture>
Lazy Loading
Load images only when they are about to enter the viewport:
<img src="project-screenshot.webp" alt="Project" loading="lazy" />
In React:
<img src={project.image} alt={project.title} loading="lazy" decoding="async" />
Code Splitting with React.lazy
Code splitting breaks your JavaScript bundle into smaller chunks that are loaded on demand. This reduces the initial load time significantly:
// Without code splitting — all pages loaded immediately
import Home from "./pages/Home";
import Projects from "./pages/Projects";
import Contact from "./pages/Contact";
// With code splitting — each page loaded only when navigated to
import { lazy, Suspense } from "react";
const Home = lazy(() => import("./pages/Home"));
const Projects = lazy(() => import("./pages/Projects"));
const Contact = lazy(() => import("./pages/Contact"));
function App() {
return (
<Suspense fallback={<div className="page-loader">Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/projects" element={<Projects />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
);
}
Suspense renders the fallback UI while the lazy component is loading.
Memoisation
Memoisation prevents unnecessary re-renders and re-calculations.
React.memo
Wraps a component so it only re-renders when its props change:
import { memo } from "react";
const ProjectCard = memo(function ProjectCard({ title, description, imageUrl }) {
console.log("Rendering:", title); // Only logs when props change
return (
<div className="card">
<h3>{title}</h3>
<p>{description}</p>
</div>
);
});
export default ProjectCard;
useMemo
Caches the result of an expensive calculation:
import { useMemo } from "react";
function ProjectsList({ projects, searchQuery }) {
// Only recalculated when projects or searchQuery changes
const filtered = useMemo(() => {
return projects.filter(p =>
p.title.toLowerCase().includes(searchQuery.toLowerCase())
);
}, [projects, searchQuery]);
return filtered.map(p => <ProjectCard key={p.id} {...p} />);
}
useCallback
Caches a function so it is not recreated on every render (important when passing callbacks to memoised children):
import { useCallback } from "react";
function ParentComponent() {
const handleDelete = useCallback((id) => {
setItems(items => items.filter(item => item.id !== id));
}, []); // Stable function reference
return <ChildComponent onDelete={handleDelete} />;
}
Lighthouse Scores
Google Lighthouse is built into Chrome DevTools (F12, then the "Lighthouse" tab). It audits your site and scores it in five categories:
- Performance — load speed, rendering, Core Web Vitals
- Accessibility — screen reader compatibility, contrast ratios, keyboard navigation
- Best Practices — HTTPS, no deprecated APIs
- SEO (Search Engine Optimisation) — meta tags, crawlability
- PWA (Progressive Web App) — offline support, installability
Aim for scores above 90 in all categories.
Accessibility (A11y)
Accessibility (often abbreviated as a11y — 'a', then 11 letters, then 'y') ensures your site works for people with disabilities:
// Use semantic HTML elements
<header>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Page Title</h1>
</article>
</main>
// ARIA labels for icon-only buttons
<button aria-label="Close menu" onClick={handleClose}>
<XIcon />
</button>
// Ensure sufficient colour contrast (4.5:1 ratio for normal text)
// Test with Chrome DevTools accessibility panel
Keyboard Navigation
All interactive elements must be keyboard accessible. Test by pressing Tab to navigate your site:
// Ensure focus is visible
button:focus-visible {
outline: 2px solid #42BCB1;
outline-offset: 2px;
}
Clean Code Principles
- Single Responsibility — each component or function does one thing
- Meaningful names —
getUserByIdis clearer thangetData - DRY (Don't Repeat Yourself) — extract repeated logic into functions or hooks
- Consistent formatting — use Prettier (a code formatter) and ESLint (a linter) in your project
npm install --save-dev prettier eslint eslint-plugin-react
Key Takeaways
- Use WebP images and the loading="lazy" attribute to reduce initial page load weight
- React.lazy and Suspense enable code splitting — pages are only loaded when navigated to
- React.memo prevents unnecessary re-renders; useMemo caches calculations; useCallback caches functions
- Run Lighthouse in Chrome DevTools to measure Performance, Accessibility, Best Practices, and SEO
- Semantic HTML and ARIA labels ensure your site works for all users, including those using screen readers
- All interactive elements must be reachable and usable via keyboard navigation
Practice Exercise
- Run a Lighthouse audit on your portfolio site and take note of your scores
- Add loading="lazy" and decoding="async" to all project images
- Wrap your page components in React.lazy and add a Suspense fallback
- Wrap your ProjectCard component with React.memo
- Fix at least one accessibility issue flagged by Lighthouse (low contrast, missing alt text, missing label, etc.)
Try it yourself
Key Takeaways
- WebP images are 25-35% smaller than JPEG; use loading='lazy' to defer off-screen images
- React.lazy and Suspense split your JS bundle by route so pages only load when navigated to
- React.memo prevents re-renders when props have not changed; useMemo caches expensive calculations; useCallback caches function references
- Run Lighthouse in Chrome DevTools to audit Performance, Accessibility, Best Practices, and SEO
- Semantic HTML (header, main, nav, article) and ARIA labels make your site navigable for screen reader users
- All interactive elements must work via keyboard; use :focus-visible to style keyboard focus indicators
Quick Quiz
1.What does React.lazy() do?
2.When should you use React.memo()?
3.What is the minimum recommended colour contrast ratio for normal body text according to WCAG (Web Content Accessibility Guidelines)?
4.What does the loading='lazy' attribute on an img element do?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx