[{"data":1,"prerenderedAt":1549},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade":1540},{"id":4,"title":5,"body":6,"description":1521,"extension":1522,"meta":1523,"navigation":1534,"path":1535,"seo":1536,"stem":1538,"__hash__":1539},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002Findex.md","Replacing YouTube Embeds with a Facade",{"type":7,"value":8,"toc":1513},"minimark",[9,14,42,45,50,90,190,194,200,206,212,222,226,236,692,695,715,994,997,1006,1232,1239,1253,1257,1260,1263,1363,1366,1370,1387,1407,1431,1440,1449,1458,1474,1478,1499,1504,1507,1510],[10,11,13],"h1",{"id":12},"how-to-replace-a-youtube-embed-with-a-facade","How to Replace a YouTube Embed with a Facade",[15,16,17,18,23,24,28,29,33,34,37,38,41],"p",{},"This walkthrough belongs to ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002F","Third-Party Script Performance"," under ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". A single YouTube ",[30,31,32],"code",{},"\u003Ciframe>"," pulls in the player application, its CSS, several scripts and a set of tracking requests — commonly ",[30,35,36],{},"600–900KB"," and ",[30,39,40],{},"300–600ms"," of main-thread work on a mobile profile — on every page view, whether or not anyone presses play. On a typical article page only a small minority of readers ever do.",[15,43,44],{},"A facade inverts that: render a poster image and a play affordance, and construct the real iframe on the first interaction.",[46,47,49],"h2",{"id":48},"rapid-diagnosis-quantify-what-the-embed-costs-you","Rapid Diagnosis: Quantify What the Embed Costs You",[51,52,53,72,78,84],"ul",{},[54,55,56,60,61,64,65,37,68,71],"li",{},[57,58,59],"strong",{},"Filter the Network panel by the embed's domains."," Count requests and total transfer from ",[30,62,63],{},"youtube.com",", ",[30,66,67],{},"ytimg.com",[30,69,70],{},"googlevideo.com"," before any interaction.",[54,73,74,77],{},[57,75,76],{},"Read the main-thread attribution."," Lighthouse's third-party summary will name the embed with its blocking time; on mobile it is frequently the largest single entry.",[54,79,80,83],{},[57,81,82],{},"Check the play rate."," Your analytics knows what fraction of page views produce a play event. If it is under 20%, you are loading the player for four out of five readers who never use it.",[54,85,86,89],{},[57,87,88],{},"Look for CLS."," An iframe without reserved dimensions collapses to a default box and then resizes, contributing shift — the facade fixes this incidentally by giving you a box you control.",[91,92,99,100,107,108,107,112,107,116,107,125,107,132,107,137,107,147,107,154,107,158,107,164,107,170,107,176,107,181,107,185,107],"svg",{"xmlns":93,"viewBox":94,"width":95,"role":96,"ariaLabel":97,"style":98},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 250","100%","img","Comparison of an eager YouTube iframe loading nine requests and 780 kilobytes on page load against a facade loading one 34 kilobyte image, deferring the player until a click","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)","\n  ",[101,102],"rect",{"className":103,"x":105,"y":105,"width":95,"height":95,"fill":106},[104],"svg-canvas","0","#ffffff"," ",[109,110,111],"title",{},"Eager embed versus facade, per page view",[113,114,115],"desc",{},"The eager iframe transfers 780 kilobytes across nine requests and 420 milliseconds of main-thread time before any interaction; the facade transfers 34 kilobytes in one request with no script execution until the user clicks.",[101,117],{"x":118,"y":118,"width":119,"height":120,"rx":121,"fill":122,"stroke":123,"style":124},"1","758","248","10","none","currentColor","stroke-opacity:0.18",[126,127,131],"text",{"x":128,"y":129,"fill":123,"style":130},"24","32","font-size:16px;font-weight:700","Cost before anyone presses play",[126,133,136],{"x":128,"y":134,"fill":123,"style":135},"72","font-size:12px;font-weight:700","Eager iframe",[101,138],{"x":139,"y":140,"width":141,"height":142,"rx":143,"fill":144,"stroke":145,"style":146},"160","56","470","26","4","#ffc300","#b8860b","fill-opacity:0.3",[126,148,153],{"x":149,"y":150,"fill":123,"style":151,"textAnchor":152},"395","74","font-size:11px;font-weight:700;","middle","780KB · 9 requests · 420ms main thread",[126,155,157],{"x":128,"y":156,"fill":123,"style":135},"122","Facade",[101,159],{"x":139,"y":160,"width":161,"height":142,"rx":143,"fill":162,"stroke":162,"style":163},"106","42","#0466c8","fill-opacity:0.2;stroke-opacity:0.55",[126,165,169],{"x":166,"y":167,"fill":123,"style":168},"220","124","font-size:11px;font-weight:700","34KB · 1 request · ~0ms",[171,172],"line",{"x1":128,"y1":173,"x2":174,"y2":173,"stroke":123,"style":175},"152","736","stroke-opacity:0.2",[126,177,180],{"x":128,"y":178,"fill":123,"style":179},"180","font-size:11.5px;fill-opacity:0.85","On click, the facade pays the full cost — for the minority of readers who asked for it.",[126,182,184],{"x":128,"y":183,"fill":123,"style":179},"204","At a 15% play rate the page-weighted saving is roughly 660KB and 350ms per view.",[126,186,189],{"x":128,"y":187,"fill":123,"style":188},"230","font-size:11px;font-style:italic;fill-opacity:0.7","Measure your own play rate before quoting a saving — it is the only variable that matters here.",[46,191,193],{"id":192},"root-cause-analysis-why-the-embed-is-so-expensive","Root Cause Analysis: Why the Embed Is So Expensive",[15,195,196,199],{},[57,197,198],{},"1. It is an application, not a video."," The iframe boots a full player: playlist logic, captions, quality selection, analytics and advertising hooks. All of it initialises whether or not playback begins.",[15,201,202,205],{},[57,203,204],{},"2. It runs on new origins."," Each origin costs a DNS lookup and a TLS handshake, and those happen during the page's most bandwidth-constrained moment.",[15,207,208,211],{},[57,209,210],{},"3. Iframe work competes with yours."," A cross-origin iframe gets its own document, but on most platforms it still shares process and network resources with the parent, so its startup shows up as input delay in your interactions.",[15,213,214,221],{},[57,215,216,217,220],{},"4. ",[30,218,219],{},"loading=\"lazy\""," only defers, it does not shrink."," Lazy-loading an iframe stops it from loading above the fold, which helps, but any reader who scrolls past pays the full cost — and on an article page most do.",[46,223,225],{"id":224},"step-by-step-resolution","Step-by-Step Resolution",[15,227,228,231,232,235],{},[57,229,230],{},"1. Render the facade as a real button with the correct aspect ratio."," Using a ",[30,233,234],{},"\u003Cbutton>"," gets keyboard support, focus styling and screen-reader semantics for free.",[237,238,243],"pre",{"className":239,"code":240,"language":241,"meta":242,"style":242},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Cbutton class=\"yt-facade\" data-video=\"M7lc1UVf-VE\"\n        aria-label=\"Play video: Debugging INP in the Performance panel\">\n  \u003Cimg src=\"\u002Fthumbs\u002Finp-debugging.avif\" width=\"1280\" height=\"720\" alt=\"\"\n       loading=\"lazy\" decoding=\"async\">\n  \u003Cspan class=\"yt-facade__play\" aria-hidden=\"true\">\u003C\u002Fspan>\n\u003C\u002Fbutton>\n\u003Cstyle>\n  .yt-facade { position: relative; display: block; width: 100%;\n               aspect-ratio: 16 \u002F 9; padding: 0; border: 0; cursor: pointer;\n               background: #000; }\n  .yt-facade img { width: 100%; height: 100%; object-fit: cover; display: block; }\n  .yt-facade__play { position: absolute; inset: 0; margin: auto;\n                     width: 68px; height: 48px; border-radius: 12px;\n                     background: rgba(0,0,0,.72); }\n\u003C\u002Fstyle>\n\u003C!-- trade-off: aspect-ratio reserves the box so the swap causes no layout\n     shift. If your poster is not 16:9 the video will letterbox inside it —\n     match the ratio to the source, do not stretch the image. -->\n","html","",[30,244,245,276,290,330,351,380,390,400,446,493,507,556,592,631,664,673,680,686],{"__ignoreMap":242},[246,247,249,253,257,261,264,268,271,273],"span",{"class":171,"line":248},1,[246,250,252],{"class":251},"saISM","\u003C",[246,254,256],{"class":255},"sZBmE","button",[246,258,260],{"class":259},"sPXB4"," class",[246,262,263],{"class":251},"=",[246,265,267],{"class":266},"sZ8jY","\"yt-facade\"",[246,269,270],{"class":259}," data-video",[246,272,263],{"class":251},[246,274,275],{"class":266},"\"M7lc1UVf-VE\"\n",[246,277,279,282,284,287],{"class":171,"line":278},2,[246,280,281],{"class":259},"        aria-label",[246,283,263],{"class":251},[246,285,286],{"class":266},"\"Play video: Debugging INP in the Performance panel\"",[246,288,289],{"class":251},">\n",[246,291,293,296,298,301,303,306,309,311,314,317,319,322,325,327],{"class":171,"line":292},3,[246,294,295],{"class":251},"  \u003C",[246,297,96],{"class":255},[246,299,300],{"class":259}," src",[246,302,263],{"class":251},[246,304,305],{"class":266},"\"\u002Fthumbs\u002Finp-debugging.avif\"",[246,307,308],{"class":259}," width",[246,310,263],{"class":251},[246,312,313],{"class":266},"\"1280\"",[246,315,316],{"class":259}," height",[246,318,263],{"class":251},[246,320,321],{"class":266},"\"720\"",[246,323,324],{"class":259}," alt",[246,326,263],{"class":251},[246,328,329],{"class":266},"\"\"\n",[246,331,333,336,338,341,344,346,349],{"class":171,"line":332},4,[246,334,335],{"class":259},"       loading",[246,337,263],{"class":251},[246,339,340],{"class":266},"\"lazy\"",[246,342,343],{"class":259}," decoding",[246,345,263],{"class":251},[246,347,348],{"class":266},"\"async\"",[246,350,289],{"class":251},[246,352,354,356,358,360,362,365,368,370,373,376,378],{"class":171,"line":353},5,[246,355,295],{"class":251},[246,357,246],{"class":255},[246,359,260],{"class":259},[246,361,263],{"class":251},[246,363,364],{"class":266},"\"yt-facade__play\"",[246,366,367],{"class":259}," aria-hidden",[246,369,263],{"class":251},[246,371,372],{"class":266},"\"true\"",[246,374,375],{"class":251},">\u003C\u002F",[246,377,246],{"class":255},[246,379,289],{"class":251},[246,381,383,386,388],{"class":171,"line":382},6,[246,384,385],{"class":251},"\u003C\u002F",[246,387,256],{"class":255},[246,389,289],{"class":251},[246,391,393,395,398],{"class":171,"line":392},7,[246,394,252],{"class":251},[246,396,397],{"class":255},"style",[246,399,289],{"class":251},[246,401,403,406,409,412,415,418,421,424,426,429,431,434,436,439,443],{"class":171,"line":402},8,[246,404,405],{"class":259},"  .yt-facade",[246,407,408],{"class":251}," { ",[246,410,411],{"class":259},"position",[246,413,414],{"class":251},": ",[246,416,417],{"class":259},"relative",[246,419,420],{"class":251},"; ",[246,422,423],{"class":259},"display",[246,425,414],{"class":251},[246,427,428],{"class":259},"block",[246,430,420],{"class":251},[246,432,433],{"class":259},"width",[246,435,414],{"class":251},[246,437,438],{"class":259},"100",[246,440,442],{"class":441},"sPARh","%",[246,444,445],{"class":251},";\n",[246,447,449,452,454,457,460,463,465,468,470,472,474,477,479,481,483,486,488,491],{"class":171,"line":448},9,[246,450,451],{"class":259},"               aspect-ratio",[246,453,414],{"class":251},[246,455,456],{"class":259},"16",[246,458,459],{"class":251}," \u002F ",[246,461,462],{"class":259},"9",[246,464,420],{"class":251},[246,466,467],{"class":259},"padding",[246,469,414],{"class":251},[246,471,105],{"class":259},[246,473,420],{"class":251},[246,475,476],{"class":259},"border",[246,478,414],{"class":251},[246,480,105],{"class":259},[246,482,420],{"class":251},[246,484,485],{"class":259},"cursor",[246,487,414],{"class":251},[246,489,490],{"class":259},"pointer",[246,492,445],{"class":251},[246,494,496,499,501,504],{"class":171,"line":495},10,[246,497,498],{"class":259},"               background",[246,500,414],{"class":251},[246,502,503],{"class":259},"#000",[246,505,506],{"class":251},"; }\n",[246,508,510,512,515,517,519,521,523,525,527,530,532,534,536,538,541,543,546,548,550,552,554],{"class":171,"line":509},11,[246,511,405],{"class":259},[246,513,514],{"class":255}," img",[246,516,408],{"class":251},[246,518,433],{"class":259},[246,520,414],{"class":251},[246,522,438],{"class":259},[246,524,442],{"class":441},[246,526,420],{"class":251},[246,528,529],{"class":259},"height",[246,531,414],{"class":251},[246,533,438],{"class":259},[246,535,442],{"class":441},[246,537,420],{"class":251},[246,539,540],{"class":259},"object-fit",[246,542,414],{"class":251},[246,544,545],{"class":259},"cover",[246,547,420],{"class":251},[246,549,423],{"class":259},[246,551,414],{"class":251},[246,553,428],{"class":259},[246,555,506],{"class":251},[246,557,559,562,564,566,568,571,573,576,578,580,582,585,587,590],{"class":171,"line":558},12,[246,560,561],{"class":259},"  .yt-facade__play",[246,563,408],{"class":251},[246,565,411],{"class":259},[246,567,414],{"class":251},[246,569,570],{"class":259},"absolute",[246,572,420],{"class":251},[246,574,575],{"class":259},"inset",[246,577,414],{"class":251},[246,579,105],{"class":259},[246,581,420],{"class":251},[246,583,584],{"class":259},"margin",[246,586,414],{"class":251},[246,588,589],{"class":259},"auto",[246,591,445],{"class":251},[246,593,595,598,600,603,606,608,610,612,615,617,619,622,624,627,629],{"class":171,"line":594},13,[246,596,597],{"class":259},"                     width",[246,599,414],{"class":251},[246,601,602],{"class":259},"68",[246,604,605],{"class":441},"px",[246,607,420],{"class":251},[246,609,529],{"class":259},[246,611,414],{"class":251},[246,613,614],{"class":259},"48",[246,616,605],{"class":441},[246,618,420],{"class":251},[246,620,621],{"class":259},"border-radius",[246,623,414],{"class":251},[246,625,626],{"class":259},"12",[246,628,605],{"class":441},[246,630,445],{"class":251},[246,632,634,637,639,642,645,647,650,652,654,656,658,661],{"class":171,"line":633},14,[246,635,636],{"class":259},"                     background",[246,638,414],{"class":251},[246,640,641],{"class":259},"rgba",[246,643,644],{"class":251},"(",[246,646,105],{"class":259},[246,648,649],{"class":251},",",[246,651,105],{"class":259},[246,653,649],{"class":251},[246,655,105],{"class":259},[246,657,649],{"class":251},[246,659,660],{"class":259},".72",[246,662,663],{"class":251},"); }\n",[246,665,667,669,671],{"class":171,"line":666},15,[246,668,385],{"class":251},[246,670,397],{"class":255},[246,672,289],{"class":251},[246,674,676],{"class":171,"line":675},16,[246,677,679],{"class":678},"sjfSM","\u003C!-- trade-off: aspect-ratio reserves the box so the swap causes no layout\n",[246,681,683],{"class":171,"line":682},17,[246,684,685],{"class":678},"     shift. If your poster is not 16:9 the video will letterbox inside it —\n",[246,687,689],{"class":171,"line":688},18,[246,690,691],{"class":678},"     match the ratio to the source, do not stretch the image. -->\n",[15,693,694],{},"Expected outcome: the visual result is indistinguishable from the embed, at the cost of one image request.",[15,696,697,700,701,704,705,707,708,37,711,714],{},[57,698,699],{},"2. Build the iframe on first activation."," A ",[30,702,703],{},"click"," listener on a ",[30,706,234],{}," also fires for ",[30,709,710],{},"Enter",[30,712,713],{},"Space",", so keyboard users are covered without extra code.",[237,716,720],{"className":717,"code":718,"language":719,"meta":242,"style":242},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","document.querySelectorAll('.yt-facade').forEach((el) => {\n  el.addEventListener('click', () => {\n    const id = el.dataset.video;\n    const frame = document.createElement('iframe');\n    frame.src = `https:\u002F\u002Fwww.youtube-nocookie.com\u002Fembed\u002F${id}?autoplay=1&rel=0`;\n    frame.title = el.getAttribute('aria-label').replace(\u002F^Play video: \u002F, '');\n    frame.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; ' +\n                  'gyroscope; picture-in-picture; web-share';\n    frame.allowFullscreen = true;\n    frame.width = 1280; frame.height = 720;\n    frame.style.cssText = 'width:100%;aspect-ratio:16\u002F9;border:0;display:block';\n    el.replaceWith(frame);\n    frame.focus();                      \u002F\u002F keep keyboard focus in the player\n  }, { once: true });\n});\n\u002F\u002F trade-off: autoplay=1 is required so the click that replaced the facade also\n\u002F\u002F starts playback. Some browsers block autoplay with sound in edge cases; the\n\u002F\u002F user then sees the player's own play button, which is an acceptable fallback.\n","javascript",[30,721,722,758,778,792,815,833,874,887,894,906,926,938,949,963,974,979,984,989],{"__ignoreMap":242},[246,723,724,727,731,733,736,739,742,745,749,752,755],{"class":171,"line":248},[246,725,726],{"class":251},"document.",[246,728,730],{"class":729},"smZ65","querySelectorAll",[246,732,644],{"class":251},[246,734,735],{"class":266},"'.yt-facade'",[246,737,738],{"class":251},").",[246,740,741],{"class":729},"forEach",[246,743,744],{"class":251},"((",[246,746,748],{"class":747},"sQw3B","el",[246,750,751],{"class":251},") ",[246,753,754],{"class":441},"=>",[246,756,757],{"class":251}," {\n",[246,759,760,763,766,768,771,774,776],{"class":171,"line":278},[246,761,762],{"class":251},"  el.",[246,764,765],{"class":729},"addEventListener",[246,767,644],{"class":251},[246,769,770],{"class":266},"'click'",[246,772,773],{"class":251},", () ",[246,775,754],{"class":441},[246,777,757],{"class":251},[246,779,780,783,786,789],{"class":171,"line":292},[246,781,782],{"class":441},"    const",[246,784,785],{"class":259}," id",[246,787,788],{"class":441}," =",[246,790,791],{"class":251}," el.dataset.video;\n",[246,793,794,796,799,801,804,807,809,812],{"class":171,"line":332},[246,795,782],{"class":441},[246,797,798],{"class":259}," frame",[246,800,788],{"class":441},[246,802,803],{"class":251}," document.",[246,805,806],{"class":729},"createElement",[246,808,644],{"class":251},[246,810,811],{"class":266},"'iframe'",[246,813,814],{"class":251},");\n",[246,816,817,820,822,825,828,831],{"class":171,"line":353},[246,818,819],{"class":251},"    frame.src ",[246,821,263],{"class":441},[246,823,824],{"class":266}," `https:\u002F\u002Fwww.youtube-nocookie.com\u002Fembed\u002F${",[246,826,827],{"class":251},"id",[246,829,830],{"class":266},"}?autoplay=1&rel=0`",[246,832,445],{"class":251},[246,834,835,838,840,843,846,848,851,853,856,858,861,864,867,869,872],{"class":171,"line":382},[246,836,837],{"class":251},"    frame.title ",[246,839,263],{"class":441},[246,841,842],{"class":251}," el.",[246,844,845],{"class":729},"getAttribute",[246,847,644],{"class":251},[246,849,850],{"class":266},"'aria-label'",[246,852,738],{"class":251},[246,854,855],{"class":729},"replace",[246,857,644],{"class":251},[246,859,860],{"class":266},"\u002F",[246,862,863],{"class":441},"^",[246,865,866],{"class":266},"Play video: \u002F",[246,868,64],{"class":251},[246,870,871],{"class":266},"''",[246,873,814],{"class":251},[246,875,876,879,881,884],{"class":171,"line":392},[246,877,878],{"class":251},"    frame.allow ",[246,880,263],{"class":441},[246,882,883],{"class":266}," 'accelerometer; autoplay; clipboard-write; encrypted-media; '",[246,885,886],{"class":441}," +\n",[246,888,889,892],{"class":171,"line":402},[246,890,891],{"class":266},"                  'gyroscope; picture-in-picture; web-share'",[246,893,445],{"class":251},[246,895,896,899,901,904],{"class":171,"line":448},[246,897,898],{"class":251},"    frame.allowFullscreen ",[246,900,263],{"class":441},[246,902,903],{"class":259}," true",[246,905,445],{"class":251},[246,907,908,911,913,916,919,921,924],{"class":171,"line":495},[246,909,910],{"class":251},"    frame.width ",[246,912,263],{"class":441},[246,914,915],{"class":259}," 1280",[246,917,918],{"class":251},"; frame.height ",[246,920,263],{"class":441},[246,922,923],{"class":259}," 720",[246,925,445],{"class":251},[246,927,928,931,933,936],{"class":171,"line":509},[246,929,930],{"class":251},"    frame.style.cssText ",[246,932,263],{"class":441},[246,934,935],{"class":266}," 'width:100%;aspect-ratio:16\u002F9;border:0;display:block'",[246,937,445],{"class":251},[246,939,940,943,946],{"class":171,"line":558},[246,941,942],{"class":251},"    el.",[246,944,945],{"class":729},"replaceWith",[246,947,948],{"class":251},"(frame);\n",[246,950,951,954,957,960],{"class":171,"line":594},[246,952,953],{"class":251},"    frame.",[246,955,956],{"class":729},"focus",[246,958,959],{"class":251},"();                      ",[246,961,962],{"class":678},"\u002F\u002F keep keyboard focus in the player\n",[246,964,965,968,971],{"class":171,"line":633},[246,966,967],{"class":251},"  }, { once: ",[246,969,970],{"class":259},"true",[246,972,973],{"class":251}," });\n",[246,975,976],{"class":171,"line":666},[246,977,978],{"class":251},"});\n",[246,980,981],{"class":171,"line":675},[246,982,983],{"class":678},"\u002F\u002F trade-off: autoplay=1 is required so the click that replaced the facade also\n",[246,985,986],{"class":171,"line":682},[246,987,988],{"class":678},"\u002F\u002F starts playback. Some browsers block autoplay with sound in edge cases; the\n",[246,990,991],{"class":171,"line":688},[246,992,993],{"class":678},"\u002F\u002F user then sees the player's own play button, which is an acceptable fallback.\n",[15,995,996],{},"Expected outcome: identical playback experience, one extra click, and zero cost for readers who never play.",[15,998,999,700,1002,1005],{},[57,1000,1001],{},"3. Warm the connection on intent, not on load.",[30,1003,1004],{},"preconnect"," fired on hover or focus removes most of the handshake latency without paying for it on every page view.",[237,1007,1009],{"className":717,"code":1008,"language":719,"meta":242,"style":242},"let warmed = false;\nfunction warm() {\n  if (warmed) return; warmed = true;\n  for (const href of ['https:\u002F\u002Fwww.youtube-nocookie.com', 'https:\u002F\u002Fi.ytimg.com']) {\n    const l = document.createElement('link');\n    l.rel = 'preconnect'; l.href = href; l.crossOrigin = '';\n    document.head.append(l);\n  }\n}\ndocument.querySelectorAll('.yt-facade').forEach((el) => {\n  el.addEventListener('pointerenter', warm, { once: true });\n  el.addEventListener('focusin', warm, { once: true });\n});\n\u002F\u002F trade-off: on touch devices pointerenter fires with the tap itself, so the\n\u002F\u002F warm-up and the load are effectively simultaneous. The benefit is real on\n\u002F\u002F desktop hover and negligible on mobile — do not count on it either way.\n",[30,1010,1011,1026,1037,1057,1088,1108,1133,1144,1149,1154,1178,1196,1213,1217,1222,1227],{"__ignoreMap":242},[246,1012,1013,1016,1019,1021,1024],{"class":171,"line":248},[246,1014,1015],{"class":441},"let",[246,1017,1018],{"class":251}," warmed ",[246,1020,263],{"class":441},[246,1022,1023],{"class":259}," false",[246,1025,445],{"class":251},[246,1027,1028,1031,1034],{"class":171,"line":278},[246,1029,1030],{"class":441},"function",[246,1032,1033],{"class":729}," warm",[246,1035,1036],{"class":251},"() {\n",[246,1038,1039,1042,1045,1048,1051,1053,1055],{"class":171,"line":292},[246,1040,1041],{"class":441},"  if",[246,1043,1044],{"class":251}," (warmed) ",[246,1046,1047],{"class":441},"return",[246,1049,1050],{"class":251},"; warmed ",[246,1052,263],{"class":441},[246,1054,903],{"class":259},[246,1056,445],{"class":251},[246,1058,1059,1062,1065,1068,1071,1074,1077,1080,1082,1085],{"class":171,"line":332},[246,1060,1061],{"class":441},"  for",[246,1063,1064],{"class":251}," (",[246,1066,1067],{"class":441},"const",[246,1069,1070],{"class":259}," href",[246,1072,1073],{"class":441}," of",[246,1075,1076],{"class":251}," [",[246,1078,1079],{"class":266},"'https:\u002F\u002Fwww.youtube-nocookie.com'",[246,1081,64],{"class":251},[246,1083,1084],{"class":266},"'https:\u002F\u002Fi.ytimg.com'",[246,1086,1087],{"class":251},"]) {\n",[246,1089,1090,1092,1095,1097,1099,1101,1103,1106],{"class":171,"line":353},[246,1091,782],{"class":441},[246,1093,1094],{"class":259}," l",[246,1096,788],{"class":441},[246,1098,803],{"class":251},[246,1100,806],{"class":729},[246,1102,644],{"class":251},[246,1104,1105],{"class":266},"'link'",[246,1107,814],{"class":251},[246,1109,1110,1113,1115,1118,1121,1123,1126,1128,1131],{"class":171,"line":382},[246,1111,1112],{"class":251},"    l.rel ",[246,1114,263],{"class":441},[246,1116,1117],{"class":266}," 'preconnect'",[246,1119,1120],{"class":251},"; l.href ",[246,1122,263],{"class":441},[246,1124,1125],{"class":251}," href; l.crossOrigin ",[246,1127,263],{"class":441},[246,1129,1130],{"class":266}," ''",[246,1132,445],{"class":251},[246,1134,1135,1138,1141],{"class":171,"line":392},[246,1136,1137],{"class":251},"    document.head.",[246,1139,1140],{"class":729},"append",[246,1142,1143],{"class":251},"(l);\n",[246,1145,1146],{"class":171,"line":402},[246,1147,1148],{"class":251},"  }\n",[246,1150,1151],{"class":171,"line":448},[246,1152,1153],{"class":251},"}\n",[246,1155,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174,1176],{"class":171,"line":495},[246,1157,726],{"class":251},[246,1159,730],{"class":729},[246,1161,644],{"class":251},[246,1163,735],{"class":266},[246,1165,738],{"class":251},[246,1167,741],{"class":729},[246,1169,744],{"class":251},[246,1171,748],{"class":747},[246,1173,751],{"class":251},[246,1175,754],{"class":441},[246,1177,757],{"class":251},[246,1179,1180,1182,1184,1186,1189,1192,1194],{"class":171,"line":509},[246,1181,762],{"class":251},[246,1183,765],{"class":729},[246,1185,644],{"class":251},[246,1187,1188],{"class":266},"'pointerenter'",[246,1190,1191],{"class":251},", warm, { once: ",[246,1193,970],{"class":259},[246,1195,973],{"class":251},[246,1197,1198,1200,1202,1204,1207,1209,1211],{"class":171,"line":558},[246,1199,762],{"class":251},[246,1201,765],{"class":729},[246,1203,644],{"class":251},[246,1205,1206],{"class":266},"'focusin'",[246,1208,1191],{"class":251},[246,1210,970],{"class":259},[246,1212,973],{"class":251},[246,1214,1215],{"class":171,"line":594},[246,1216,978],{"class":251},[246,1218,1219],{"class":171,"line":633},[246,1220,1221],{"class":678},"\u002F\u002F trade-off: on touch devices pointerenter fires with the tap itself, so the\n",[246,1223,1224],{"class":171,"line":666},[246,1225,1226],{"class":678},"\u002F\u002F warm-up and the load are effectively simultaneous. The benefit is real on\n",[246,1228,1229],{"class":171,"line":675},[246,1230,1231],{"class":678},"\u002F\u002F desktop hover and negligible on mobile — do not count on it either way.\n",[15,1233,1234,1235,1238],{},"Expected outcome: perceived start-up latency after the click drops by roughly the handshake time, typically ",[30,1236,1237],{},"100–250ms"," on desktop.",[15,1240,1241,1244,1245,1247,1248,1252],{},[57,1242,1243],{},"4. Source the poster locally."," Fetching the thumbnail from ",[30,1246,67],{}," reintroduces a third-party connection on load — precisely what you removed. Download it at build time, convert to AVIF or WebP, and serve it from your own origin as described in ",[19,1249,1251],{"href":1250},"\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F","serving AVIF and WebP with fallbacks",".",[46,1254,1256],{"id":1255},"verification","Verification",[15,1258,1259],{},"Load the page and confirm the Network panel shows no requests to any video domain before interaction. Then click and confirm playback starts immediately, that focus lands inside the player, and that nothing on the page moved when the iframe replaced the button.",[15,1261,1262],{},"Measure the aggregate effect on the routes that carry videos:",[237,1264,1268],{"className":1265,"code":1266,"language":1267,"meta":242,"style":242},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","{ \"ci\": { \"assert\": { \"assertions\": {\n  \"third-party-summary\": [\"error\", { \"maxNumericValue\": 250 }],\n  \"total-byte-weight\": [\"error\", { \"maxNumericValue\": 1200000 }],\n  \"cumulative-layout-shift\": [\"error\", { \"maxNumericValue\": 0.05 }]\n} } } }\n","json",[30,1269,1270,1292,1317,1337,1358],{"__ignoreMap":242},[246,1271,1272,1275,1278,1281,1284,1286,1289],{"class":171,"line":248},[246,1273,1274],{"class":251},"{ ",[246,1276,1277],{"class":255},"\"ci\"",[246,1279,1280],{"class":251},": { ",[246,1282,1283],{"class":255},"\"assert\"",[246,1285,1280],{"class":251},[246,1287,1288],{"class":255},"\"assertions\"",[246,1290,1291],{"class":251},": {\n",[246,1293,1294,1297,1300,1303,1306,1309,1311,1314],{"class":171,"line":278},[246,1295,1296],{"class":255},"  \"third-party-summary\"",[246,1298,1299],{"class":251},": [",[246,1301,1302],{"class":266},"\"error\"",[246,1304,1305],{"class":251},", { ",[246,1307,1308],{"class":255},"\"maxNumericValue\"",[246,1310,414],{"class":251},[246,1312,1313],{"class":259},"250",[246,1315,1316],{"class":251}," }],\n",[246,1318,1319,1322,1324,1326,1328,1330,1332,1335],{"class":171,"line":292},[246,1320,1321],{"class":255},"  \"total-byte-weight\"",[246,1323,1299],{"class":251},[246,1325,1302],{"class":266},[246,1327,1305],{"class":251},[246,1329,1308],{"class":255},[246,1331,414],{"class":251},[246,1333,1334],{"class":259},"1200000",[246,1336,1316],{"class":251},[246,1338,1339,1342,1344,1346,1348,1350,1352,1355],{"class":171,"line":332},[246,1340,1341],{"class":255},"  \"cumulative-layout-shift\"",[246,1343,1299],{"class":251},[246,1345,1302],{"class":266},[246,1347,1305],{"class":251},[246,1349,1308],{"class":255},[246,1351,414],{"class":251},[246,1353,1354],{"class":259},"0.05",[246,1356,1357],{"class":251}," }]\n",[246,1359,1360],{"class":171,"line":353},[246,1361,1362],{"class":251},"} } } }\n",[15,1364,1365],{},"Check the product side too, because this change is only worth shipping if playback holds up. Compare play rate before and after: a facade that requires one extra click should show a small decrease at most. A large drop usually means the play affordance is not obvious enough — make it look like the control it replaced.",[46,1367,1369],{"id":1368},"faq","FAQ",[1371,1372,1375,1379],"details",{"className":1373},[1374],"faq-item",[1376,1377,1378],"summary",{},"Does a facade break YouTube's view counting?",[15,1380,1381,1382,1386],{},"No. The view is counted by the player once it loads and playback begins, which still happens — just later. What you lose is ",[1383,1384,1385],"em",{},"impression"," data: YouTube no longer knows the video was displayed to readers who never pressed play. If someone depends on that impression metric, agree the change with them first, or record your own impression event on the facade.",[1371,1388,1390,1393],{"className":1389},[1374],[1376,1391,1392],{},"Is `loading=\"lazy\"` on the iframe enough on its own?",[15,1394,1395,1396,1399,1400,1403,1404,1406],{},"It helps for videos far below the fold and does nothing for the rest. Any reader who scrolls to the embed pays the full ",[30,1397,1398],{},"700KB"," and the full main-thread cost, which on an article page is most of them. The facade defers on ",[1383,1401,1402],{},"intent"," rather than on ",[1383,1405,411],{},", so the cost is paid only by the people who actually want the video.",[1371,1408,1410,1413],{"className":1409},[1374],[1376,1411,1412],{},"How do I keep the facade accessible?",[15,1414,1415,1416,1418,1419,1422,1423,1426,1427,1430],{},"Use a real ",[30,1417,234],{}," rather than a clickable ",[30,1420,1421],{},"\u003Cdiv>",", give it an ",[30,1424,1425],{},"aria-label"," naming the video, mark the decorative poster with an empty ",[30,1428,1429],{},"alt",", and move focus into the iframe after replacement so keyboard users are not dumped at the top of the document. Test the whole flow with the keyboard only — tab to the facade, press Enter, and confirm the player has focus.",[1371,1432,1434,1437],{"className":1433},[1374],[1376,1435,1436],{},"Can the same pattern work for maps and chat widgets?",[15,1438,1439],{},"Yes, and the economics are usually even better. A map facade is a static tile image that becomes an interactive map on click; a chat facade is a styled button that boots the widget when pressed. Both replace a permanently running third-party application with an image, and both are far more likely to be ignored by the average visitor than a video is.",[1371,1441,1443,1446],{"className":1442},[1374],[1376,1444,1445],{},"Where should the poster image come from?",[15,1447,1448],{},"Generate it at build time from the video's own thumbnail and store it with your other assets. Fetching it live from the vendor's image host reintroduces a third-party connection on every page load, which is a meaningful share of what the facade was meant to remove. Convert it to a modern format at the size the facade actually renders — a 16:9 poster at the article's content width is usually well under 40KB.",[1371,1450,1452,1455],{"className":1451},[1374],[1376,1453,1454],{},"Does this pattern work for privacy compliance as well?",[15,1456,1457],{},"It helps considerably, which is often the reason it gets approved. No vendor code runs and no vendor cookie is set until the visitor deliberately activates the player, so the embed stops being a passive tracking surface on every page view. That usually means it no longer needs to sit behind a consent gate to be lawful in stricter jurisdictions — but confirm the specifics with whoever owns that decision rather than assuming.",[1371,1459,1461,1464],{"className":1460},[1374],[1376,1462,1463],{},"What if the video is meant to autoplay as a hero background?",[15,1465,1466,1467,107,1470,1473],{},"Then a facade is the wrong tool, because there is no interaction to defer to. Host the clip yourself as a short, muted, ",[30,1468,1469],{},"playsinline",[30,1471,1472],{},"\u003Cvideo>"," with a poster image, encoded small and without audio tracks. A third-party player is a poor fit for background video in any case: you are loading an entire application to render a loop that a plain video element handles in a fraction of the bytes.",[46,1475,1477],{"id":1476},"related","Related",[51,1479,1480,1485,1492],{},[54,1481,1482,1484],{},[19,1483,22],{"href":21}," — the containment strategy this pattern belongs to.",[54,1486,1487,1491],{},[19,1488,1490],{"href":1489},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fmeasuring-third-party-cost-with-the-coverage-panel\u002F","Measuring third-party cost with the Coverage panel"," — quantify the embed before and after.",[54,1493,1494,1498],{},[19,1495,1497],{"href":1496},"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F","Lazy loading images without hurting LCP"," — how to load the poster image without delaying the metric.",[1500,1501,1503],"script",{"type":1502},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Replace a YouTube embed with a facade\",\n  \"description\": \"Render a poster image and an accessible play button in place of a YouTube iframe, construct the real player on first activation, warm the connection on intent, and serve the poster from your own origin.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Render the facade\", \"text\": \"Use a real button with a reserved 16:9 box, a local poster image and a visible play affordance.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Build the iframe on activation\", \"text\": \"Create the player iframe on click with autoplay enabled and move focus into it.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Warm the connection on intent\", \"text\": \"Inject preconnect hints on hover or focus so the handshake is done before the click.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Host the poster yourself\", \"text\": \"Download and convert the thumbnail at build time so no third-party image request happens on load.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F#step-by-step-resolution\" }\n  ]\n}\n",[1500,1505,1506],{"type":1502},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Replace a YouTube Embed with a Facade\",\n  \"description\": \"Cut hundreds of kilobytes and hundreds of milliseconds per page view by deferring the video player until the reader actually asks for it.\",\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\u002Freplacing-youtube-embeds-with-a-facade\u002F\" }\n}\n",[1500,1508,1509],{"type":1502},"\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\": \"Replacing YouTube Embeds with a Facade\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F\" }\n  ]\n}\n",[397,1511,1512],{},"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":242,"searchDepth":278,"depth":278,"links":1514},[1515,1516,1517,1518,1519,1520],{"id":48,"depth":278,"text":49},{"id":192,"depth":278,"text":193},{"id":224,"depth":278,"text":225},{"id":1255,"depth":278,"text":1256},{"id":1368,"depth":278,"text":1369},{"id":1476,"depth":278,"text":1477},"Swap a heavyweight video iframe for a static poster and a play button, then load the real player on interaction — without breaking keyboard access or shifting layout.","md",{"slug":1524,"type":1525,"breadcrumb":1526,"datePublished":1533,"dateModified":1533},"replacing-youtube-embeds-with-a-facade","article",[1527,1529,1530,1531],{"name":1528,"url":860},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1532},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F","2026-08-01",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade",{"title":5,"description":1537},"A YouTube iframe costs ~700KB and hundreds of milliseconds of main-thread time. Replace it with a poster-image facade that loads the player only on click — accessibly.","javascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002Findex","2Fa9DKpL3SwTRLbrBXKj1LmAMuD_FIj_l5HjoNiY8kk",[1541,1545],{"title":1542,"path":1543,"stem":1544,"children":-1},"Partytown vs Async Script Loading for Tag Managers","\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers","javascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fpartytown-vs-async-script-loading-for-tag-managers\u002Findex",{"title":1546,"path":1547,"stem":1548,"children":-1},"Tree Shaking and Dead Code Elimination","\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination","javascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Findex",1785622405980]