How to Self-Host Third-Party Scripts (and When Not To)
This guide sits within Third-Party Script Performance in JavaScript Bundle Optimization & Code Splitting. Every third-party origin a page contacts costs DNS, TCP and TLS setup before the first byte — 100–300ms on mobile, and more where the vendor's edge is far from the user. Scripts from those origins also compete with your own resources under browser-imposed priorities you do not control, and their caching headers are set by someone else, often with short lifetimes.
Self-hosting — serving the vendor's script from your own origin, either by mirroring the file at build time or proxying it through your CDN — removes the extra connection and puts caching in your hands. It is not free: vendors update scripts for bug fixes, compliance and API changes, and a mirrored copy can go stale or break when their backend changes. The right approach depends on the script's update cadence, its criticality and the vendor's terms.
Rapid Diagnosis
- List third-party origins on the critical path. In the Network panel, filter by domain; note connection setup times for each in the Timing tab.
- Check cache headers on vendor scripts.
Cache-Control: max-age=3600or shorter means returning visitors re-validate often. - Check script stability. Download the script twice over a week and diff it; frequently changing scripts are poor mirroring candidates.
- Read the vendor's terms. Some explicitly prohibit self-hosting; others document a proxy setup.
Root Cause Analysis
1. Connection setup per origin. Each vendor domain needs its own connection; on high-latency networks that dominates small script downloads.
2. Short vendor cache lifetimes. Vendors keep TTLs short so they can push updates, which forces returning visitors to revalidate or re-download.
3. Single points of failure. A slow or unreachable vendor CDN delays any synchronous script and can block rendering.
4. Priority you cannot tune. Cross-origin scripts are fetched with default priorities; you cannot preload them as effectively without preconnecting.
Step-by-Step Resolution
1. Proxy through your CDN for frequently updated scripts
Route a path on your domain to the vendor's script URL with a modest TTL. You get connection reuse and control over caching without freezing the version.
location = /vendor/analytics.js {
proxy_pass https://cdn.vendor.example/analytics.js;
proxy_cache vendor; proxy_cache_valid 200 1h;
proxy_cache_use_stale error timeout updating; # keep serving if the vendor is down
add_header Cache-Control "public, max-age=3600, stale-while-revalidate=86400";
# trade-off: proxying forwards your users' requests through your edge; make
# sure you are not leaking cookies to the vendor or caching personalised
# responses. Strip cookies on this location.
}
Expected outcome: the script loads over your existing connection, with your caching rules and resilience to vendor outages.
2. Mirror stable scripts at build time with integrity
For scripts that rarely change (a facade library, a widget loader), download them during the build, fingerprint the file and serve it as an immutable asset.
curl -fsSL https://cdn.vendor.example/widget-loader@2.4.1.js -o public/vendor/widget-loader.$(sha256sum <(curl -fsSL https://cdn.vendor.example/widget-loader@2.4.1.js) | cut -c1-8).js
# trade-off: a mirrored file never updates on its own. Schedule a job that
# re-downloads and diffs it weekly, and pin to a versioned vendor URL so the
# mirror is reproducible.
Expected outcome: immutable caching for a script that otherwise revalidates hourly.
3. Keep the vendor's API calls on the vendor's origin
Self-hosting the loader script does not mean proxying data collection. Beacons and API calls can stay on the vendor's domain — they are usually asynchronous and off the critical path. Proxying them too is possible (some analytics vendors document first-party collection endpoints) but adds operational responsibility.
4. Remove the old preconnect and confirm
Once a vendor's script is self-hosted, remove preconnect/dns-prefetch hints for its origin unless other requests still need them early.
Verification
In a throttled trace, the script should load on your origin's connection with no DNS/TCP/TLS phase. Compare the time from navigation to script execution before and after; on mobile, savings of 150–300ms per removed origin are typical. Check the vendor's dashboard still receives data and that features behave identically. Set up an alert for proxy errors so vendor outages are visible.
Worked Example: A/B Testing Snippet on the Critical Path
An e-commerce site loaded an A/B testing script synchronously in the head (required to avoid flicker), from the vendor's domain with a 5-minute cache TTL. On mobile, the connection setup alone added 220ms before the script could execute, and every hard navigation revalidated it. Proxying it through the site's CDN with a 10-minute edge TTL and stale-while-revalidate removed the connection setup; LCP improved by roughly 200ms at p75 on mobile. The team kept the vendor's event-collection endpoint unchanged, so no data pipeline changes were needed.
Common Mistakes
- Freezing a script that must stay current. Consent tools and fraud-detection scripts change for legal and security reasons; mirror them only with an update job.
- Forwarding cookies through the proxy. First-party cookies sent to a vendor via your proxy can create privacy and security problems.
- Assuming self-hosting fixes execution cost. It speeds up loading, not the script's main-thread work.
- Breaking the script's relative URL assumptions. Some loaders fetch additional chunks relative to their own URL; a mirrored loader may then request files from your origin that do not exist.
Edge Cases
Scripts that check their own origin. Some vendors verify document.currentScript.src for licensing or configuration. Proxying may break them; test thoroughly.
Content Security Policy. Moving scripts to your origin simplifies script-src (fewer third-party hosts) but proxied scripts can still load further resources from the vendor, which must remain allowed.
Subresource Integrity. With a mirrored, versioned file you can add integrity attributes; with a proxy of a versionless URL, you cannot, because the content changes.
Regional vendor edges. Some vendors have better global edge coverage than your CDN in certain regions. Measure field timings by country before and after.
FAQ
Is preconnect a good-enough alternative?
It hides some of the setup cost by doing it early, which helps when the script is requested soon after. It does not remove the extra connection, cannot fix short cache lifetimes, and costs a socket for every hint. Self-hosting removes the problem; preconnect mitigates it.
Does self-hosting help returning visitors?
Yes, through caching you control. A vendor script with a one-hour TTL is revalidated on most return visits; a mirrored, fingerprinted copy is served from the browser cache with no request at all.
What about Google Tag Manager?
GTM can be served via first-party paths using server-side tagging or documented proxy setups. The container still loads its tags, which may be third-party themselves; self-hosting the container is useful, but auditing what it loads matters more — see auditing Google Tag Manager container weight.
Is it legal to mirror vendor scripts?
It depends on the vendor's licence and terms of service. Many permit it, some forbid it, and some provide an official first-party option. Check before mirroring and prefer documented proxy configurations where they exist.
Related
- Partytown vs async script loading for tag managers — moving third-party execution off the main thread.
- dns-prefetch vs preconnect — warming connections you cannot remove.
- Measuring third-party cost with the Coverage panel — sizing what each vendor costs.