Third-party scripts on a Next.js 15 landing page: keep Core Web Vitals green
You ship a fast SEO landing page—optimized hero, stable layout, green LCP. Then marketing adds Google Tag Manager, Intercom, Meta Pixel, and a heatmap tool. A week later, Search Console flags Poor INP on mobile. Clicks on the primary CTA feel sluggish because the main thread is busy running code you never wrote.
Third-party scripts are the most common source of Core Web Vitals regressions after image and font work is done. They are not part of your Next.js bundle, but they still compete for CPU, block parsing when loaded incorrectly, and attach expensive global listeners. Google grades URLs using field data (CrUX), which includes real users loading all those tags.
This guide covers a production setup for Next.js 15 App Router: next/script strategies, deferred analytics, Partytown where it fits, chat lazy-loading, and a release checklist—so you keep attribution without sacrificing INP or LCP.
How third-party code hurts INP (and sometimes LCP)
INP measures latency from user input to the next paint. Vendor scripts hurt it through:
- Long tasks during parse and execution on the main thread
- Global listeners on
click,scroll, andtouchstart(session replay tools are notorious) - Network contention when render-blocking scripts in
<head>delay hero images and fonts
LCP is less often the culprit, but sync GTM snippets in <head> or a chat launcher injected above the fold can still push LCP past 2.5s.
| Service | Typical gzip weight | CWV risk |
|---|---|---|
| GA4 (gtag) | ~45–90 KB | INP |
| Google Tag Manager | container + tags | LCP, INP |
| Meta Pixel | ~30–80 KB | INP |
| Intercom / Crisp | 150–400 KB+ | INP, layout |
| Hotjar / Clarity | 80–200 KB | INP |
For a service landing page, analytics plus one conversion pixel is a reasonable default. Session replay and live chat on the first screen are explicit trade-offs—measure INP after every new tag.
next/script strategies that actually help
In the App Router, next/script coordinates load order with hydration and avoids duplicate execution on client navigations.
| strategy | Loads | Use on landing pages |
|---|---|---|
beforeInteractive | Before hydration | Rare; critical polyfills only |
afterInteractive | Right after hydration | GTM only if unavoidable |
lazyOnload | After window.load | Default for GA4, pixels |
worker | Web Worker (Partytown) | GA4 / pixels when INP is tight |
GA4 pattern
'use client';
import Script from 'next/script';
const GA_ID = process.env.NEXT_PUBLIC_GA_ID;
export function Analytics() {
if (!GA_ID) return null;
return (
<>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`}
strategy="lazyOnload"
/>
<Script id="ga4-init" strategy="lazyOnload">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_ID}', { anonymize_ip: true });
`}
</Script>
</>
);
}
Mount <Analytics /> at the end of body in app/layout.tsx, not via raw tags in <head>. Never use beforeInteractive for analytics—it is not needed for first paint and hurts LCP.
For CTA clicks, defer gtag('event', ...) with requestIdleCallback so navigation and UI updates win the race on the main thread.
If you need similar implementation work — message on Telegram.
GTM: useful for marketing, expensive for CWV
GTM lets marketers ship tags without deploys. For a performance-focused landing page, it is often an anti-pattern: the container loads early, then fires multiple tags—each adding long tasks.
Practical rules:
- Prefer direct gtag when you have ≤ 3 tags (GA4 + one Ads tag + one pixel).
- If GTM is required: single container,
strategy="lazyOnload", triggers on Window Loaded, not synchronous Custom HTML on every page view. - Enable Consent Mode v2 before ad tags fire.
- Consider server-side GTM (sGTM) so the browser only runs a thin transport—better INP on mid-tier phones.
Partytown, chat widgets, and the facade pattern
Partytown moves compatible scripts to a Web Worker via strategy="worker" (requires experimental.nextScriptWorkers in next.config.ts). GA4 often works; chat widgets that touch document directly frequently do not.
For Intercom-style tools, use lazy mount:
- Load the SDK after
load+requestIdleCallback(or after 25% scroll). - Avoid placing the default bubble over the hero CTA.
- Use a facade button (zero SDK cost) and boot the widget on click.
That keeps hundreds of kilobytes of parse cost off the critical path until the user actually wants chat.
Monitor regressions with Speed Insights
Optimize with field data, not Lighthouse alone. Add @vercel/speed-insights next to your analytics component and compare Search Console CWV before and after every GTM change.
Workflow:
- Record a baseline in Search Console.
- Maintain a tag changelog (date + what was added in GTM).
- Run Lighthouse CI on PRs with thresholds on Total Blocking Time.
- Segment by route—blog pages may omit chat; the homepage carries the full stack.
Edge caching speeds up your HTML (ISR/PPR on Vercel), but third-party assets always come from vendor domains—your lever is deferral and count, not CDN cache.
Need help? Telegram → or vic.kell@ya.ru
FAQ
Does lazyOnload miss the first pageview?
On a static service landing, delay is usually negligible. For fast-bounce ad landings, you might use afterInteractive for gtag only and keep everything else lazy. SPAs should send page_view on pathname changes in useEffect.
How many third-party scripts are “enough”?
Aim for ≤ 150 KB compressed third-party JS before load and no more than 3–4 origins. Add one tag at a time and re-check INP.
Is Partytown production-ready in Next.js 15?
Yes, with testing per tag. Keep a non-Partytown fallback for chat and A/B tools.
Do third parties affect rankings directly?
Not as a “domain list” ranking factor, but through CWV and engagement. Poor INP on mobile correlates with worse conversion and weaker user signals.
Conclusion
On a Next.js 15 SEO landing page, third-party scripts need the same discipline as images and fonts: lazyOnload for analytics, minimize GTM, defer chat SDKs, and use Partytown only where compatible. Pair that with Speed Insights and a marketing changelog so attribution stays intact while INP stays in the green zone alongside your LCP and CLS work.