[{"data":1,"prerenderedAt":1106},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers":1097},{"id":4,"title":5,"body":6,"description":1077,"extension":1078,"meta":1079,"navigation":1091,"path":1092,"seo":1093,"stem":1095,"__hash__":1096},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002Findex.md","Partytown vs Async Script Loading for Tag Managers",{"type":7,"value":8,"toc":1068},"minimark",[9,14,39,47,52,85,89,189,345,349,363,373,382,388,392,406,412,462,660,663,673,841,848,854,953,956,960,963,966,969,973,985,1007,1020,1029,1033,1054,1058,1061,1064],[10,11,13],"h1",{"id":12},"partytown-vs-async-loading-which-should-run-your-tag-manager","Partytown vs Async Loading: Which Should Run Your Tag Manager?",[15,16,17,18,23,24,28,29,33,34,38],"p",{},"This comparison belongs to ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002F","Third-Party Script Performance"," within ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". A tag container costs ",[30,31,32],"code",{},"200–600ms"," of main-thread time on a mobile profile, and that time lands wherever the container happens to execute — frequently on top of a user interaction, where it becomes ",[19,35,37],{"href":36},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F","INP"," damage.",[15,40,41,42,46],{},"There are two credible answers. Move the work to another thread with a worker sandbox such as Partytown, or keep it on the main thread and control ",[43,44,45],"em",{},"when"," it runs. They are not equivalent, and the wrong choice is expensive in different ways.",[48,49,51],"h2",{"id":50},"rapid-diagnosis-what-does-your-container-actually-do","Rapid Diagnosis: What Does Your Container Actually Do?",[53,54,55,63,69,79],"ul",{},[56,57,58,62],"li",{},[59,60,61],"strong",{},"Does it write more than it reads?"," Analytics and conversion tags mostly send data out. Those are worker-friendly. Personalisation and A\u002FB tools that read and mutate the DOM are not.",[56,64,65,68],{},[59,66,67],{},"When does it run today?"," Record an interaction, not a load. If container work appears inside your input handler's frame, the timing is your problem, not the total cost.",[56,70,71,74,75,78],{},[59,72,73],{},"How much does it cost?"," Under ",[30,76,77],{},"100ms"," of main-thread time, deferral is enough and worker isolation is over-engineering.",[56,80,81,84],{},[59,82,83],{},"Can you verify events end to end?"," Worker isolation changes execution semantics. Without a way to compare event-for-event against a control, you cannot roll it out safely.",[48,86,88],{"id":87},"the-comparison","The Comparison",[90,91,92,108],"table",{},[93,94,95],"thead",{},[96,97,98,102,105],"tr",{},[99,100,101],"th",{},"Axis",[99,103,104],{},"Worker sandbox (Partytown)",[99,106,107],{},"Async \u002F deferred loading",[109,110,111,123,134,145,156,167,178],"tbody",{},[96,112,113,117,120],{},[114,115,116],"td",{},"Main-thread cost",[114,118,119],{},"Near zero — proxy overhead only",[114,121,122],{},"Full cost, moved later",[96,124,125,128,131],{},[114,126,127],{},"INP protection",[114,129,130],{},"Strong: tag work cannot block input",[114,132,133],{},"Partial: depends on when it runs",[96,135,136,139,142],{},[114,137,138],{},"Setup complexity",[114,140,141],{},"High — service worker or proxy, CSP changes",[114,143,144],{},"Low — an attribute or a small loader",[96,146,147,150,153],{},[114,148,149],{},"Vendor compatibility",[114,151,152],{},"Partial; some scripts detect and break",[114,154,155],{},"Complete",[96,157,158,161,164],{},[114,159,160],{},"Debuggability",[114,162,163],{},"Harder — second execution context",[114,165,166],{},"Unchanged",[96,168,169,172,175],{},[114,170,171],{},"Data fidelity risk",[114,173,174],{},"Real; needs event-for-event validation",[114,176,177],{},"Minimal",[96,179,180,183,186],{},[114,181,182],{},"Reversibility",[114,184,185],{},"Config change, but validation must be redone",[114,187,188],{},"Immediate",[190,191,198,199,206,207,206,211,206,215,206,224,206,231,206,239,206,246,206,253,206,258,206,264,206,270,206,275,206,279,206,282,206,286,206,289,206,292,206,295,206,298,206,301,206,303,206,306,206,310,206,313,206,315,206,318,206,322,206,325,206,330,206,333,206,337,206,340,206],"svg",{"xmlns":192,"viewBox":193,"width":194,"role":195,"ariaLabel":196,"style":197},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 252","100%","img","Decision tree asking whether the container costs more than 100 milliseconds, whether it manipulates the DOM, and whether event fidelity can be validated, routing to worker isolation, deferred loading or interaction gating","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)","\n  ",[200,201],"rect",{"className":202,"x":204,"y":204,"width":194,"height":194,"fill":205},[203],"svg-canvas","0","#ffffff"," ",[208,209,210],"title",{},"Choosing between worker isolation and controlled loading",[212,213,214],"desc",{},"Three questions in sequence: cost above one hundred milliseconds, DOM manipulation, and whether event fidelity can be validated — each routing to a different containment strategy.",[200,216],{"x":217,"y":217,"width":218,"height":219,"rx":220,"fill":221,"stroke":222,"style":223},"1","758","250","10","none","currentColor","stroke-opacity:0.18",[225,226,230],"text",{"x":227,"y":228,"fill":222,"style":229},"24","32","font-size:16px;font-weight:700","Which containment does this container need?",[200,232],{"x":233,"y":234,"width":235,"height":236,"rx":237,"fill":222,"stroke":222,"style":238},"30","54","230","40","6","fill-opacity:0.04;stroke-opacity:0.3",[225,240,245],{"x":241,"y":242,"fill":222,"style":243,"textAnchor":244},"145","79","font-size:11.5px;font-weight:600;","middle","Costs more than 100ms?",[247,248],"line",{"x1":249,"y1":250,"x2":251,"y2":250,"stroke":222,"style":252},"260","74","292","stroke-opacity:0.4",[254,255],"polygon",{"points":256,"fill":222,"style":257},"300,74 290,69 290,79","fill-opacity:0.55",[225,259,263],{"x":260,"y":261,"fill":222,"style":262,"textAnchor":244},"278","66","font-size:10px;fill-opacity:0.65","no",[200,265],{"x":266,"y":234,"width":267,"height":236,"rx":237,"fill":268,"stroke":268,"style":269},"300","240","#0466c8","fill-opacity:0.16;stroke-opacity:0.5",[225,271,274],{"x":272,"y":242,"fill":222,"style":273,"textAnchor":244},"420","font-size:11.5px;font-weight:700;","defer — you are done",[247,276],{"x1":241,"y1":277,"x2":241,"y2":278,"stroke":222,"style":252},"94","112",[254,280],{"points":281,"fill":222,"style":257},"145,120 140,110 150,110",[225,283,285],{"x":284,"y":278,"fill":222,"style":262},"164","yes",[200,287],{"x":233,"y":288,"width":235,"height":236,"rx":237,"fill":222,"stroke":222,"style":238},"120",[225,290,291],{"x":241,"y":241,"fill":222,"style":243,"textAnchor":244},"Reads or mutates the DOM?",[247,293],{"x1":249,"y1":294,"x2":251,"y2":294,"stroke":222,"style":252},"140",[254,296],{"points":297,"fill":222,"style":257},"300,140 290,135 290,145",[225,299,285],{"x":260,"y":300,"fill":222,"style":262,"textAnchor":244},"132",[200,302],{"x":266,"y":288,"width":267,"height":236,"rx":237,"fill":268,"stroke":268,"style":269},[225,304,305],{"x":272,"y":241,"fill":222,"style":273,"textAnchor":244},"gate on interaction instead",[247,307],{"x1":241,"y1":308,"x2":241,"y2":309,"stroke":222,"style":252},"160","178",[254,311],{"points":312,"fill":222,"style":257},"145,186 140,176 150,176",[225,314,263],{"x":284,"y":309,"fill":222,"style":262},[200,316],{"x":233,"y":317,"width":235,"height":236,"rx":237,"fill":222,"stroke":222,"style":238},"186",[225,319,321],{"x":241,"y":320,"fill":222,"style":243,"textAnchor":244},"211","Can you validate events?",[247,323],{"x1":249,"y1":324,"x2":251,"y2":324,"stroke":222,"style":252},"206",[254,326],{"points":327,"fill":328,"style":329},"300,206 290,201 290,211","#b8860b","fill-opacity:0.8",[225,331,285],{"x":260,"y":332,"fill":222,"style":262,"textAnchor":244},"198",[200,334],{"x":266,"y":317,"width":267,"height":236,"rx":237,"fill":335,"stroke":328,"style":336},"#ffc300","fill-opacity:0.28",[225,338,339],{"x":272,"y":320,"fill":222,"style":273,"textAnchor":244},"worker isolation pays off",[225,341,344],{"x":342,"y":324,"fill":222,"style":343},"560","font-size:11px;fill-opacity:0.75","otherwise: defer and revisit",[48,346,348],{"id":347},"root-cause-analysis-how-each-approach-fails","Root Cause Analysis: How Each Approach Fails",[15,350,351,354,355,358,359,362],{},[59,352,353],{},"1. Worker isolation breaks synchronous DOM reads."," Inside the sandbox, ",[30,356,357],{},"document.title"," or ",[30,360,361],{},"getBoundingClientRect()"," becomes an asynchronous proxy call. Scripts written assuming synchronous access either stall or silently take a fallback path — and the fallback is often \"report nothing\".",[15,364,365,368,369,372],{},[59,366,367],{},"2. Some vendors detect the sandbox."," A script that inspects ",[30,370,371],{},"window.top",", checks for a service worker, or fingerprints its environment can decide it is being tampered with and disable itself.",[15,374,375,206,378,381],{},[59,376,377],{},"3. Async loading leaves the work on the critical thread.",[30,379,380],{},"async"," executes the container the moment it arrives, which can be during hydration or in the middle of a click. It reduces nothing; it merely randomises when the cost lands.",[15,383,384,387],{},[59,385,386],{},"4. Interaction gating changes what you measure."," Loading tags on first interaction means bounce visits are never recorded. That is a data-model change, not a performance detail, and it will be noticed in the weekly report if it was not agreed first.",[48,389,391],{"id":390},"step-by-step-resolution","Step-by-Step Resolution",[15,393,394,397,398,401,402,405],{},[59,395,396],{},"1. Establish the baseline cost and the current timing."," Record an interaction with the container enabled and disabled, and compare the input handler's frame. This tells you whether you have a ",[43,399,400],{},"cost"," problem or a ",[43,403,404],{},"timing"," problem.",[15,407,408,411],{},[59,409,410],{},"2. If it is a timing problem, fix the timing."," Deferred loading with an interaction trigger and an idle fallback solves most cases in a few lines.",[413,414,419],"pre",{"className":415,"code":416,"language":417,"meta":418,"style":418},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003C!-- Predictable: executes after parsing, in order, never mid-interaction. -->\n\u003Cscript defer src=\"https:\u002F\u002Ftags.example.com\u002Fcontainer.js\">\u003C\u002Fscript>\n","html","",[30,420,421,429],{"__ignoreMap":418},[422,423,425],"span",{"class":247,"line":424},1,[422,426,428],{"class":427},"sjfSM","\u003C!-- Predictable: executes after parsing, in order, never mid-interaction. -->\n",[422,430,432,436,440,444,447,450,454,457,459],{"class":247,"line":431},2,[422,433,435],{"class":434},"saISM","\u003C",[422,437,439],{"class":438},"sZBmE","script",[422,441,443],{"class":442},"sPXB4"," defer",[422,445,446],{"class":442}," src",[422,448,449],{"class":434},"=",[422,451,453],{"class":452},"sZ8jY","\"https:\u002F\u002Ftags.example.com\u002Fcontainer.js\"",[422,455,456],{"class":434},">\u003C\u002F",[422,458,439],{"class":438},[422,460,461],{"class":434},">\n",[413,463,467],{"className":464,"code":465,"language":466,"meta":418,"style":418},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Or gate it: no cost at all until the visitor engages.\nconst load = () => {\n  const s = document.createElement('script');\n  s.src = 'https:\u002F\u002Ftags.example.com\u002Fcontainer.js'; s.defer = true;\n  document.head.append(s);\n};\n['pointerdown', 'keydown', 'scroll'].forEach((e) =>\n  addEventListener(e, load, { once: true, passive: true }));\nrequestIdleCallback?.(load, { timeout: 6000 });\n\u002F\u002F trade-off: the idle fallback guarantees the container eventually runs, so\n\u002F\u002F bounce visits are still measured — at the price of the cost returning on\n\u002F\u002F pages where the user does nothing. Drop the fallback only if the data owner\n\u002F\u002F accepts losing those sessions.\n","javascript",[30,468,469,474,496,522,544,556,562,601,621,636,642,648,654],{"__ignoreMap":418},[422,470,471],{"class":247,"line":424},[422,472,473],{"class":427},"\u002F\u002F Or gate it: no cost at all until the visitor engages.\n",[422,475,476,480,484,487,490,493],{"class":247,"line":431},[422,477,479],{"class":478},"sPARh","const",[422,481,483],{"class":482},"smZ65"," load",[422,485,486],{"class":478}," =",[422,488,489],{"class":434}," () ",[422,491,492],{"class":478},"=>",[422,494,495],{"class":434}," {\n",[422,497,499,502,505,507,510,513,516,519],{"class":247,"line":498},3,[422,500,501],{"class":478},"  const",[422,503,504],{"class":442}," s",[422,506,486],{"class":478},[422,508,509],{"class":434}," document.",[422,511,512],{"class":482},"createElement",[422,514,515],{"class":434},"(",[422,517,518],{"class":452},"'script'",[422,520,521],{"class":434},");\n",[422,523,525,528,530,533,536,538,541],{"class":247,"line":524},4,[422,526,527],{"class":434},"  s.src ",[422,529,449],{"class":478},[422,531,532],{"class":452}," 'https:\u002F\u002Ftags.example.com\u002Fcontainer.js'",[422,534,535],{"class":434},"; s.defer ",[422,537,449],{"class":478},[422,539,540],{"class":442}," true",[422,542,543],{"class":434},";\n",[422,545,547,550,553],{"class":247,"line":546},5,[422,548,549],{"class":434},"  document.head.",[422,551,552],{"class":482},"append",[422,554,555],{"class":434},"(s);\n",[422,557,559],{"class":247,"line":558},6,[422,560,561],{"class":434},"};\n",[422,563,565,568,571,574,577,579,582,585,588,591,595,598],{"class":247,"line":564},7,[422,566,567],{"class":434},"[",[422,569,570],{"class":452},"'pointerdown'",[422,572,573],{"class":434},", ",[422,575,576],{"class":452},"'keydown'",[422,578,573],{"class":434},[422,580,581],{"class":452},"'scroll'",[422,583,584],{"class":434},"].",[422,586,587],{"class":482},"forEach",[422,589,590],{"class":434},"((",[422,592,594],{"class":593},"sQw3B","e",[422,596,597],{"class":434},") ",[422,599,600],{"class":478},"=>\n",[422,602,604,607,610,613,616,618],{"class":247,"line":603},8,[422,605,606],{"class":482},"  addEventListener",[422,608,609],{"class":434},"(e, load, { once: ",[422,611,612],{"class":442},"true",[422,614,615],{"class":434},", passive: ",[422,617,612],{"class":442},[422,619,620],{"class":434}," }));\n",[422,622,624,627,630,633],{"class":247,"line":623},9,[422,625,626],{"class":482},"requestIdleCallback",[422,628,629],{"class":434},"?.(load, { timeout: ",[422,631,632],{"class":442},"6000",[422,634,635],{"class":434}," });\n",[422,637,639],{"class":247,"line":638},10,[422,640,641],{"class":427},"\u002F\u002F trade-off: the idle fallback guarantees the container eventually runs, so\n",[422,643,645],{"class":247,"line":644},11,[422,646,647],{"class":427},"\u002F\u002F bounce visits are still measured — at the price of the cost returning on\n",[422,649,651],{"class":247,"line":650},12,[422,652,653],{"class":427},"\u002F\u002F pages where the user does nothing. Drop the fallback only if the data owner\n",[422,655,657],{"class":247,"line":656},13,[422,658,659],{"class":427},"\u002F\u002F accepts losing those sessions.\n",[15,661,662],{},"Expected outcome: container work never overlaps an interaction; INP input delay attributable to the tag falls to near zero.",[15,664,665,668,669,672],{},[59,666,667],{},"3. If it is a cost problem, sandbox it — with a validation plan."," Worker isolation is worth the complexity when the container is genuinely expensive ",[43,670,671],{},"and"," mostly write-only.",[413,674,676],{"className":415,"code":675,"language":417,"meta":418,"style":418},"\u003Cscript type=\"text\u002Fpartytown\" src=\"https:\u002F\u002Ftags.example.com\u002Fcontainer.js\">\u003C\u002Fscript>\n\u003Cscript>\n  partytown = {\n    \u002F\u002F Requests the worker cannot make directly are proxied through your origin.\n    resolveUrl: (url) => url.hostname === 'tags.example.com'\n      ? new URL(`\u002Fproxy?u=${encodeURIComponent(url.href)}`, location.origin)\n      : url,\n    forward: ['dataLayer.push', 'gtag']\n  };\n\u003C\u002Fscript>\n\u003C!-- trade-off: `forward` is an allowlist. Any global the container expects to\n     call on the main thread and that is NOT listed silently does nothing —\n     which is exactly the failure mode that produces \"we lost 8% of events\". -->\n",[30,677,678,704,712,721,726,750,788,796,812,817,826,831,836],{"__ignoreMap":418},[422,679,680,682,684,687,689,692,694,696,698,700,702],{"class":247,"line":424},[422,681,435],{"class":434},[422,683,439],{"class":438},[422,685,686],{"class":442}," type",[422,688,449],{"class":434},[422,690,691],{"class":452},"\"text\u002Fpartytown\"",[422,693,446],{"class":442},[422,695,449],{"class":434},[422,697,453],{"class":452},[422,699,456],{"class":434},[422,701,439],{"class":438},[422,703,461],{"class":434},[422,705,706,708,710],{"class":247,"line":431},[422,707,435],{"class":434},[422,709,439],{"class":438},[422,711,461],{"class":434},[422,713,714,717,719],{"class":247,"line":498},[422,715,716],{"class":434},"  partytown ",[422,718,449],{"class":478},[422,720,495],{"class":434},[422,722,723],{"class":247,"line":524},[422,724,725],{"class":427},"    \u002F\u002F Requests the worker cannot make directly are proxied through your origin.\n",[422,727,728,731,734,737,739,741,744,747],{"class":247,"line":546},[422,729,730],{"class":482},"    resolveUrl",[422,732,733],{"class":434},": (",[422,735,736],{"class":593},"url",[422,738,597],{"class":434},[422,740,492],{"class":478},[422,742,743],{"class":434}," url.hostname ",[422,745,746],{"class":478},"===",[422,748,749],{"class":452}," 'tags.example.com'\n",[422,751,752,755,758,761,763,766,769,771,773,776,779,782,785],{"class":247,"line":558},[422,753,754],{"class":478},"      ?",[422,756,757],{"class":478}," new",[422,759,760],{"class":482}," URL",[422,762,515],{"class":434},[422,764,765],{"class":452},"`\u002Fproxy?u=${",[422,767,768],{"class":482},"encodeURIComponent",[422,770,515],{"class":452},[422,772,736],{"class":434},[422,774,775],{"class":452},".",[422,777,778],{"class":434},"href",[422,780,781],{"class":452},")",[422,783,784],{"class":452},"}`",[422,786,787],{"class":434},", location.origin)\n",[422,789,790,793],{"class":247,"line":564},[422,791,792],{"class":478},"      :",[422,794,795],{"class":434}," url,\n",[422,797,798,801,804,806,809],{"class":247,"line":603},[422,799,800],{"class":434},"    forward: [",[422,802,803],{"class":452},"'dataLayer.push'",[422,805,573],{"class":434},[422,807,808],{"class":452},"'gtag'",[422,810,811],{"class":434},"]\n",[422,813,814],{"class":247,"line":623},[422,815,816],{"class":434},"  };\n",[422,818,819,822,824],{"class":247,"line":638},[422,820,821],{"class":434},"\u003C\u002F",[422,823,439],{"class":438},[422,825,461],{"class":434},[422,827,828],{"class":247,"line":644},[422,829,830],{"class":427},"\u003C!-- trade-off: `forward` is an allowlist. Any global the container expects to\n",[422,832,833],{"class":247,"line":650},[422,834,835],{"class":427},"     call on the main thread and that is NOT listed silently does nothing —\n",[422,837,838],{"class":247,"line":656},[422,839,840],{"class":427},"     which is exactly the failure mode that produces \"we lost 8% of events\". -->\n",[15,842,843,844,847],{},"Expected outcome: main-thread cost drops to proxy overhead, typically under ",[30,845,846],{},"20ms",", with the container's own work running off-thread.",[15,849,850,853],{},[59,851,852],{},"4. Validate event fidelity before and after."," Run both configurations against the same synthetic journey and diff the outbound requests.",[413,855,859],{"className":856,"code":857,"language":858,"meta":418,"style":418},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Capture the events each configuration emits for an identical scripted journey.\nnode scripts\u002Fjourney.mjs --config=baseline > baseline.jsonl\nnode scripts\u002Fjourney.mjs --config=partytown > worker.jsonl\ndiff \u003C(jq -r '.event' baseline.jsonl | sort) \u003C(jq -r '.event' worker.jsonl | sort)\n# trade-off: a scripted journey covers the paths you thought of. Follow it with\n# a percentage rollout and compare real event volumes for a week before going\n# to 100% — silent loss is the failure mode that matters here.\n","bash",[30,860,861,866,883,897,938,943,948],{"__ignoreMap":418},[422,862,863],{"class":247,"line":424},[422,864,865],{"class":427},"# Capture the events each configuration emits for an identical scripted journey.\n",[422,867,868,871,874,877,880],{"class":247,"line":431},[422,869,870],{"class":593},"node",[422,872,873],{"class":452}," scripts\u002Fjourney.mjs",[422,875,876],{"class":442}," --config=baseline",[422,878,879],{"class":478}," >",[422,881,882],{"class":452}," baseline.jsonl\n",[422,884,885,887,889,892,894],{"class":247,"line":498},[422,886,870],{"class":593},[422,888,873],{"class":452},[422,890,891],{"class":442}," --config=partytown",[422,893,879],{"class":478},[422,895,896],{"class":452}," worker.jsonl\n",[422,898,899,902,905,908,911,914,917,920,922,924,926,928,931,933,935],{"class":247,"line":524},[422,900,901],{"class":593},"diff",[422,903,904],{"class":452}," \u003C(",[422,906,907],{"class":593},"jq",[422,909,910],{"class":442}," -r",[422,912,913],{"class":452}," '.event' baseline.jsonl ",[422,915,916],{"class":478},"|",[422,918,919],{"class":593}," sort",[422,921,781],{"class":452},[422,923,904],{"class":452},[422,925,907],{"class":593},[422,927,910],{"class":442},[422,929,930],{"class":452}," '.event' worker.jsonl ",[422,932,916],{"class":478},[422,934,919],{"class":593},[422,936,937],{"class":452},")\n",[422,939,940],{"class":247,"line":546},[422,941,942],{"class":427},"# trade-off: a scripted journey covers the paths you thought of. Follow it with\n",[422,944,945],{"class":247,"line":558},[422,946,947],{"class":427},"# a percentage rollout and compare real event volumes for a week before going\n",[422,949,950],{"class":247,"line":564},[422,951,952],{"class":427},"# to 100% — silent loss is the failure mode that matters here.\n",[15,954,955],{},"Expected outcome: an explicit, evidenced answer to \"did we lose events?\", rather than a hope.",[48,957,959],{"id":958},"verification","Verification",[15,961,962],{},"Confirm the main-thread effect first: record the same interaction after the change and check the bottom-up view for vendor script frames inside your handler. They should be absent (gated or deferred) or attributed to the worker (sandboxed).",[15,964,965],{},"Then confirm the metric moved in the field, segmenting INP by whether the session received the tag. A successful containment shows input delay dropping while processing duration is unchanged — because you removed contention, not your own handler's work.",[15,967,968],{},"Finally, keep watching the data side for a full reporting cycle. Performance work on tags is only successful if the measurement it exists to serve survives; a 300ms saving that costs 8% of conversion events will be reverted, correctly.",[48,970,972],{"id":971},"faq","FAQ",[974,975,978,982],"details",{"className":976},[977],"faq-item",[979,980,981],"summary",{},"Does Partytown work with every tag manager?",[15,983,984],{},"No. It works well with containers that mostly emit events and rarely read the DOM synchronously — most analytics and conversion tags fall in that group. Personalisation, A\u002FB testing and session-replay tools that need synchronous layout access frequently misbehave or disable themselves. Test each tag individually rather than the container as a whole, because one incompatible tag inside a compatible container is enough to break the rollout.",[974,986,988,991],{"className":987},[977],[979,989,990],{},"Is async loading ever the right answer for a container?",[15,992,993,994,996,997,1000,1001,1003,1004,1006],{},"Rarely. ",[30,995,380],{}," gives you the download parallelism of ",[30,998,999],{},"defer"," without the execution predictability, so the container can run during hydration or in the middle of an interaction. ",[30,1002,999],{}," is the better default in almost every case, and interaction gating is better still when the data owner accepts the trade. Reserve ",[30,1005,380],{}," for a tag that must fire as early as possible and genuinely has no ordering requirements.",[974,1008,1010,1013],{"className":1009},[977],[979,1011,1012],{},"How much main-thread time does the worker proxy itself cost?",[15,1014,1015,1016,1019],{},"Small but not zero — typically a few milliseconds of setup plus a fraction of a millisecond per proxied operation, since each one crosses a thread boundary. A container that performs thousands of DOM operations will therefore be ",[43,1017,1018],{},"slower"," overall in a sandbox, even though the main thread is less blocked. Profile the specific container; the technique suits chatty-but-shallow scripts, not DOM-heavy ones.",[974,1021,1023,1026],{"className":1022},[977],[979,1024,1025],{},"Can I combine the two approaches?",[15,1027,1028],{},"Yes, and it is often the best configuration: sandbox the measurement container, and interaction-gate the heavier optional tags that would not survive the worker. The two techniques address different tags rather than competing for the same one, and separating them also makes each rollout independently reversible.",[48,1030,1032],{"id":1031},"related","Related",[53,1034,1035,1040,1047],{},[56,1036,1037,1039],{},[19,1038,22],{"href":21}," — the wider containment strategy and per-origin cost accounting.",[56,1041,1042,1046],{},[19,1043,1045],{"href":1044},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fdeferring-non-critical-analytics-scripts-safely\u002F","Deferring non-critical analytics scripts safely"," — the mechanics of the deferred-loading option.",[56,1048,1049,1053],{},[19,1050,1052],{"href":1051},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-input-delay-from-third-party-tags\u002F","Reducing input delay from third-party tags"," — measuring the INP damage this decision is meant to remove.",[439,1055,1057],{"type":1056},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Choose between worker isolation and deferred loading for a tag manager\",\n  \"description\": \"Establish whether a tag container has a cost problem or a timing problem, apply deferred or interaction-gated loading or a worker sandbox accordingly, and validate event fidelity before rollout.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Baseline cost and timing\", \"text\": \"Record an interaction with the container enabled and disabled to separate total cost from execution timing.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Fix timing with defer or gating\", \"text\": \"Use defer or interaction-gated injection with an idle fallback so container work never overlaps an interaction.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Sandbox expensive write-only containers\", \"text\": \"Move the container into a worker sandbox with an explicit forward allowlist and a proxy for its requests.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Validate event fidelity\", \"text\": \"Diff the emitted events between configurations on an identical journey and confirm volumes in a percentage rollout.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002F#step-by-step-resolution\" }\n  ]\n}\n",[439,1059,1060],{"type":1056},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Partytown vs Async Loading: Which Should Run Your Tag Manager?\",\n  \"description\": \"A decision matrix comparing worker isolation with deferred and interaction-gated loading for tag containers, including the failure modes each introduces.\",\n  \"datePublished\": \"2026-08-01\",\n  \"dateModified\": \"2026-08-01\",\n  \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"publisher\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"mainEntityOfPage\": { \"@type\": \"WebPage\", \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002F\" }\n}\n",[439,1062,1063],{"type":1056},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"JavaScript Bundle Optimization & Code Splitting\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Third-Party Script Performance\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Partytown vs Async Script Loading for Tag Managers\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002F\" }\n  ]\n}\n",[1065,1066,1067],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":418,"searchDepth":431,"depth":431,"links":1069},[1070,1071,1072,1073,1074,1075,1076],{"id":50,"depth":431,"text":51},{"id":87,"depth":431,"text":88},{"id":347,"depth":431,"text":348},{"id":390,"depth":431,"text":391},{"id":958,"depth":431,"text":959},{"id":971,"depth":431,"text":972},{"id":1031,"depth":431,"text":1032},"Worker sandboxing and deferred loading solve different halves of the tag-manager problem. A decision matrix, plus the failure modes each one introduces.","md",{"slug":1080,"type":1081,"breadcrumb":1082,"datePublished":1090,"dateModified":1090},"partytown-vs-async-script-loading-for-tag-managers","article",[1083,1086,1087,1088],{"name":1084,"url":1085},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1089},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002F","2026-08-01",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers",{"title":5,"description":1094},"Worker isolation removes tag work from the main thread but breaks synchronous DOM access. Compare it against deferred and interaction-gated loading before you migrate.","javascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002Findex","NTTwjuKqCeS1e5iv3fQ18E25T3eYPgJ_5xiYAa57i0M",[1098,1102],{"title":1099,"path":1100,"stem":1101,"children":-1},"Measuring Third-Party Cost with the Coverage Panel","\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fmeasuring-third-party-cost-with-the-coverage-panel","javascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fmeasuring-third-party-cost-with-the-coverage-panel\u002Findex",{"title":1103,"path":1104,"stem":1105,"children":-1},"Replacing YouTube Embeds with a Facade","\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade","javascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002Findex",1785622406806]