Interaction to Next Paint (INP) is the Core Web Vitals responsiveness metric that teams are now measured against. It replaced First Input Delay in March 2024, and it is significantly harder to pass because it captures every interaction during a session, not just the first one.
Many teams focus their optimisation effort on application code: component rendering, data fetching, hydration. That work matters. But on a surprising number of pages, the real INP bottleneck is third-party JavaScript — tag managers, chat widgets, analytics libraries, heatmaps, experimentation tools — running expensive work on the main thread at exactly the wrong moment.
A single user interaction can feel slow not because your app code is heavy, but because the browser is busy parsing, evaluating, or executing non-critical vendor code that nobody on the engineering team explicitly asked for on that route.
TL;DR
- Third-party scripts hurt INP when they create long tasks that block the main thread.
- If a user interacts while that work is happening, the browser cannot respond or paint quickly.
- The most effective fixes: delay non-essential execution, lazy-load on intent, use facades for heavy embeds, and offload suitable scripts to worker-based strategies when practical.
For background on how INP replaced FID and what the current thresholds look like, see Core Web Vitals: What's Changed.
Why INP is especially unforgiving on real mobile devices
INP is a field-oriented metric. It is measured on real users, real devices, and real network conditions — not in a Lighthouse lab run on a fast developer laptop. That distinction matters enormously for third-party script impact.
A tag manager callback that completes in 15 ms on a high-end MacBook can take 80–120 ms on a mid-range Android device. On a fast machine, it never shows up as a long task. On a real user's phone, it blocks the main thread long enough to delay an interaction and push INP above the 200 ms threshold.
This gap between lab and field is one of the main reasons third-party script impact goes unnoticed during development. The engineer profiling on a fast desktop sees no problem. The user tapping a button on a three-year-old phone in Johannesburg or a crowded London commuter train sees a stalled UI.
Lower-powered processors, constrained memory, and variable network conditions all amplify main-thread blocking. If your audience includes any meaningful share of mobile traffic, lab-only testing will undercount the real INP cost of third-party scripts.
Diagnosis: how to find the real culprits
Before fixing anything, identify which scripts are actually causing the damage. Not all third-party code is equally expensive.
Chrome DevTools Performance recordings
Record a session that includes realistic user interactions (clicks, taps, scrolls, form inputs). Then inspect the results:
- Long tasksappear as red-flagged blocks on the Main thread track. Any task over 50 ms is a long task.
- Interactions track (available in newer Chrome versions) shows which user interactions were delayed and what was running on the main thread at that moment.
- Bottom-Up tab lets you group execution time by domain or URL, making it easy to see how much time each third-party origin consumed.
Common third-party categories to check
- Tag managers— orchestrate other scripts, sometimes firing dozens of tags on every route change.
- Chat and support widgets— often load multiple scripts, stylesheets, and iframes on page start.
- Analytics libraries— event listeners and data batching can create periodic main-thread spikes.
- Heatmap and session replay tools— continuously observe DOM mutations and can create sustained background work.
- A/B testing and experimentation— may execute synchronously to avoid visual flicker, blocking interactions during evaluation.
The goal is to separate your rendering work from vendor script evaluation. If the flame chart shows long tasks attributed to external domains during user interactions, those are your targets.
Do not want to manually inspect every page? Use CodeAva Website Audit to surface third-party scripts and technical signals that may be contributing to poor responsiveness.
The engineering fixes: a prioritised playbook
Fix 1: Lazy-load on intent
When to use it: chat widgets, support tools, feedback forms, and any embed that is not needed until the user explicitly requests it.
Instead of loading the vendor script at page start, show a lightweight placeholder. Inject the real script only when the user signals intent — a click, a hover, a scroll into the relevant viewport area.
// Lazy-load a chat widget on first click
function initChat() {
const script = document.createElement("script");
script.src = "https://chat-vendor.example/widget.js";
script.async = true;
document.body.appendChild(script);
}
const chatButton = document.getElementById("chat-placeholder");
chatButton?.addEventListener("click", initChat, { once: true });Trade-offs: the widget takes a moment to initialise after the first click. Users see a brief loading state. For most support chat use cases, this is an acceptable trade compared to blocking the main thread on every page load for every user.
Fix 2: Use facades and lazy iframes for embeds
When to use it: YouTube videos, Google Maps, social media embeds, and any heavy iframe-based widget.
Embedded iframes bring their own document, scripts, stylesheets, and network requests. A YouTube embed, for example, loads over 500 KB of JavaScript even if the user never presses play.
A facade is a lightweight preview (typically a static image and a play button) that replaces the iframe until the user interacts. On click, the facade swaps in the real embed.
<!-- Lazy iframe: loads only when near the viewport --> <iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID" loading="lazy" title="Video title" allow="accelerometer; autoplay; encrypted-media; gyroscope" allowfullscreen ></iframe>
Trade-offs: the facade adds a small interaction cost (one click before the embed loads). For below-the-fold content this is almost always worth it. For hero-area video, consider whether the embed is truly needed on initial render.
Fix 3: Offload suitable scripts to worker-based execution
When to use it: analytics, tracking pixels, and data-collection scripts that mostly listen to events and send network requests without heavy DOM manipulation.
Libraries like Partytown move third-party scripts into a Web Worker, keeping the main thread free for user interactions. The worker communicates with the DOM through a synchronous proxy layer.
Partytown is not a universal fix
Trade-offs: added complexity, potential compatibility issues with some vendor scripts, and a small communication overhead between worker and main thread. For analytics-heavy pages, the INP improvement can be substantial. For DOM-intensive third-party code, it may not be a good fit.
Fix 4: Yield and chunk unavoidable work
When to use it: when heavy third-party logic must stay on the main thread and cannot be deferred or offloaded.
If a script creates a single long task, the browser cannot respond to interactions until that task completes. Breaking the work into smaller chunks gives the browser opportunities to process pending interactions between chunks.
// Yield to the browser between chunks of work
async function processInChunks(items, processFn) {
for (const item of items) {
processFn(item);
// Modern: scheduler.yield() (progressive enhancement)
if ("scheduler" in globalThis && "yield" in scheduler) {
await scheduler.yield();
} else {
// Fallback: yield via setTimeout
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
}scheduler.yield() is a modern browser API that explicitly yields the main thread back to the browser so it can handle pending interactions. It is not yet available in all browsers, so treat it as progressive enhancement with a setTimeout fallback.
Trade-offs: chunking adds latency to the total processing time. The work takes longer end-to-end, but the page stays responsive during execution. For user-facing interactions, that is usually the right trade.
Fix 5: GTM and third-party governance
When to use it: always. This is an organisational fix, not just a technical one.
Performance problems are often organisational as much as technical. Marketing and product teams add tags through Google Tag Manager (or similar tools) without retirement rules. Over time, GTM becomes an ungoverned dumping ground of legacy tags, experiment remnants, and duplicate tracking scripts.
Practical governance steps:
- Build a third-party inventory. List every script, its owner, its purpose, and when it was last reviewed.
- Remove dead tags. If an experiment ended six months ago, its tag should not still be firing.
- Set trigger discipline. Not every tag needs to fire on every page or every route change. Scope triggers to relevant pages and events.
- Use performance budgets. Set a maximum third-party JavaScript budget per template and enforce it in CI.
- Assign ownership. Every tag should have a named owner who is responsible for its continued relevance and performance impact.
Anti-patterns: what not to do
async and defer are not enough
async and defer prevent a script from blocking HTML parsing. But once the script executes, it can still create long tasks and hurt INP. Do not assume that adding async makes a script harmless.- Loading chat/support widgets on every page immediately. Most users never open the chat. Loading it eagerly penalises everyone for the benefit of a small percentage.
- Embedding heavy third-party iframes without lazy loading. An eager YouTube or Maps embed loads hundreds of kilobytes of JavaScript even if it is below the fold and never viewed.
- Letting GTM become an ungoverned dumping ground. Without retirement rules, tag count grows monotonically. Every tag adds main-thread cost.
- Shipping scripts globally that are only needed on a small subset of routes. A pricing-page experiment script should not run on every blog post.
- Treating all third-party code as equally important. Prioritise. A payment SDK on the checkout page is critical. A heatmap on every page may not be.
Practical decision table
| Third-party type | Default mistake | Better strategy | Why it helps INP |
|---|---|---|---|
| Chat widget | Load on page start | Load on user intent (click/hover) | Avoids blocking main-thread work before any interaction |
| Analytics / tracking | Run entirely on main thread | Evaluate worker-based / off-main-thread options | Reduces contention during user interactions |
| YouTube / map embeds | Eager iframe load | Facade + lazy iframe | Cuts script and iframe overhead until needed |
| Tag manager | All tags on every page | Governance + trigger discipline | Reduces unnecessary execution per route |
| Unavoidable heavy logic | One long task | Chunk and yield | Gives the browser chances to respond between chunks |
How to monitor and prevent regressions
Fixing third-party script impact is not a one-time effort. New tags get added. Vendors push updates. Marketing launches campaigns. Without ongoing monitoring, INP regressions creep back.
- Compare field data to lab data. CrUX (Chrome User Experience Report) shows real-user INP at the origin and URL level. If field INP is significantly worse than lab, third-party scripts running in production (but not in your local dev environment) are a likely cause.
- Track INP after marketing or product changes. When a new tag is added or a campaign launches, check whether INP moves. Correlation is not causation, but a spike after a tag addition is a strong signal.
- Review script additions during releases. Make third-party script changes visible in pull requests and release notes, not buried in GTM.
- Maintain a third-party budget. Set a maximum total size and execution-time budget for third-party JavaScript per page template. Enforce it in CI or as a pre-deploy check.
- Re-profile high-traffic templates regularly. Homepage, product pages, and checkout deserve periodic Performance recordings to catch regressions before they affect enough users to degrade your CrUX scores.
Use CodeAva Website Audit as a release or QA checkpoint to surface technical signals early. It does not replace deep performance profiling, but it provides an early-warning layer before regressions reach enough users to move your field scores.
Conclusion
You may not control vendor code quality, but you do control when and how vendor code runs on your domain. Better INP usually comes from execution discipline, not just bundle slimming.
Three principles that protect responsiveness:
- Delay what is not needed now. Lazy-load on intent. Use facades. Do not pay the cost of every embed on every page load.
- Move what you can off the main thread. Evaluate worker-based execution for analytics and tracking scripts. Test carefully.
- Govern what stays. Build an inventory. Remove dead tags. Set budgets. Assign ownership. Treat third-party performance as an ongoing operational concern, not a one-time audit.
Protect your responsiveness budget before new tags reach production. Use CodeAva Website Audit as part of your release workflow, then validate high-impact pages with real performance profiling.





