[{"data":1,"prerenderedAt":1315},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction":1306},{"id":4,"title":5,"body":6,"description":1285,"extension":1286,"meta":1287,"navigation":1299,"path":1300,"seo":1301,"stem":1304,"__hash__":1305},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction\u002Findex.md","Loading Chat Widgets on Interaction",{"type":7,"value":8,"toc":1269},"minimark",[9,14,29,32,131,136,165,169,175,181,187,193,318,322,327,580,583,587,913,916,920,923,976,980,983,987,990,1107,1111,1114,1118,1144,1148,1158,1164,1170,1176,1180,1192,1201,1210,1219,1228,1232,1255,1260,1263,1266],[10,11,13],"h1",{"id":12},"how-to-load-chat-widgets-only-when-users-need-them","How to Load Chat Widgets Only When Users Need Them",[15,16,17,18,23,24,28],"p",{},"This guide applies the facade pattern from ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002F","Third-Party Script Performance"," to live-chat widgets, within ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". Customer-support chat tools — Intercom, Zendesk, Drift, HubSpot, LiveChat and others — typically load 300–700KB of JavaScript, open WebSocket connections, inject iframes and run long tasks during page load, on every page, for every visitor. A small fraction of visitors ever open the chat.",[15,30,31],{},"The cost lands on Core Web Vitals in several ways: download bandwidth competes with LCP resources, startup evaluation adds long tasks that delay early interactions (INP), and late-injected launcher buttons can shift layout (CLS). The facade pattern fixes all three: render a lightweight look-alike button immediately, and load the real widget only when the user hovers or clicks it — or, as a compromise, after the page has been idle for a while.",[15,33,34],{},[35,36,42,43,42,50,42,54,42,57,42,66,42,72,42,79,42,89,42,94,42,98,42,102,42,106,42,110,42,116,42,119,42,123,42,127,42],"svg",{"viewBox":37,"width":38,"role":39,"ariaLabel":40,"style":41},"0 0 760 194","100%","img","Bar chart of bytes and main-thread time added at page load by a chat widget, compared with a facade button.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[44,45],"rect",{"className":46,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],"svg-canvas","0","#ffffff",[51,52,53],"title",{},"Startup cost of a typical chat widget (mid-tier phone)",[55,56,40],"desc",{},[44,58],{"x":59,"y":59,"width":60,"height":61,"rx":62,"fill":63,"stroke":64,"style":65},"1","758","192","10","none","currentColor","stroke-opacity:0.18",[67,68,53],"text",{"x":69,"y":70,"fill":64,"style":71},"28.0","34.0","font-size:16px;font-weight:700",[67,73,78],{"x":74,"y":75,"fill":64,"style":76,"textAnchor":77},"202.3","70.0","font-size:13px","end","Widget JS transferred (KB)",[44,80],{"x":81,"y":82,"width":83,"height":84,"rx":85,"fill":86,"stroke":87,"style":88},"214.3","56.0","312.4","19","3","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[67,90,93],{"x":91,"y":75,"fill":64,"style":92},"532.7","font-size:12px;font-weight:600","420",[67,95,97],{"x":74,"y":96,"fill":64,"style":76,"textAnchor":77},"101.0","Widget main-thread time (ms)",[44,99],{"x":81,"y":100,"width":101,"height":84,"rx":85,"fill":86,"stroke":87,"style":88},"87.0","453.7",[67,103,105],{"x":104,"y":96,"fill":64,"style":92},"674.0","610",[67,107,109],{"x":74,"y":108,"fill":64,"style":76,"textAnchor":77},"132.0","Facade JS + CSS (KB)",[44,111],{"x":81,"y":112,"width":113,"height":84,"rx":85,"fill":114,"stroke":114,"style":115},"118.0","2.2","#0466c8","fill-opacity:0.55;stroke-opacity:0.9",[67,117,85],{"x":118,"y":108,"fill":64,"style":92},"222.5",[67,120,122],{"x":74,"y":121,"fill":64,"style":76,"textAnchor":77},"163.0","Facade main-thread time (ms)",[44,124],{"x":81,"y":125,"width":126,"height":84,"rx":85,"fill":114,"stroke":114,"style":115},"149.0","2.0",[67,128,130],{"x":129,"y":121,"fill":64,"style":92},"222.3","2",[132,133,135],"h2",{"id":134},"rapid-diagnosis","Rapid Diagnosis",[137,138,139,147,153,159],"ul",{},[140,141,142,146],"li",{},[143,144,145],"strong",{},"Measure the widget's footprint."," Network panel filtered by the vendor's domains; total transferred bytes and request count.",[140,148,149,152],{},[143,150,151],{},"Find its long tasks."," In a throttled trace, group main-thread work by third-party; chat widgets often appear near the top.",[140,154,155,158],{},[143,156,157],{},"Check open rates."," Analytics on chat opens per page view — usually a few percent or less.",[140,160,161,164],{},[143,162,163],{},"Check CLS from the launcher."," A launcher button injected late into the corner can shift sticky footers or cookie banners.",[132,166,168],{"id":167},"root-cause-analysis","Root Cause Analysis",[15,170,171,174],{},[143,172,173],{},"1. Eager loading by default."," Vendor install snippets load everything on page load, optimising for their feature readiness, not your metrics.",[15,176,177,180],{},[143,178,179],{},"2. Heavy runtime."," Widgets bundle UI frameworks, real-time messaging, emoji pickers and file uploaders — all loaded before anyone opens chat.",[15,182,183,186],{},[143,184,185],{},"3. Persistent connections."," WebSocket connections and polling keep the network and main thread busy throughout the visit.",[15,188,189,192],{},[143,190,191],{},"4. Late layout insertion."," The launcher appears when the script finishes, potentially moving other fixed elements.",[15,194,195],{},[35,196,42,199,42,202,42,205,42,207,42,224,42,227,42,229,42,235,42,242,42,245,42,249,42,252,42,256,42,259,42,263,42,272,42,275,42,278,42,281,42,287,42,293,42,297,42,302,42,305,42,310,42,314,42],{"viewBox":197,"width":38,"role":39,"ariaLabel":198,"style":41},"0 0 760 290","Sequence showing a lightweight facade button rendered at load, the user's intent triggering the vendor script, and the real widget opening.",[44,200],{"className":201,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,203,204],{},"Facade-to-widget handoff",[55,206,198],{},[208,209,210],"defs",{},[211,212,219],"marker",{"id":213,"viewBox":214,"refX":215,"refY":216,"markerWidth":217,"markerHeight":217,"orient":218},"fa63ef99f0","0 0 10 10","9","5","7","auto-start-reverse",[220,221],"path",{"d":222,"fill":64,"style":223},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[44,225],{"x":59,"y":59,"width":60,"height":226,"rx":62,"fill":63,"stroke":64,"style":65},"288",[67,228,204],{"x":69,"y":70,"fill":64,"style":71},[44,230],{"x":231,"y":82,"width":232,"height":70,"rx":233,"fill":64,"stroke":64,"style":234},"41.0","150.0","6","fill-opacity:0.06;stroke-opacity:0.4",[67,236,241],{"x":237,"y":238,"fill":64,"style":239,"textAnchor":240},"116.0","78.0","font-size:13px;font-weight:700","middle","User",[44,243],{"x":244,"y":82,"width":232,"height":70,"rx":233,"fill":64,"stroke":64,"style":234},"217.0",[67,246,248],{"x":247,"y":238,"fill":64,"style":239,"textAnchor":240},"292.0","Facade",[44,250],{"x":251,"y":82,"width":232,"height":70,"rx":233,"fill":64,"stroke":64,"style":234},"393.0",[67,253,255],{"x":254,"y":238,"fill":64,"style":239,"textAnchor":240},"468.0","Vendor script",[44,257],{"x":258,"y":82,"width":232,"height":70,"rx":233,"fill":64,"stroke":64,"style":234},"569.0",[67,260,262],{"x":261,"y":238,"fill":64,"style":239,"textAnchor":240},"644.0","Chat UI",[264,265],"line",{"x1":237,"y1":266,"x2":237,"y2":267,"stroke":64,"strokeWidth":268,"strokeDashArray":269,"style":271},"90.0","268.0","1.5",[270,270],"4","stroke-opacity:0.35",[264,273],{"x1":247,"y1":266,"x2":247,"y2":267,"stroke":64,"strokeWidth":268,"strokeDashArray":274,"style":271},[270,270],[264,276],{"x1":254,"y1":266,"x2":254,"y2":267,"stroke":64,"strokeWidth":268,"strokeDashArray":277,"style":271},[270,270],[264,279],{"x1":261,"y1":266,"x2":261,"y2":267,"stroke":64,"strokeWidth":268,"strokeDashArray":280,"style":271},[270,270],[264,282],{"x1":237,"y1":283,"x2":284,"y2":283,"stroke":64,"strokeWidth":268,"style":285,"markerEnd":286},"124.0","290.0","stroke-opacity:0.6","url(#fa63ef99f0)",[67,288,292],{"x":289,"y":290,"fill":64,"style":291,"textAnchor":240},"204.0","117.0","font-size:12px","hover \u002F click",[264,294],{"x1":247,"y1":295,"x2":296,"y2":295,"stroke":64,"strokeWidth":268,"style":285,"markerEnd":286},"164.0","466.0",[67,298,301],{"x":299,"y":300,"fill":64,"style":291,"textAnchor":240},"380.0","157.0","inject script",[264,303],{"x1":254,"y1":289,"x2":304,"y2":289,"stroke":64,"strokeWidth":268,"style":285,"markerEnd":286},"642.0",[67,306,309],{"x":307,"y":308,"fill":64,"style":291,"textAnchor":240},"556.0","197.0","boot widget",[264,311],{"x1":261,"y1":312,"x2":112,"y2":312,"stroke":64,"strokeWidth":268,"strokeDashArray":313,"style":285,"markerEnd":286},"244.0",[216,270],[67,315,317],{"x":299,"y":316,"fill":64,"style":291,"textAnchor":240},"237.0","chat opens",[132,319,321],{"id":320},"step-by-step-resolution","Step-by-Step Resolution",[323,324,326],"h3",{"id":325},"_1-render-a-facade-that-looks-like-the-launcher","1. Render a facade that looks like the launcher",[328,329,334],"pre",{"className":330,"code":331,"language":332,"meta":333,"style":333},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Cbutton class=\"chat-facade\" type=\"button\" aria-label=\"Open chat\" hidden>\n  \u003Cimg src=\"\u002Ficons\u002Fchat-bubble.svg\" width=\"28\" height=\"28\" alt=\"\">\n\u003C\u002Fbutton>\n\u003Cstyle>\n  .chat-facade { position: fixed; right: 20px; bottom: 20px; width: 56px; height: 56px;\n                 border-radius: 50%; border: 0; background: #0466c8; color: #fff; }\n\u003C\u002Fstyle>\n\u003C!-- trade-off: the facade must match the vendor's launcher size and position\n     exactly, or users see the button jump when the real widget replaces it. -->\n","html","",[335,336,337,382,423,433,443,513,558,567,574],"code",{"__ignoreMap":333},[338,339,341,345,349,353,356,360,363,365,368,371,373,376,379],"span",{"class":264,"line":340},1,[338,342,344],{"class":343},"saISM","\u003C",[338,346,348],{"class":347},"sZBmE","button",[338,350,352],{"class":351},"sPXB4"," class",[338,354,355],{"class":343},"=",[338,357,359],{"class":358},"sZ8jY","\"chat-facade\"",[338,361,362],{"class":351}," type",[338,364,355],{"class":343},[338,366,367],{"class":358},"\"button\"",[338,369,370],{"class":351}," aria-label",[338,372,355],{"class":343},[338,374,375],{"class":358},"\"Open chat\"",[338,377,378],{"class":351}," hidden",[338,380,381],{"class":343},">\n",[338,383,385,388,390,393,395,398,401,403,406,409,411,413,416,418,421],{"class":264,"line":384},2,[338,386,387],{"class":343},"  \u003C",[338,389,39],{"class":347},[338,391,392],{"class":351}," src",[338,394,355],{"class":343},[338,396,397],{"class":358},"\"\u002Ficons\u002Fchat-bubble.svg\"",[338,399,400],{"class":351}," width",[338,402,355],{"class":343},[338,404,405],{"class":358},"\"28\"",[338,407,408],{"class":351}," height",[338,410,355],{"class":343},[338,412,405],{"class":358},[338,414,415],{"class":351}," alt",[338,417,355],{"class":343},[338,419,420],{"class":358},"\"\"",[338,422,381],{"class":343},[338,424,426,429,431],{"class":264,"line":425},3,[338,427,428],{"class":343},"\u003C\u002F",[338,430,348],{"class":347},[338,432,381],{"class":343},[338,434,436,438,441],{"class":264,"line":435},4,[338,437,344],{"class":343},[338,439,440],{"class":347},"style",[338,442,381],{"class":343},[338,444,446,449,452,455,458,461,464,467,469,472,476,478,481,483,485,487,489,492,494,497,499,501,504,506,508,510],{"class":264,"line":445},5,[338,447,448],{"class":351},"  .chat-facade",[338,450,451],{"class":343}," { ",[338,453,454],{"class":351},"position",[338,456,457],{"class":343},": ",[338,459,460],{"class":351},"fixed",[338,462,463],{"class":343},"; ",[338,465,466],{"class":351},"right",[338,468,457],{"class":343},[338,470,471],{"class":351},"20",[338,473,475],{"class":474},"sPARh","px",[338,477,463],{"class":343},[338,479,480],{"class":351},"bottom",[338,482,457],{"class":343},[338,484,471],{"class":351},[338,486,475],{"class":474},[338,488,463],{"class":343},[338,490,491],{"class":351},"width",[338,493,457],{"class":343},[338,495,496],{"class":351},"56",[338,498,475],{"class":474},[338,500,463],{"class":343},[338,502,503],{"class":351},"height",[338,505,457],{"class":343},[338,507,496],{"class":351},[338,509,475],{"class":474},[338,511,512],{"class":343},";\n",[338,514,516,519,521,524,527,529,532,534,536,538,541,543,545,547,550,552,555],{"class":264,"line":515},6,[338,517,518],{"class":351},"                 border-radius",[338,520,457],{"class":343},[338,522,523],{"class":351},"50",[338,525,526],{"class":474},"%",[338,528,463],{"class":343},[338,530,531],{"class":351},"border",[338,533,457],{"class":343},[338,535,48],{"class":351},[338,537,463],{"class":343},[338,539,540],{"class":351},"background",[338,542,457],{"class":343},[338,544,114],{"class":351},[338,546,463],{"class":343},[338,548,549],{"class":351},"color",[338,551,457],{"class":343},[338,553,554],{"class":351},"#fff",[338,556,557],{"class":343},"; }\n",[338,559,561,563,565],{"class":264,"line":560},7,[338,562,428],{"class":343},[338,564,440],{"class":347},[338,566,381],{"class":343},[338,568,570],{"class":264,"line":569},8,[338,571,573],{"class":572},"sjfSM","\u003C!-- trade-off: the facade must match the vendor's launcher size and position\n",[338,575,577],{"class":264,"line":576},9,[338,578,579],{"class":572},"     exactly, or users see the button jump when the real widget replaces it. -->\n",[15,581,582],{},"Expected outcome: users see a chat button immediately with no third-party cost.",[323,584,586],{"id":585},"_2-load-the-real-widget-on-intent","2. Load the real widget on intent",[328,588,592],{"className":589,"code":590,"language":591,"meta":333,"style":333},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","const facade = document.querySelector('.chat-facade');\nfacade.hidden = false;\nlet loading;\nfunction loadChat({ open = false } = {}) {\n  loading ??= new Promise((resolve) => {\n    window.chatSettings = { app_id: 'abc123', hide_default_launcher: false };\n    const s = document.createElement('script');\n    s.src = 'https:\u002F\u002Fwidget.vendor.example\u002Floader.js'; s.async = true;\n    s.onload = resolve;\n    document.head.append(s);\n  });\n  return loading.then(() => { facade.remove(); if (open) window.ChatVendor?.('show'); });\n}\nfacade.addEventListener('pointerenter', () => loadChat(), { once: true });\nfacade.addEventListener('click', () => loadChat({ open: true }));\n\u002F\u002F trade-off: the first click waits for the widget to load (often 1-2s on\n\u002F\u002F mobile). Show a spinner in the facade so the user knows it is working; the\n\u002F\u002F hover preload hides most of the wait for desktop users.\n","javascript",[335,593,594,621,633,641,668,697,719,741,761,771,783,789,833,839,869,895,901,907],{"__ignoreMap":333},[338,595,596,599,602,605,608,612,615,618],{"class":264,"line":340},[338,597,598],{"class":474},"const",[338,600,601],{"class":351}," facade",[338,603,604],{"class":474}," =",[338,606,607],{"class":343}," document.",[338,609,611],{"class":610},"smZ65","querySelector",[338,613,614],{"class":343},"(",[338,616,617],{"class":358},"'.chat-facade'",[338,619,620],{"class":343},");\n",[338,622,623,626,628,631],{"class":264,"line":384},[338,624,625],{"class":343},"facade.hidden ",[338,627,355],{"class":474},[338,629,630],{"class":351}," false",[338,632,512],{"class":343},[338,634,635,638],{"class":264,"line":425},[338,636,637],{"class":474},"let",[338,639,640],{"class":343}," loading;\n",[338,642,643,646,649,652,656,658,660,663,665],{"class":264,"line":435},[338,644,645],{"class":474},"function",[338,647,648],{"class":610}," loadChat",[338,650,651],{"class":343},"({ ",[338,653,655],{"class":654},"sQw3B","open",[338,657,604],{"class":474},[338,659,630],{"class":351},[338,661,662],{"class":343}," } ",[338,664,355],{"class":474},[338,666,667],{"class":343}," {}) {\n",[338,669,670,673,676,679,682,685,688,691,694],{"class":264,"line":445},[338,671,672],{"class":343},"  loading ",[338,674,675],{"class":474},"??=",[338,677,678],{"class":474}," new",[338,680,681],{"class":351}," Promise",[338,683,684],{"class":343},"((",[338,686,687],{"class":654},"resolve",[338,689,690],{"class":343},") ",[338,692,693],{"class":474},"=>",[338,695,696],{"class":343}," {\n",[338,698,699,702,704,707,710,713,716],{"class":264,"line":515},[338,700,701],{"class":343},"    window.chatSettings ",[338,703,355],{"class":474},[338,705,706],{"class":343}," { app_id: ",[338,708,709],{"class":358},"'abc123'",[338,711,712],{"class":343},", hide_default_launcher: ",[338,714,715],{"class":351},"false",[338,717,718],{"class":343}," };\n",[338,720,721,724,727,729,731,734,736,739],{"class":264,"line":560},[338,722,723],{"class":474},"    const",[338,725,726],{"class":351}," s",[338,728,604],{"class":474},[338,730,607],{"class":343},[338,732,733],{"class":610},"createElement",[338,735,614],{"class":343},[338,737,738],{"class":358},"'script'",[338,740,620],{"class":343},[338,742,743,746,748,751,754,756,759],{"class":264,"line":569},[338,744,745],{"class":343},"    s.src ",[338,747,355],{"class":474},[338,749,750],{"class":358}," 'https:\u002F\u002Fwidget.vendor.example\u002Floader.js'",[338,752,753],{"class":343},"; s.async ",[338,755,355],{"class":474},[338,757,758],{"class":351}," true",[338,760,512],{"class":343},[338,762,763,766,768],{"class":264,"line":576},[338,764,765],{"class":343},"    s.onload ",[338,767,355],{"class":474},[338,769,770],{"class":343}," resolve;\n",[338,772,774,777,780],{"class":264,"line":773},10,[338,775,776],{"class":343},"    document.head.",[338,778,779],{"class":610},"append",[338,781,782],{"class":343},"(s);\n",[338,784,786],{"class":264,"line":785},11,[338,787,788],{"class":343},"  });\n",[338,790,792,795,798,801,804,806,809,812,815,818,821,824,827,830],{"class":264,"line":791},12,[338,793,794],{"class":474},"  return",[338,796,797],{"class":343}," loading.",[338,799,800],{"class":610},"then",[338,802,803],{"class":343},"(() ",[338,805,693],{"class":474},[338,807,808],{"class":343}," { facade.",[338,810,811],{"class":610},"remove",[338,813,814],{"class":343},"(); ",[338,816,817],{"class":474},"if",[338,819,820],{"class":343}," (open) window.",[338,822,823],{"class":610},"ChatVendor",[338,825,826],{"class":343},"?.(",[338,828,829],{"class":358},"'show'",[338,831,832],{"class":343},"); });\n",[338,834,836],{"class":264,"line":835},13,[338,837,838],{"class":343},"}\n",[338,840,842,845,848,850,853,856,858,860,863,866],{"class":264,"line":841},14,[338,843,844],{"class":343},"facade.",[338,846,847],{"class":610},"addEventListener",[338,849,614],{"class":343},[338,851,852],{"class":358},"'pointerenter'",[338,854,855],{"class":343},", () ",[338,857,693],{"class":474},[338,859,648],{"class":610},[338,861,862],{"class":343},"(), { once: ",[338,864,865],{"class":351},"true",[338,867,868],{"class":343}," });\n",[338,870,872,874,876,878,881,883,885,887,890,892],{"class":264,"line":871},15,[338,873,844],{"class":343},[338,875,847],{"class":610},[338,877,614],{"class":343},[338,879,880],{"class":358},"'click'",[338,882,855],{"class":343},[338,884,693],{"class":474},[338,886,648],{"class":610},[338,888,889],{"class":343},"({ open: ",[338,891,865],{"class":351},[338,893,894],{"class":343}," }));\n",[338,896,898],{"class":264,"line":897},16,[338,899,900],{"class":572},"\u002F\u002F trade-off: the first click waits for the widget to load (often 1-2s on\n",[338,902,904],{"class":264,"line":903},17,[338,905,906],{"class":572},"\u002F\u002F mobile). Show a spinner in the facade so the user knows it is working; the\n",[338,908,910],{"class":264,"line":909},18,[338,911,912],{"class":572},"\u002F\u002F hover preload hides most of the wait for desktop users.\n",[15,914,915],{},"Expected outcome: zero chat JavaScript at load; the widget appears within a second or two of the user asking for it.",[323,917,919],{"id":918},"_3-optionally-load-after-sustained-idle-for-proactive-messaging","3. Optionally load after sustained idle for proactive messaging",[15,921,922],{},"If the business relies on proactive chat prompts, load the widget after the page has been idle for a while (for example 10–15 seconds after load), not at load.",[328,924,926],{"className":589,"code":925,"language":591,"meta":333,"style":333},"addEventListener('load', () => setTimeout(() => requestIdleCallback(() => loadChat()), 12000));\n\u002F\u002F trade-off: proactive prompts appear later than before. Agree the delay with\n\u002F\u002F the support team; most find that prompts after 15s of engagement convert better anyway.\n",[335,927,928,966,971],{"__ignoreMap":333},[338,929,930,932,934,937,939,941,944,946,948,951,953,955,957,960,963],{"class":264,"line":340},[338,931,847],{"class":610},[338,933,614],{"class":343},[338,935,936],{"class":358},"'load'",[338,938,855],{"class":343},[338,940,693],{"class":474},[338,942,943],{"class":610}," setTimeout",[338,945,803],{"class":343},[338,947,693],{"class":474},[338,949,950],{"class":610}," requestIdleCallback",[338,952,803],{"class":343},[338,954,693],{"class":474},[338,956,648],{"class":610},[338,958,959],{"class":343},"()), ",[338,961,962],{"class":351},"12000",[338,964,965],{"class":343},"));\n",[338,967,968],{"class":264,"line":384},[338,969,970],{"class":572},"\u002F\u002F trade-off: proactive prompts appear later than before. Agree the delay with\n",[338,972,973],{"class":264,"line":425},[338,974,975],{"class":572},"\u002F\u002F the support team; most find that prompts after 15s of engagement convert better anyway.\n",[323,977,979],{"id":978},"_4-preserve-unread-message-state","4. Preserve unread-message state",[15,981,982],{},"Some users have ongoing conversations. If the vendor sets a cookie or local storage key indicating unread messages, check it and load the widget immediately for those users only.",[132,984,986],{"id":985},"verification","Verification",[15,988,989],{},"In a throttled trace of a page view without interaction, no requests to the vendor should appear and the long tasks attributed to the widget should be gone. Lab TBT and the INP of early interactions should improve by roughly the widget's startup work. Click the facade and confirm the widget opens with conversation history intact. Track chat open rates before and after to confirm the support experience is unaffected.",[15,991,992],{},[35,993,42,996,42,999,42,1002,42,1004,42,1007,42,1009,42,1013,42,1020,42,1024,42,1028,42,1031,42,1035,42,1038,42,1042,42,1045,42,1049,42,1052,42,1055,42,1058,42,1061,42,1063,42,1065,42,1067,42,1071,42,1073,42,1076,42,1078,42,1081,42,1083,42,1086,42,1089,42,1093,42,1095,42,1097,42,1099,42,1102,42,1104,42],{"viewBox":994,"width":38,"role":39,"ariaLabel":995,"style":41},"0 0 760 198","Comparison of load-on-load, load-on-idle and load-on-interaction strategies for chat widgets.",[44,997],{"className":998,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,1000,1001],{},"Loading strategies for chat widgets",[55,1003,995],{},[44,1005],{"x":59,"y":59,"width":60,"height":1006,"rx":62,"fill":63,"stroke":64,"style":65},"196",[67,1008,1001],{"x":69,"y":70,"fill":64,"style":71},[44,1010],{"x":69,"y":82,"width":1011,"height":1012,"rx":48,"fill":64,"stroke":64,"style":234},"136.6","30.0",[67,1014,1019],{"x":1015,"y":1016,"fill":64,"style":1017,"textAnchor":1018},"38.0","75.5","font-size:12.5px;font-weight:700","start","Strategy",[44,1021],{"x":1022,"y":82,"width":1023,"height":1012,"rx":48,"fill":64,"stroke":64,"style":234},"164.6","189.1",[67,1025,1027],{"x":1026,"y":1016,"fill":64,"style":1017,"textAnchor":240},"259.2","Startup cost",[44,1029],{"x":1030,"y":82,"width":1023,"height":1012,"rx":48,"fill":64,"stroke":64,"style":234},"353.7",[67,1032,1034],{"x":1033,"y":1016,"fill":64,"style":1017,"textAnchor":240},"448.3","Proactive prompts",[44,1036],{"x":1037,"y":82,"width":1023,"height":1012,"rx":48,"fill":64,"stroke":64,"style":234},"542.9",[67,1039,1041],{"x":1040,"y":1016,"fill":64,"style":1017,"textAnchor":240},"637.4","First-open delay",[44,1043],{"x":69,"y":1044,"width":1011,"height":1012,"rx":48,"fill":63,"stroke":64,"style":271},"86.0",[67,1046,1048],{"x":1015,"y":1047,"fill":64,"style":1017,"textAnchor":1018},"105.5","Load on page load",[44,1050],{"x":1022,"y":1044,"width":1023,"height":1012,"rx":48,"fill":86,"stroke":87,"style":1051},"fill-opacity:0.24;stroke-opacity:0.9",[67,1053,1054],{"x":1026,"y":1047,"fill":64,"style":291,"textAnchor":240},"full widget",[44,1056],{"x":1030,"y":1044,"width":1023,"height":1012,"rx":48,"fill":114,"stroke":114,"style":1057},"fill-opacity:0.14;stroke-opacity:0.9",[67,1059,1060],{"x":1033,"y":1047,"fill":64,"style":291,"textAnchor":240},"immediate",[44,1062],{"x":1037,"y":1044,"width":1023,"height":1012,"rx":48,"fill":114,"stroke":114,"style":1057},[67,1064,63],{"x":1040,"y":1047,"fill":64,"style":291,"textAnchor":240},[44,1066],{"x":69,"y":237,"width":1011,"height":1012,"rx":48,"fill":63,"stroke":64,"style":271},[67,1068,1070],{"x":1015,"y":1069,"fill":64,"style":1017,"textAnchor":1018},"135.5","Load after idle (12s)",[44,1072],{"x":1022,"y":237,"width":1023,"height":1012,"rx":48,"fill":114,"stroke":114,"style":1057},[67,1074,1075],{"x":1026,"y":1069,"fill":64,"style":291,"textAnchor":240},"none at startup",[44,1077],{"x":1030,"y":237,"width":1023,"height":1012,"rx":48,"fill":64,"stroke":64,"style":234},[67,1079,1080],{"x":1033,"y":1069,"fill":64,"style":291,"textAnchor":240},"delayed",[44,1082],{"x":1037,"y":237,"width":1023,"height":1012,"rx":48,"fill":114,"stroke":114,"style":1057},[67,1084,1085],{"x":1040,"y":1069,"fill":64,"style":291,"textAnchor":240},"usually none",[44,1087],{"x":69,"y":1088,"width":1011,"height":1012,"rx":48,"fill":63,"stroke":64,"style":271},"146.0",[67,1090,1092],{"x":1015,"y":1091,"fill":64,"style":1017,"textAnchor":1018},"165.5","Load on hover\u002Fclick",[44,1094],{"x":1022,"y":1088,"width":1023,"height":1012,"rx":48,"fill":114,"stroke":114,"style":1057},[67,1096,63],{"x":1026,"y":1091,"fill":64,"style":291,"textAnchor":240},[44,1098],{"x":1030,"y":1088,"width":1023,"height":1012,"rx":48,"fill":86,"stroke":87,"style":1051},[67,1100,1101],{"x":1033,"y":1091,"fill":64,"style":291,"textAnchor":240},"not possible",[44,1103],{"x":1037,"y":1088,"width":1023,"height":1012,"rx":48,"fill":64,"stroke":64,"style":234},[67,1105,1106],{"x":1040,"y":1091,"fill":64,"style":291,"textAnchor":240},"1-2s on first click",[132,1108,1110],{"id":1109},"worked-example-a-saas-marketing-site","Worked Example: A SaaS Marketing Site",[15,1112,1113],{},"A SaaS marketing site loaded its chat widget on every page. In lab tests on a mid-tier phone, the widget added 430KB and 640ms of main-thread time; field INP p75 was 240ms, with LoAF attribution naming the widget's scripts in a third of slow interactions during the first ten seconds. A facade with hover preloading and click-to-open, plus a 15-second idle load on pricing pages only (where proactive chat mattered to sales), brought site-wide INP p75 to 170ms and LCP p75 down by 180ms. Chat conversations per visit were unchanged; conversations started from the pricing page rose slightly, because proactive prompts now arrived after users had read the page.",[132,1115,1117],{"id":1116},"common-mistakes","Common Mistakes",[137,1119,1120,1126,1132,1138],{},[140,1121,1122,1125],{},[143,1123,1124],{},"A facade that does not match the real launcher."," Size or position differences cause a visible jump and potential CLS.",[140,1127,1128,1131],{},[143,1129,1130],{},"Forgetting accessibility."," The facade must be a real button with an accessible name and keyboard support.",[140,1133,1134,1137],{},[143,1135,1136],{},"Loading on scroll."," Scrolling is not intent to chat; it loads the widget for most visitors anyway.",[140,1139,1140,1143],{},[143,1141,1142],{},"Ignoring logged-in or ongoing conversations."," Users with unread messages expect the widget to show them.",[132,1145,1147],{"id":1146},"edge-cases","Edge Cases",[15,1149,1150,1153,1154,1157],{},[143,1151,1152],{},"Vendors that require a boot call."," Some widgets need ",[335,1155,1156],{},"boot()"," with user identity after load; pass identity data in the deferred loader, not in an eager inline snippet.",[15,1159,1160,1163],{},[143,1161,1162],{},"Single-page apps."," On route changes, keep the facade or widget mounted once rather than re-injecting it per route.",[15,1165,1166,1169],{},[143,1167,1168],{},"Consent requirements."," If chat sets non-essential cookies, loading on click can double as the consent moment, which simplifies compliance for some setups — confirm with your legal team.",[15,1171,1172,1175],{},[143,1173,1174],{},"Mobile keyboards."," On mobile, opening the widget triggers the keyboard; make sure the facade's spinner state does not leave users tapping repeatedly.",[132,1177,1179],{"id":1178},"faq","FAQ",[1181,1182,1185,1189],"details",{"className":1183},[1184],"faq-item",[1186,1187,1188],"summary",{},"Do chat vendors support delayed loading officially?",[15,1190,1191],{},"Many expose options to hide the default launcher and to boot programmatically, which is all a facade needs. Some publish lazy-loading guides; others' snippets assume eager loading but work fine when injected later. Test the vendor's features (unread badges, proactive messages) after the change.",[1181,1193,1195,1198],{"className":1194},[1184],[1186,1196,1197],{},"Will deferring chat affect support metrics?",[15,1199,1200],{},"Usually not in ways users notice, since few open chat in the first seconds of a visit. Proactive messages are the main change; tune their timing with the support team and compare engagement before and after.",[1181,1202,1204,1207],{"className":1203},[1184],[1186,1205,1206],{},"Can Partytown run the chat widget in a worker instead?",[15,1208,1209],{},"Chat widgets are DOM-heavy and interactive, which is a poor fit for worker-based execution. A facade is simpler and more reliable for this category; worker offloading suits tag-like scripts better.",[1181,1211,1213,1216],{"className":1212},[1184],[1186,1214,1215],{},"What about help-centre search widgets?",[15,1217,1218],{},"The same pattern applies: render a static search box or button, load the vendor script on focus or click. Anything that is not needed until the user acts is a candidate.",[1181,1220,1222,1225],{"className":1221},[1184],[1186,1223,1224],{},"How do I keep the facade in sync with vendor design changes?",[15,1226,1227],{},"Vendors occasionally change their launcher's size, shape or position. Keep the facade's styles in one small component, add a visual regression test that compares it with the real launcher after loading, and review it whenever the vendor announces UI changes. A mismatch is cosmetic, but it causes a visible jump that users notice.",[132,1229,1231],{"id":1230},"related","Related",[137,1233,1234,1241,1248],{},[140,1235,1236,1240],{},[19,1237,1239],{"href":1238},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F","Replacing YouTube embeds with a facade"," — the same pattern for video embeds.",[140,1242,1243,1247],{},[19,1244,1246],{"href":1245},"\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 effect of vendors.",[140,1249,1250,1254],{},[19,1251,1253],{"href":1252},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freducing-consent-management-platform-cost\u002F","Reducing consent management platform cost"," — another always-loaded third party.",[1256,1257,1259],"script",{"type":1258},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Load Chat Widgets Only When Users Need Them\",\n  \"description\": \"Use a facade pattern to defer a live-chat widget until the user shows intent, cutting startup JavaScript and protecting INP and LCP.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Render a facade that looks like the launcher\",\n      \"text\": \"Expected outcome: users see a chat button immediately with no third-party cost.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Load the real widget on intent\",\n      \"text\": \"Expected outcome: zero chat JavaScript at load; the widget appears within a second or two of the user asking for it.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Optionally load after sustained idle for proactive messaging\",\n      \"text\": \"If the business relies on proactive chat prompts, load the widget after the page has been idle for a while (for example 10–15 seconds after load), not at load.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Preserve unread-message state\",\n      \"text\": \"Some users have ongoing conversations.\"\n    }\n  ]\n}\n",[1256,1261,1262],{"type":1258},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Load Chat Widgets Only When Users Need Them\",\n  \"description\": \"Use a facade pattern to defer a live-chat widget until the user shows intent, cutting startup JavaScript and protecting INP and LCP.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction\u002F\"\n  }\n}\n",[1256,1264,1265],{"type":1258},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"JavaScript Bundle Optimization & Code Splitting\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Third-Party Script Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Loading Chat Widgets on Interaction\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction\u002F\"\n    }\n  ]\n}\n",[440,1267,1268],{},"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 pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}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 .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":333,"searchDepth":384,"depth":384,"links":1270},[1271,1272,1273,1279,1280,1281,1282,1283,1284],{"id":134,"depth":384,"text":135},{"id":167,"depth":384,"text":168},{"id":320,"depth":384,"text":321,"children":1274},[1275,1276,1277,1278],{"id":325,"depth":425,"text":326},{"id":585,"depth":425,"text":586},{"id":918,"depth":425,"text":919},{"id":978,"depth":425,"text":979},{"id":985,"depth":384,"text":986},{"id":1109,"depth":384,"text":1110},{"id":1116,"depth":384,"text":1117},{"id":1146,"depth":384,"text":1147},{"id":1178,"depth":384,"text":1179},{"id":1230,"depth":384,"text":1231},"Use a facade pattern to defer a live-chat widget until the user shows intent, cutting startup JavaScript and protecting INP and LCP.","md",{"slug":1288,"type":1289,"breadcrumb":1290,"datePublished":1298,"dateModified":1298},"loading-chat-widgets-on-interaction","article",[1291,1294,1295,1296],{"name":1292,"url":1293},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1297},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction\u002F","2026-10-06",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction",{"title":1302,"description":1303},"Loading Chat Widgets on Interaction to Protect INP","Chat widgets ship hundreds of kilobytes and long tasks at load. Replace them with a lightweight facade button that loads the real widget on click or idle.","javascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Floading-chat-widgets-on-interaction\u002Findex","g9PRaG7n7mNRA2OHi-26hHCGkpmprykvdTAZwaxo2u4",[1307,1311],{"title":1308,"path":1309,"stem":1310},"Isolating Ad Scripts from the Main Thread","\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fisolating-ad-scripts-from-the-main-thread","javascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fisolating-ad-scripts-from-the-main-thread\u002Findex",{"title":1312,"path":1313,"stem":1314},"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",1791308076536]