Skip to main content

Responsive + Performance

When to use: Before launch. After major changes. Monthly audits. When users report "it's slow" or "it looks broken on my phone."

The test: Load the page on 3G throttling at 375px wide. Still usable? Still fast?


Responsive Design

Breakpoint Testing

Test at these four widths. No exceptions.

BreakpointWidthWhat to Check
Mobile375pxPrimary content visible, CTA reachable by thumb, no horizontal scroll
Tablet768pxLayout adapts (not just squeezed desktop), navigation usable
Laptop1024pxContent fills space without stretching, grid aligns
Desktop1440pxMax-width containers prevent ultra-wide text lines

Responsive Checklist

CheckThresholdHow to Measure
No horizontal overflowZero horizontal scrollbar at any breakpointResize browser, check for scrollbar
Touch targets44x44px minimum on mobile and tabletDevTools: inspect element dimensions
Font scalingBody text 16px minimum at every breakpointDevTools: computed font-size
Image scalingwidth and height attributes set, CSS object-fit usedDevTools: verify attributes present and computed aspect ratio unchanged
CLS on resizeUnder 0.1 when viewport changesLighthouse or Web Vitals extension
Reflow at 400%Content accessible at 400% zoom without horizontal scrollBrowser zoom to 400%, check layout

Mobile-Specific Checks

  • Hero answers what/who/what-to-do on small screen
  • CTA visible early without scrolling
  • Navigation doesn't block primary content
  • Thumb zone respected (primary actions bottom half of screen)
  • No hover-dependent functionality (mobile has no hover)
  • Text inputs don't zoom the viewport (font-size 16px minimum on inputs)

Tablet-Specific Checks

  • Layout is not just a scaled-down desktop
  • Multi-column content adapts to 2-column or single-column
  • Modals don't cover entire screen (use drawers or full pages instead)

Core Web Vitals

These are the metrics Google uses for ranking and the thresholds users notice.

MetricGoodNeeds WorkPoorHow to Measure
LCP (Largest Contentful Paint)Under 2.5s2.5-4.0sOver 4.0sLighthouse, PageSpeed Insights
INP (Interaction to Next Paint)Under 200ms200-500msOver 500msChrome Web Vitals extension
CLS (Cumulative Layout Shift)Under 0.10.1-0.25Over 0.25Lighthouse, Web Vitals extension

LCP Optimization

ProblemSolutionExpected Impact
Large hero imageCompress to WebP/AVIF, add width/height, use fetchpriority="high"500ms-2s improvement
Render-blocking CSSInline critical CSS, defer non-critical200ms-1s improvement
Slow server responseCDN, caching headers, edge rendering100ms-500ms improvement
Web fonts blocking renderfont-display: swap, preload key fonts100ms-500ms improvement

INP Optimization

ProblemSolutionExpected Impact
Heavy JavaScript on interactionCode-split, defer non-critical JS50-200ms improvement
Long event handlersBreak into smaller tasks with requestIdleCallback50-100ms improvement
Third-party scripts blocking main threadLoad async, defer, or lazy-load100-300ms improvement

CLS Prevention

ProblemSolution
Images without dimensionsAlways set width and height attributes
Dynamic content injected above viewportReserve space with min-height or aspect-ratio
Web fonts causing layout shiftUse font-display: optional or size-adjust
Ads or embeds resizingSet fixed dimensions on containers

Page Weight Budget

ResourceBudgetHow to Measure
Total page weightUnder 1MBDevTools: Network tab, total transferred
JavaScriptUnder 300KB transferredDevTools: filter by JS, check transferred size
CSSUnder 100KB transferredDevTools: filter by CSS
Images totalUnder 500KBDevTools: filter by Img
FontsUnder 100KBDevTools: filter by Font
Largest single imageUnder 200KBDevTools: sort by size

Image Optimization

FormatUse WhenTypical Savings
WebPPhotographs, complex images25-35% smaller than JPEG
AVIFWhen browser support sufficient40-50% smaller than JPEG
SVGIcons, logos, simple illustrationsResolution-independent
CheckThresholdHow to Measure
FormatWebP or AVIF for photos, SVG for iconsCheck file extensions
Responsive imagessrcset with multiple sizesInspect <img> attributes
Lazy loadingBelow-fold images use loading="lazy"Inspect <img> attributes
Dimensions setwidth and height on all <img>DOM audit
CompressionUnder 80% quality for JPEG/WebPImage analysis tool

Script Performance

  • Critical JS inlined or preloaded
  • Non-critical JS deferred with defer attribute
  • No render-blocking scripts in <head> without async/defer
  • Third-party scripts loaded async
  • Bundle size tracked in CI (alert on 10%+ increase)

Font Loading

  • font-display: swap or optional on all @font-face
  • Key fonts preloaded: <link rel="preload" as="font">
  • Maximum 2-3 font files loaded
  • Subset fonts to required character ranges
  • WOFF2 format used (best compression)

Testing Tools

ToolWhat It MeasuresWhen to Use
LighthousePerformance, accessibility, SEO, best practicesEvery deploy
PageSpeed InsightsReal user data + lab dataMonthly
WebPageTestDetailed waterfall, filmstrip, network analysisDebugging specific issues
Chrome Web Vitals ExtensionReal-time CWV while browsingDuring development
axe DevToolsAccessibility violationsEvery deploy

Manual Testing

TestHowWhat to Check
Keyboard onlyUnplug mouse, navigate with Tab/Enter/SpaceAll actions reachable, focus visible
Screen readerVoiceOver (Mac) or NVDA (Windows)Page makes sense read aloud
Color blindnessChrome DevTools > Rendering > Emulate vision deficiencyInformation not lost
Slow connectionChrome DevTools > Network > Slow 3GPage usable within 5 seconds
Real deviceLoad on actual phone, not just DevTools emulationTouch targets, font sizes, scroll behavior

Monitoring

  • Core Web Vitals tracked in Search Console
  • Real User Monitoring (RUM) enabled (Vercel Analytics, Cloudflare, or similar)
  • Performance budget in CI (alert on 10%+ regression)
  • Error rate monitoring active

Automated Test

const { test, expect } = require("@playwright/test");
const AxeBuilder = require("@axe-core/playwright").default;

test("Performance and accessibility", async ({ page }) => {
await page.goto("/");

// Accessibility audit
const axeResults = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
.analyze();
expect(axeResults.violations).toEqual([]);

// No horizontal overflow
const hasOverflow = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});
expect(hasOverflow).toBe(false);
});

Quick Diagnosis

SymptomLikely CauseFix
Page feels slowLarge uncompressed imagesCompress, use WebP, add dimensions
Layout jumps on loadMissing image dimensions or dynamic contentSet width/height, reserve space
Mobile layout brokenNo responsive breakpointsAdd media queries or responsive grid
Text unreadable on mobileFont too smallSet minimum 16px, check computed sizes
Slow on repeat visitsNo caching headersAdd Cache-Control headers
High CLS scoreFonts loading late, ads injectingUse font-display: swap, fix container sizes
Can't tap buttons on mobileTouch targets too smallIncrease to 44x44px minimum with 8px clearance

Context