← Back to Blog

Why your site feels slow even when PageSpeed says it's fast

Why your site feels slow even when PageSpeed says it's fast

You've optimized your images, minified your CSS, lazy-loaded everything below the fold, and PageSpeed Insights gives you a green score. Then a real person visits your site on a mid-range phone, taps a button, and nothing happens for 400 milliseconds. They tap again. Still nothing. They leave.

PageSpeed measures how fast your page loads. First Input Delay (FID) and its successor Interaction to Next Paint (INP) measure how fast your page responds once someone tries to interact with it. These are different things, and the second one is the one your visitors actually feel.

The gap between loading and responding

When a browser loads a page, it goes through a sequence: fetch HTML, parse CSS, execute JavaScript, paint pixels. During the JavaScript execution phase, the browser's main thread is busy. If a user taps a button, clicks a link, or tries to type in a form field while the main thread is executing a script, the browser queues that input and processes it later.

That queue time is your First Input Delay. The user clicked. The browser acknowledged. But it couldn't respond until the script finished running. On a fast laptop, that script might take 50ms. On a three-year-old Android phone on a 4G connection, the same script might block the main thread for 300-600ms.

Google replaced FID with INP (Interaction to Next Paint) as a Core Web Vital in March 2024, but the underlying problem is the same: main thread blocking. INP is actually stricter because it measures every interaction throughout the page's lifetime, not just the first one.

What causes main thread blocking

Third-party scripts. Google Analytics, Facebook Pixel, Hotjar, Intercom, cookie consent banners, chat widgets. Each one runs JavaScript on page load, and many of them run synchronously on the main thread. A typical small business site loads 5-8 third-party scripts, and their combined execution time can block the main thread for 500ms or more.

Framework hydration. React, Vue, and Next.js pages that use server-side rendering still need to "hydrate" on the client. The browser downloads the pre-rendered HTML (fast paint), then downloads and executes the JavaScript bundle to make the page interactive. Until hydration completes, buttons and forms look clickable but don't respond. This is the "uncanny valley" of web performance: the page looks ready but isn't.

Unoptimized event handlers. A click handler that triggers a layout recalculation, a scroll listener that runs on every pixel, a form validation function that parses the entire DOM. Any of these can block the main thread during the exact moment a user is trying to interact.

Large JavaScript bundles. Even if your code is well-structured, a 500KB JavaScript bundle takes time to parse and compile. On mobile, the parse time alone can block the main thread for 200-400ms.

What the audit checks

The First Input Delay Audit analyzes your page for the most common causes of interaction delay:

Script execution timing. It identifies scripts that block the main thread during the critical first-interaction window and estimates their blocking duration.

Third-party script inventory. Lists every external script with its estimated main-thread cost, sorted by impact. This is usually where the quick wins are.

Event handler analysis. Flags synchronous event handlers that could cause interaction delays, especially handlers attached to high-frequency events like scroll and resize.

Bundle size assessment. Reports total JavaScript parsed on page load and flags bundles that exceed the 150KB compressed threshold where parse time becomes noticeable on mobile.

Practical fixes

Defer third-party scripts. Move analytics, chat widgets, and tracking pixels to load after the page is interactive. Use defer or async attributes, or better yet, load them programmatically after a user interaction event. Your analytics will still capture the data; it just won't block the first interaction.

Break up long tasks. Any JavaScript task that runs for more than 50ms is a "long task" in Web Vitals terminology. Use requestIdleCallback or setTimeout(fn, 0) to break synchronous work into smaller chunks that yield back to the main thread between executions.

Reduce bundle size. Code-split your JavaScript so users only download the code needed for the current page. Tree-shaking, dynamic imports, and route-based splitting can cut initial bundle size by 40-70%.

Use web workers for heavy computation. Anything that doesn't need DOM access (data processing, form validation logic, analytics computation) can run in a web worker thread without blocking the main thread.

Pair this audit with the Render-Block Audit to catch CSS and font loading that compounds with JavaScript blocking, and the Third-Party Tag Weight tool for a detailed breakdown of external script costs.

If you're building sites for clients, The $97 Launch includes a performance-first architecture that keeps main thread blocking under 100ms from the start.

Fact-check notes and sources

Related reading

This post is informational, not web-performance consulting advice. Mentions of Google, React, and other technologies are nominative fair use. No affiliation is implied.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026