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.

Vendor CDN vs self-hosted script Comparison of loading a third-party script from the vendor's origin versus serving it from the site's own origin. Vendor CDN vs self-hosted script From the vendor's CDN • New origin: DNS + TCP + TLS • Vendor controls caching (often 1h) • Always the latest version • Vendor outage can block your page From your own origin • Reuses the existing connection • You choose caching and compression • You must refresh it deliberately • Vendor outage affects only its API calls

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=3600 or 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.

Loading a 30KB analytics script on mobile Two timelines comparing a script fetched from a new vendor origin with the same script served from the site's existing connection. Loading a 30KB analytics script on mobile Vendor origin DNS TCP + TLS download Self-hosted download (reused conn) 0ms 100ms 200ms 300ms 400ms 500ms 600ms 700ms

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.

nginx
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.

bash
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.

Proxy, mirror or leave it? Decision sequence for how to host a third-party script based on its update cadence, terms and criticality. Proxy, mirror or leave it? Vendor terms forbid self-hosting? Keep vendor CDN; preconnect and defer instead yes no Script changes often or is versionless? CDN proxy with short TTL + stale-if-error yes no Versioned and stable? Mirror at build time, cache immutably yes no Measure first — it may not be on the critical path

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.