User Experience & Page Experience Checklist
Core Web Vitals, mobile usability, and page speed are confirmed ranking factors. Good UX increases engagement, reduces bounce rates, and improves conversions. These 18 checks cover every aspect of page experience with implementation code.
Why User Experience Matters
Google Page Experience update made Core Web Vitals official ranking factors. Pages that load fast, respond quickly to interactions, and maintain visual stability rank higher. Poor UX increases bounce rates and reduces conversions, creating a negative feedback loop.
In 2026, UX also affects AI search visibility. AI agents that render pages for screenshots or parse HTML expect fast, stable content. Poor Core Web Vitals correlate with poor user experience across all surfaces including AI-generated answers.
Sources
- Sites meeting Core Web Vitals thresholds are 24% less likely to be abandoned, Google News Initiative.
- Core Web Vitals are confirmed ranking signals, Google Search Central.
CWV Metrics Summary
The 18 Checks
Every check ranked by impact. Start at the top and work down.
| # | Check | Category | Impact | Difficulty |
|---|---|---|---|---|
| 1 | Achieve LCP under 2.5 seconds | CWV | Critical | Hard |
| 2 | Achieve INP under 200ms | CWV | Critical | Hard |
| 3 | Achieve CLS under 0.1 | CWV | Critical | Medium |
| 4 | Pass mobile-friendly test | Mobile | Critical | Medium |
| 5 | Eliminate render-blocking resources | Speed | High | Hard |
| 6 | Optimize images for web | Speed | High | Easy |
| 7 | Use responsive images | Mobile | High | Easy |
| 8 | Set proper viewport meta tag | Mobile | High | Easy |
| 9 | Avoid intrusive interstitials | UX | High | Easy |
| 10 | Use readable font sizes (16px+) | Readability | Medium | Easy |
| 11 | Maintain adequate tap target sizes | Mobile | Medium | Easy |
| 12 | Minimize main thread work | Speed | Medium | Hard |
| 13 | Use efficient cache policies | Speed | Medium | Medium |
| 14 | Enable text compression | Speed | Medium | Easy |
| 15 | Avoid excessive DOM size | Performance | Medium | Hard |
| 16 | Use CSS containment | Performance | Low | Medium |
| 17 | Prioritize visible content | Speed | Low | Medium |
| 18 | Monitor UX metrics in GSC | Monitoring | Low | Easy |
Deep Dive: Core Web Vitals Optimization
Implementation guides with code examples for LCP, INP, and CLS.
1 Achieve LCP Under 2.5 Seconds
Largest Contentful Paint measures when the largest content element in the viewport becomes visible. It is usually a hero image, large heading, or video poster. Identify the LCP element using Lighthouse, then preload it and eliminate anything that blocks rendering.
<!-- Preload the LCP image with high priority -->
<link rel="preload" as="image" href="/images/hero.webp" fetchpriority="high">
<!-- Use fetchpriority on the img tag itself -->
<img src="/images/hero.webp" fetchpriority="high" alt="Hero" width="1200" height="600">
<!-- Preload critical CSS -->
<link rel="preload" as="style" href="/css/critical.css"
onload="this.onload=null;this.rel='stylesheet'">
<!-- Defer non-critical JavaScript -->
<script defer src="/js/analytics.js"></script> Monitor LCP in the field using the CrUX report in Google Search Console. Lab tools show potential issues; field data shows real user impact. Google uses the 75th percentile of real-user measurements.
2 Achieve INP Under 200ms
Interaction to Next Paint measures the latency of all interactions throughout the page lifecycle. Long JavaScript tasks (over 50ms) block the main thread and make the page feel unresponsive. Break them up by yielding to the main thread.
// Modern approach: scheduler.yield() (Chrome 115+)
async function processItems(items) {
for (const item of items) {
heavyProcess(item);
await scheduler.yield(); // yield without timeout overhead
}
}
// Fallback: yield via setTimeout
async function processItemsWithFallback(items) {
for (const item of items) {
heavyProcess(item);
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
} else {
await new Promise(r => setTimeout(r, 0));
}
}
}
// Debounce event handlers to reduce main thread pressure
function debounce(fn, delay = 150) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
window.addEventListener('resize', debounce(() => {
// handle resize
}, 150)); 3 Achieve CLS Under 0.1
Cumulative Layout Shift measures how much visible content shifts during page load. The main culprits: images without dimensions, dynamically injected content, web fonts causing FOUT, and ads that resize after loading.
<!-- Always set explicit width and height -->
<img src="/image.webp" width="800" height="450" alt="" loading="lazy">
<!-- For responsive images, CSS handles the rest -->
img, video, iframe {
max-width: 100%;
height: auto;
}
<!-- Reserve space for embeds -->
<div style="position: relative; padding-bottom: 56.25%; height: 0;">
<iframe src="https://www.youtube.com/embed/..."
style="position: absolute; top: 0; left: 0;
width: 100%; height: 100%;"></iframe>
</div>
<!-- Reserve space for ads -->
<div style="min-height: 250px; width: 100%;">
<div id="ad-slot"></div>
</div> Eliminate Render-Blocking Resources
The single highest-impact fix for LCP on most sites.
Render-blocking resources are CSS stylesheets and JavaScript files in the HTML head that prevent the browser from painting any content until they are fully downloaded, parsed, and executed. Every render-blocking resource adds latency to LCP.
The fix has three parts:
1. Inline critical CSS — Extract the minimum CSS needed to render above-the-fold content and place it directly in a <style> tag in the HTML head. Tools like Critical (npm package) automate this.
2. Load non-critical CSS asynchronously — Use rel=preload with an onload handler to load the full stylesheet after the page has painted. Add a noscript fallback.
3. Defer non-critical JavaScript — Add the defer or async attribute to script tags. Deferred scripts execute after HTML parsing; async scripts execute as soon as they download. Use defer for scripts that depend on DOM order.
<!-- Inline critical CSS directly in the HTML head -->
<style>
/* Critical above-the-fold styles only */
body { font-family: sans-serif; margin: 0; }
.hero { display: flex; min-height: 60vh; }
.nav { position: sticky; top: 0; }
</style>
<!-- Load non-critical CSS asynchronously -->
<link rel="preload" as="style" href="/css/full.css"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/full.css"></noscript>
<!-- Defer non-critical JavaScript -->
<script defer src="/js/main.js"></script>
<script defer src="/js/analytics.js"></script>
<!-- Use async for independent scripts (analytics, tags) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"></script> Script Loading Behavior
CSS Containment Implementation
Tell the browser which parts of the page are independent to skip unnecessary recalculations.
CSS containment (the contain property) tells the browser that an element's subtree is independent from the rest of the page. This lets the browser skip layout and paint recalculations for that element when changes happen elsewhere, improving rendering performance by 20-50% on complex pages.
The four containment types:
layout — Internal layout does not affect elements outside. The element becomes a containing block for positioned descendants.
paint — Nothing inside paints outside the element bounds. Clipping is applied automatically.
size — The element can be laid out without examining its children. Use when you know the size in advance.
content — Combines layout and paint (but not size). The safest default for most components.
/* Apply containment to independent components */ .card { contain: layout paint; } /* For list items where you know the height */ .list-item { contain: layout paint size; height: 80px; } /* For complex widgets with frequent DOM mutations */ .widget { contain: content; } /* Strict containment for static, sized elements */ .sidebar { contain: layout paint size; width: 300px; } /* Contain inline styles for dynamic content */ .comment { contain: layout paint; content-visibility: auto; contain-intrinsic-size: 0 200px; }
content-visibility: auto is a related property that defers rendering of off-screen content entirely. Combined with contain-intrinsic-size, it tells the browser how much space to reserve for content it has not yet rendered. This dramatically improves initial load performance for long pages.
Containment Types
Mobile & Responsive Design
Mobile-first indexing means your mobile experience is your primary ranking experience.
4 Pass Mobile-Friendly Test
Google uses mobile-first indexing. Your mobile version is the primary version for ranking. Use the viewport meta tag, responsive images, readable font sizes, and adequate tap targets.
<!-- Required viewport meta tag (must be in every page) -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Mobile-first CSS approach -->
/* Base styles for mobile (smallest screens) */
.container {
padding: 0 16px;
width: 100%;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 0 24px;
max-width: 720px;
margin: 0 auto;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
max-width: 960px;
}
}
/* Large desktop */
@media (min-width: 1200px) {
.container {
max-width: 1140px;
}
} 8 Set Proper Viewport Meta Tag
Without the viewport meta tag, mobile browsers render pages at desktop width (typically 980px) and then scale down, making text unreadable. This is a hard requirement for mobile-first indexing.
<!-- Correct viewport configuration -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Do NOT use these (they break mobile UX): -->
<!-- <meta name="viewport" content="width=320"> -->
<!-- <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no"> -->
<!-- Responsive images scale with viewport -->
<img src="/image.webp"
srcset="/image-400.webp 400w,
/image-800.webp 800w,
/image-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw"
width="1200" height="600"
loading="lazy"
alt="Description"> 10 Use Readable Font Sizes (16px+)
Google recommends a base font size of at least 16px for body text on mobile. Smaller text forces users to pinch-to-zoom, which is a negative mobile UX signal. Use relative units (rem) for scalable typography.
/* Set base font size on html element */ html { font-size: 16px; /* 1rem = 16px */ } /* Use rem for all typography */ body { font-size: 1rem; /* 16px */ line-height: 1.6; } h1 { font-size: 2rem; } /* 32px */ h2 { font-size: 1.5rem; } /* 24px */ h3 { font-size: 1.25rem; }/* 20px */ p { font-size: 1rem; } /* 16px */ /* Ensure minimum tap target size: 48x48px */ button, a, input, select { min-height: 48px; min-width: 48px; }
11 Maintain Adequate Tap Target Sizes
Google recommends tap targets of at least 48x48px with at least 8px of spacing between targets. Small, tightly packed tap targets cause mis-taps on mobile, which is a negative UX signal.
/* Minimum tap target size */ button, a.button, .nav-link { min-height: 48px; min-width: 48px; padding: 12px 24px; display: inline-flex; align-items: center; justify-content: center; } /* Spacing between adjacent tap targets */ .nav-link + .nav-link { margin-left: 8px; } /* For inline links in text, ensure enough padding */ .content a { padding: 4px 0; display: inline; }
Font Loading Optimization
Custom fonts cause CLS and delay text rendering. Here is how to load them without penalties.
Custom web fonts cause CLS in two ways: FOIT (Flash of Invisible Text) where text is hidden until the font loads, and FOUT (Flash of Unstyled Text) where text swaps from fallback to custom font causing layout shift. The goal is to show text immediately and minimize the shift when the custom font arrives.
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-font.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
font-weight: 400;
}
/* Preload the font for earlier discovery */
<link rel="preload" as="font" href="/fonts/custom-font.woff2"
type="font/woff2" crossorigin>
/* Use size-adjust to match fallback metrics to custom font */
@font-face {
font-family: 'CustomFont-Fallback';
src: local('Arial');
size-adjust: 104%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: 'CustomFont', 'CustomFont-Fallback', sans-serif;
} Best practices: Only load the font weights and styles you actually use. Subset fonts to include only the character sets you need. Use woff2 format exclusively — it has the best compression. Self-host fonts instead of loading from Google Fonts to eliminate the extra DNS lookup and connection.
Font Display Options
Minimize Main Thread Work
Heavy JavaScript blocks the main thread and degrades INP and TBT.
12 Minimize Main Thread Work
The browser main thread handles all JavaScript execution, layout, paint, and compositing. When a long task occupies the main thread for more than 50ms, the page becomes unresponsive to user input. Use Chrome DevTools Performance tab to identify long tasks.
// Break up long tasks using scheduler.yield()
async function processLargeDataset(items) {
const results = [];
for (let i = 0; i < items.length; i++) {
results.push(expensiveOperation(items[i]));
// Yield every 50 items to keep the main thread free
if (i % 50 === 0) {
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
} else {
await new Promise(r => setTimeout(r, 0));
}
}
}
return results;
}
// Use Web Workers for CPU-intensive work
// worker.js
self.addEventListener('message', (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
});
// main.js
const worker = new Worker('/js/worker.js');
worker.postMessage(data);
worker.addEventListener('message', (e) => {
updateUI(e.data);
});
// Use requestIdleCallback for non-critical work
function scheduleNonCriticalWork(task) {
if ('requestIdleCallback' in window) {
requestIdleCallback(task, { timeout: 2000 });
} else {
setTimeout(task, 1);
}
} 15 Avoid Excessive DOM Size
Google flags pages with more than 1,500 DOM nodes. Large DOM trees increase memory usage, style recalculation time, and layout complexity. Use pagination, virtual scrolling, or lazy rendering to keep the DOM lean.
<!-- Use content-visibility for off-screen content -->
<div style="content-visibility: auto; contain-intrinsic-size: 0 500px;">
<!-- Off-screen content deferred until scrolled into view -->
</div>
<!-- Virtual list pattern for long lists -->
// Only render visible items + buffer
function renderVisibleItems(items, container, itemHeight) {
const scrollTop = container.scrollTop;
const viewportHeight = container.clientHeight;
const startIndex = Math.floor(scrollTop / itemHeight) - 5;
const endIndex = Math.ceil((scrollTop + viewportHeight) / itemHeight) + 5;
// Only create DOM nodes for visible range
return items.slice(
Math.max(0, startIndex),
Math.min(items.length, endIndex)
);
} 13 Use Efficient Cache Policies
Proper caching reduces server load and improves repeat-visit performance. Set long cache durations for static assets (images, fonts, CSS, JS) and use cache-busting via file hashes for updates.
# Nginx cache configuration
# Images and fonts: cache for 1 year (immutable)
location ~* \.(webp|avif|jpg|png|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# CSS and JS with hash in filename: cache for 1 year
location ~* \.(css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML: short cache or no cache
location ~* \.html$ {
expires 0;
add_header Cache-Control "no-cache";
}
# Enable Brotli compression
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript
image/svg+xml application/json; 14 Enable Text Compression
Brotli compression reduces text-based asset sizes by 15-25% more than Gzip. Enable it at the server or CDN level for HTML, CSS, JavaScript, SVG, and JSON.
# Nginx: Enable Brotli (requires ngx_brotli module)
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript
image/svg+xml application/json text/xml;
# Fallback: Enable Gzip
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types text/html text/css application/javascript
image/svg+xml application/json text/xml;
# Cloudflare: Brotli is enabled by default
# Go to Speed > Optimization > Content Optimization
# Ensure "Brotli" is set to "On" User Experience Tools
Free tools for measuring, monitoring, and debugging page experience.
Google PageSpeed Insights
Lab and field data for any URL. Shows LCP, INP, CLS with detailed improvement suggestions and CrUX field data.
FreeLighthouse
Open-source automated tool for auditing performance, accessibility, SEO, and best practices. Built into Chrome DevTools.
FreeChrome DevTools Performance
Record and analyze runtime performance. Identify long tasks, render-blocking resources, layout shifts, and main thread bottlenecks.
FreeWebPageTest
Detailed waterfall charts, filmstrip view, and multi-location testing. Advanced debugging for render-blocking and load order issues.
FreeGTmetrix
Performance monitoring with Lighthouse-powered audits, historical tracking, and alerts for CWV regressions.
FreeCrUX Dashboard
Chrome User Experience Report dashboard. Real-user performance data from millions of Chrome users. Track CWV trends over time.
FreeCommon UX Mistakes
Bad practices that wreck page experience and Core Web Vitals.
Bad Practice vs Best Practice
Frequently Asked Questions
Common questions about user experience and page experience for SEO.
Core Web Vitals are three specific page speed and interaction metrics Google uses as ranking factors: Largest Contentful Paint (LCP measures loading speed, target under 2.5s), Interaction to Next Paint (INP measures interactivity, target under 200ms), and Cumulative Layout Shift (CLS measures visual stability, target under 0.1). They were made official ranking signals as part of the Google Page Experience update and continue to influence rankings in 2026.
Use Google PageSpeed Insights (pagespeed.web.dev) for lab and field data on any URL. Chrome DevTools Lighthouse panel runs lab audits locally. Google Search Console has a dedicated Core Web Vitals report showing real-user CrUX data across your entire site. WebPageTest provides detailed waterfall charts for advanced debugging. All tools show LCP, INP, and CLS scores with improvement suggestions.
Yes. Google uses mobile-first indexing, meaning it primarily uses the mobile version of your content for indexing and ranking. Pages that are not mobile-friendly will rank lower in mobile search results. This means responsive design, proper viewport configuration, readable font sizes (16px minimum), and adequate tap target sizes (48x48px minimum) are all ranking factors.
Google recommends LCP under 2.5 seconds. Pages that load in under 2 seconds have the lowest bounce rates. Every additional second of load time increases bounce rate by approximately 32%. For TTFB (server response time), target under 200ms. Use a CDN, enable server-side caching, optimize database queries, and compress assets with Brotli or Gzip to hit these targets.
Render-blocking resources are CSS stylesheets and JavaScript files in the HTML head that prevent the browser from painting content until they are fully downloaded and parsed. To eliminate them: inline critical above-the-fold CSS in a style tag, load non-critical CSS asynchronously using rel=preload with an onload handler, add the defer attribute to non-critical JavaScript files, and remove unused CSS with tools like PurgeCSS or Chrome DevTools Coverage tab.
CSS containment (the contain property) tells the browser that an element's subtree is independent from the rest of the page. This lets the browser skip layout and paint recalculations for that element when changes happen elsewhere, improving rendering performance. Use contain: layout paint on components like cards, list items, and widgets. For off-screen content, use content-visibility: auto with contain-intrinsic-size to defer rendering entirely.
Use font-display: swap to show text immediately with a fallback font while the custom font loads. Preload critical fonts with link rel=preload as=font. Use the size-adjust, ascent-override, and descent-override CSS descriptors to create a fallback font that matches the custom font metrics, eliminating layout shift during the swap. Limit font weights and subsets to only what you actually use. Self-host fonts in woff2 format to eliminate external DNS lookups.
The viewport meta tag tells the browser how to control the page dimensions and scaling on mobile devices. Without it, mobile browsers render pages at a desktop width (typically 980px) and then scale down, making text unreadable. The correct tag is: meta name=viewport content='width=device-width, initial-scale=1'. This is a hard requirement for mobile-first indexing and passing Google mobile-friendly tests. Never set maximum-scale=1 or user-scalable=no as these block pinch-to-zoom and hurt accessibility.