[{"data":1,"prerenderedAt":929},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs":920},{"id":4,"title":5,"body":6,"description":899,"extension":900,"meta":901,"navigation":913,"path":914,"seo":915,"stem":918,"__hash__":919},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs\u002Findex.md","Using modulepreload for ES Module Graphs",{"type":7,"value":8,"toc":881},"minimark",[9,14,34,44,185,190,226,230,236,242,248,254,369,373,378,493,496,500,507,511,514,518,521,649,653,656,660,663,667,678,682,689,693,722,726,732,738,744,750,754,766,775,784,793,802,815,828,840,844,867,871,874,877],[10,11,13],"h1",{"id":12},"how-to-use-modulepreload-for-es-module-graphs","How to Use modulepreload for ES Module Graphs",[15,16,17,18,23,24,28,29,33],"p",{},"This guide covers the module-specific hint within ",[19,20,22],"a",{"href":21},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002F","Resource Hints & Early Hints",", part of ",[19,25,27],{"href":26},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",". ES modules are loaded as a graph: the browser fetches the entry module, parses it to find its ",[30,31,32],"code",{},"import"," statements, fetches those, parses them, and so on. Each level of the graph is discovered only after the previous level arrives, producing a waterfall that costs one round trip per level of depth.",[15,35,36,39,40,43],{},[30,37,38],{},"\u003Clink rel=\"modulepreload\">"," tells the browser about modules before they are imported. Unlike a plain ",[30,41,42],{},"preload as=\"script\"",", it fetches the module with the correct request mode and credentials for module scripts, stores it in the module map, and may parse and compile it ahead of time. Listing the startup graph's modules collapses the waterfall into one parallel batch.",[15,45,46],{},[47,48,54,55,54,62,54,66,54,69,54,78,54,84,54,89,54,98,54,105,54,109,54,113,54,119,54,123,54,127,54,131,54,136,54,143,54,149,54,153,54,157,54,161,54,165,54,169,54,173,54,177,54,181,54],"svg",{"viewBox":49,"width":50,"role":51,"ariaLabel":52,"style":53},"0 0 760 178","100%","img","Timelines comparing sequential discovery of a three-level module graph with parallel fetching via modulepreload hints.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[56,57],"rect",{"className":58,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],"svg-canvas","0","#ffffff",[63,64,65],"title",{},"A three-level module graph with and without modulepreload",[67,68,52],"desc",{},[56,70],{"x":71,"y":71,"width":72,"height":73,"rx":74,"fill":75,"stroke":76,"style":77},"1","758","176","10","none","currentColor","stroke-opacity:0.18",[79,80,65],"text",{"x":81,"y":82,"fill":76,"style":83},"28.0","34.0","font-size:16px;font-weight:700",[79,85,88],{"x":81,"y":86,"fill":76,"style":87},"74.0","font-size:12.5px;font-weight:700","Discovered",[56,90],{"x":91,"y":92,"width":93,"height":94,"rx":95,"fill":96,"stroke":96,"style":97},"108.9","56.0","177.0","26.0","3","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[79,99,104],{"x":100,"y":101,"fill":76,"style":102,"textAnchor":103},"197.4","73.5","font-size:11.5px","middle","main.js",[56,106],{"x":107,"y":92,"width":93,"height":94,"rx":95,"fill":76,"stroke":76,"style":108},"287.4","fill-opacity:0.06;stroke-opacity:0.4",[79,110,112],{"x":111,"y":101,"fill":76,"style":102,"textAnchor":103},"375.9","router.js",[56,114],{"x":115,"y":92,"width":93,"height":94,"rx":95,"fill":116,"stroke":117,"style":118},"465.9","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[79,120,122],{"x":121,"y":101,"fill":76,"style":102,"textAnchor":103},"554.4","view.js",[79,124,126],{"x":81,"y":125,"fill":76,"style":87},"112.0","Preloaded",[56,128],{"x":91,"y":129,"width":130,"height":94,"rx":95,"fill":116,"stroke":117,"style":118},"94.0","197.6",[79,132,135],{"x":133,"y":134,"fill":76,"style":102,"textAnchor":103},"207.7","111.5","all three in parallel",[137,138],"line",{"x1":139,"y1":140,"x2":141,"y2":140,"stroke":76,"style":142},"108.2","134.0","726.0","stroke-opacity:0.4",[79,144,148],{"x":145,"y":146,"fill":147,"style":102,"textAnchor":103},"118.9","151.0","#51617a","0ms",[79,150,152],{"x":151,"y":146,"fill":147,"style":102,"textAnchor":103},"176.8","100ms",[79,154,156],{"x":155,"y":146,"fill":147,"style":102,"textAnchor":103},"245.5","200ms",[79,158,160],{"x":159,"y":146,"fill":147,"style":102,"textAnchor":103},"314.1","300ms",[79,162,164],{"x":163,"y":146,"fill":147,"style":102,"textAnchor":103},"382.8","400ms",[79,166,168],{"x":167,"y":146,"fill":147,"style":102,"textAnchor":103},"451.4","500ms",[79,170,172],{"x":171,"y":146,"fill":147,"style":102,"textAnchor":103},"520.1","600ms",[79,174,176],{"x":175,"y":146,"fill":147,"style":102,"textAnchor":103},"588.7","700ms",[79,178,180],{"x":179,"y":146,"fill":147,"style":102,"textAnchor":103},"657.4","800ms",[79,182,184],{"x":183,"y":146,"fill":147,"style":102,"textAnchor":103},"715.3","900ms",[186,187,189],"h2",{"id":188},"rapid-diagnosis","Rapid Diagnosis",[191,192,193,201,214,220],"ul",{},[194,195,196,200],"li",{},[197,198,199],"strong",{},"Look for staircases"," in the Network panel's JavaScript requests: each module starting only after another finishes.",[194,202,203,54,206,209,210,213],{},[197,204,205],{},"Check hint types.",[30,207,208],{},"rel=\"preload\" as=\"script\""," for module scripts can result in a second request (mode mismatch); modules should use ",[30,211,212],{},"modulepreload",".",[194,215,216,219],{},[197,217,218],{},"Check bundler output."," Vite emits modulepreload links automatically; custom setups and unbundled sites may not.",[194,221,222,225],{},[197,223,224],{},"Count hints."," Dozens of modulepreloads can crowd out the LCP image and CSS.",[186,227,229],{"id":228},"root-cause-analysis","Root Cause Analysis",[15,231,232,235],{},[197,233,234],{},"1. Discovery waterfalls."," Imports are only known after parsing the importer.",[15,237,238,241],{},[197,239,240],{},"2. Wrong hint type."," Plain preload does not match the module request's mode, so the module is fetched again.",[15,243,244,247],{},[197,245,246],{},"3. Over-preloading."," Preloading the entire graph, including rarely used modules, wastes bandwidth during load.",[15,249,250,253],{},[197,251,252],{},"4. Missing hints for dynamic routes."," Dynamically imported chunks and their dependencies load in their own waterfall when triggered.",[15,255,256],{},[47,257,54,260,54,263,54,266,54,268,54,271,54,273,54,277,54,283,54,287,54,291,54,294,54,297,54,302,54,306,54,309,54,311,54,316,54,318,54,321,54,324,54,328,54,330,54,333,54,335,54,337,54,340,54,344,54,346,54,348,54,350,54,353,54,356,54,360,54,362,54,364,54,366,54],{"viewBox":258,"width":50,"role":51,"ariaLabel":259,"style":53},"0 0 760 244","Comparison of plain preload and modulepreload when used for ES module scripts.",[56,261],{"className":262,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,264,265],{},"preload vs modulepreload for JavaScript modules",[67,267,259],{},[56,269],{"x":71,"y":71,"width":72,"height":270,"rx":74,"fill":75,"stroke":76,"style":77},"242",[79,272,265],{"x":81,"y":82,"fill":76,"style":83},[56,274],{"x":81,"y":92,"width":275,"height":276,"rx":60,"fill":76,"stroke":76,"style":108},"210.0","30.0",[79,278,282],{"x":279,"y":280,"fill":76,"style":87,"textAnchor":281},"38.0","75.5","start","Aspect",[56,284],{"x":285,"y":92,"width":286,"height":276,"rx":60,"fill":76,"stroke":76,"style":108},"238.0","247.0",[79,288,290],{"x":289,"y":280,"fill":76,"style":87,"textAnchor":103},"361.5","preload as=script",[56,292],{"x":293,"y":92,"width":286,"height":276,"rx":60,"fill":76,"stroke":76,"style":108},"485.0",[79,295,212],{"x":296,"y":280,"fill":76,"style":87,"textAnchor":103},"608.5",[56,298],{"x":81,"y":299,"width":275,"height":300,"rx":60,"fill":75,"stroke":76,"style":301},"86.0","46.0","stroke-opacity:0.35",[79,303,305],{"x":279,"y":304,"fill":76,"style":87,"textAnchor":281},"105.5","Request mode matches module",[79,307,32],{"x":279,"y":308,"fill":76,"style":87,"textAnchor":281},"121.5",[56,310],{"x":285,"y":299,"width":286,"height":300,"rx":60,"fill":116,"stroke":117,"style":118},[79,312,315],{"x":289,"y":313,"fill":76,"style":314,"textAnchor":103},"113.5","font-size:12px","not by default",[56,317],{"x":293,"y":299,"width":286,"height":300,"rx":60,"fill":96,"stroke":96,"style":97},[79,319,320],{"x":296,"y":313,"fill":76,"style":314,"textAnchor":103},"yes",[56,322],{"x":81,"y":323,"width":275,"height":276,"rx":60,"fill":75,"stroke":76,"style":301},"132.0",[79,325,327],{"x":279,"y":326,"fill":76,"style":87,"textAnchor":281},"151.5","Stored in module map",[56,329],{"x":285,"y":323,"width":286,"height":276,"rx":60,"fill":116,"stroke":117,"style":118},[79,331,332],{"x":289,"y":326,"fill":76,"style":314,"textAnchor":103},"no",[56,334],{"x":293,"y":323,"width":286,"height":276,"rx":60,"fill":96,"stroke":96,"style":97},[79,336,320],{"x":296,"y":326,"fill":76,"style":314,"textAnchor":103},[56,338],{"x":81,"y":339,"width":275,"height":276,"rx":60,"fill":75,"stroke":76,"style":301},"162.0",[79,341,343],{"x":279,"y":342,"fill":76,"style":87,"textAnchor":281},"181.5","Early parse \u002F compile",[56,345],{"x":285,"y":339,"width":286,"height":276,"rx":60,"fill":116,"stroke":117,"style":118},[79,347,332],{"x":289,"y":342,"fill":76,"style":314,"textAnchor":103},[56,349],{"x":293,"y":339,"width":286,"height":276,"rx":60,"fill":96,"stroke":96,"style":97},[79,351,352],{"x":296,"y":342,"fill":76,"style":314,"textAnchor":103},"may",[56,354],{"x":81,"y":355,"width":275,"height":276,"rx":60,"fill":75,"stroke":76,"style":301},"192.0",[79,357,359],{"x":279,"y":358,"fill":76,"style":87,"textAnchor":281},"211.5","Dependencies fetched",[56,361],{"x":285,"y":355,"width":286,"height":276,"rx":60,"fill":116,"stroke":117,"style":118},[79,363,332],{"x":289,"y":358,"fill":76,"style":314,"textAnchor":103},[56,365],{"x":293,"y":355,"width":286,"height":276,"rx":60,"fill":76,"stroke":76,"style":108},[79,367,368],{"x":296,"y":358,"fill":76,"style":314,"textAnchor":103},"only those listed",[186,370,372],{"id":371},"step-by-step-resolution","Step-by-Step Resolution",[374,375,377],"h3",{"id":376},"_1-preload-the-startup-graphs-modules","1. Preload the startup graph's modules",[379,380,385],"pre",{"className":381,"code":382,"language":383,"meta":384,"style":384},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Cscript type=\"module\" src=\"\u002Fassets\u002Fmain.3c1f.js\">\u003C\u002Fscript>\n\u003Clink rel=\"modulepreload\" href=\"\u002Fassets\u002Frouter.8a2b.js\">\n\u003Clink rel=\"modulepreload\" href=\"\u002Fassets\u002Flanding-view.51e0.js\">\n\u003C!-- trade-off: browsers may or may not fetch dependencies of a modulepreloaded\n     module automatically; list every module you need early rather than relying\n     on recursive preloading. -->\n","html","",[30,386,387,426,452,474,481,487],{"__ignoreMap":384},[388,389,391,395,399,403,406,410,413,415,418,421,423],"span",{"class":137,"line":390},1,[388,392,394],{"class":393},"saISM","\u003C",[388,396,398],{"class":397},"sZBmE","script",[388,400,402],{"class":401},"sPXB4"," type",[388,404,405],{"class":393},"=",[388,407,409],{"class":408},"sZ8jY","\"module\"",[388,411,412],{"class":401}," src",[388,414,405],{"class":393},[388,416,417],{"class":408},"\"\u002Fassets\u002Fmain.3c1f.js\"",[388,419,420],{"class":393},">\u003C\u002F",[388,422,398],{"class":397},[388,424,425],{"class":393},">\n",[388,427,429,431,434,437,439,442,445,447,450],{"class":137,"line":428},2,[388,430,394],{"class":393},[388,432,433],{"class":397},"link",[388,435,436],{"class":401}," rel",[388,438,405],{"class":393},[388,440,441],{"class":408},"\"modulepreload\"",[388,443,444],{"class":401}," href",[388,446,405],{"class":393},[388,448,449],{"class":408},"\"\u002Fassets\u002Frouter.8a2b.js\"",[388,451,425],{"class":393},[388,453,455,457,459,461,463,465,467,469,472],{"class":137,"line":454},3,[388,456,394],{"class":393},[388,458,433],{"class":397},[388,460,436],{"class":401},[388,462,405],{"class":393},[388,464,441],{"class":408},[388,466,444],{"class":401},[388,468,405],{"class":393},[388,470,471],{"class":408},"\"\u002Fassets\u002Flanding-view.51e0.js\"",[388,473,425],{"class":393},[388,475,477],{"class":137,"line":476},4,[388,478,480],{"class":479},"sjfSM","\u003C!-- trade-off: browsers may or may not fetch dependencies of a modulepreloaded\n",[388,482,484],{"class":137,"line":483},5,[388,485,486],{"class":479},"     module automatically; list every module you need early rather than relying\n",[388,488,490],{"class":137,"line":489},6,[388,491,492],{"class":479},"     on recursive preloading. -->\n",[15,494,495],{},"Expected outcome: all startup modules download in parallel with the entry.",[374,497,499],{"id":498},"_2-let-the-bundler-generate-hints","2. Let the bundler generate hints",[15,501,502,503,213],{},"Vite and other modern tools emit modulepreload links for static imports and preload dependencies of dynamic imports; tune them rather than hand-writing lists — see ",[19,504,506],{"href":505},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fcontrolling-vite-modulepreload-for-dynamic-imports\u002F","controlling Vite modulepreload for dynamic imports",[374,508,510],{"id":509},"_3-exclude-non-critical-modules","3. Exclude non-critical modules",[15,512,513],{},"Modules needed only after interaction (editors, charts, dialogs) should not be preloaded at startup; prefetch them on intent instead.",[374,515,517],{"id":516},"_4-add-crossorigin-for-cross-origin-modules","4. Add crossorigin for cross-origin modules",[15,519,520],{},"Module scripts are CORS requests; modulepreload uses CORS by default, but if modules come from another origin, that origin must send CORS headers, and credentials modes must match.",[15,522,523],{},[47,524,54,527,54,530,54,533,54,535,54,552,54,555,54,557,54,562,54,568,54,571,54,575,54,582,54,587,54,591,54,595,54,598,54,602,54,604,54,606,54,609,54,612,54,614,54,617,54,620,54,624,54,626,54,629,54,632,54,635,54,639,54,642,54,645,54],{"viewBox":525,"width":50,"role":51,"ariaLabel":526,"style":53},"0 0 760 325","Decision sequence for whether to add a modulepreload hint for a JavaScript module.",[56,528],{"className":529,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,531,532],{},"Should this module be preloaded?",[67,534,526],{},[536,537,538],"defs",{},[539,540,547],"marker",{"id":541,"viewBox":542,"refX":543,"refY":544,"markerWidth":545,"markerHeight":545,"orient":546},"fa9640bcc0","0 0 10 10","9","5","7","auto-start-reverse",[548,549],"path",{"d":550,"fill":76,"style":551},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[56,553],{"x":71,"y":71,"width":72,"height":554,"rx":74,"fill":75,"stroke":76,"style":77},"323",[79,556,532],{"x":81,"y":82,"fill":76,"style":83},[56,558],{"x":81,"y":92,"width":559,"height":560,"rx":561,"fill":76,"stroke":76,"style":108},"320.0","35.0","6",[79,563,567],{"x":564,"y":565,"fill":76,"style":566,"textAnchor":103},"188.0","78.0","font-size:13px;font-weight:700","Imported during startup (static graph of the entry)?",[56,569],{"x":570,"y":92,"width":559,"height":560,"rx":561,"fill":96,"stroke":96,"style":97},"412.0",[79,572,212],{"x":573,"y":565,"fill":76,"style":574,"textAnchor":103},"572.0","font-size:12.5px",[137,576],{"x1":577,"y1":101,"x2":578,"y2":101,"stroke":76,"strokeWidth":579,"style":580,"markerEnd":581},"348.0","410.0","1.5","stroke-opacity:0.6","url(#fa9640bcc0)",[79,583,320],{"x":584,"y":585,"fill":96,"style":586,"textAnchor":103},"380.0","66.5","font-size:11.5px;font-weight:700",[137,588],{"x1":564,"y1":589,"x2":564,"y2":590,"stroke":76,"strokeWidth":579,"style":580,"markerEnd":581},"91.0","125.0",[79,592,332],{"x":593,"y":594,"fill":147,"style":586},"196.0","113.0",[56,596],{"x":81,"y":597,"width":559,"height":560,"rx":561,"fill":76,"stroke":76,"style":108},"127.0",[79,599,601],{"x":564,"y":600,"fill":76,"style":566,"textAnchor":103},"149.0","Needed for the current route's first render?",[56,603],{"x":570,"y":597,"width":559,"height":560,"rx":561,"fill":96,"stroke":96,"style":97},[79,605,212],{"x":573,"y":600,"fill":76,"style":574,"textAnchor":103},[137,607],{"x1":577,"y1":608,"x2":578,"y2":608,"stroke":76,"strokeWidth":579,"style":580,"markerEnd":581},"144.5",[79,610,320],{"x":584,"y":611,"fill":96,"style":586,"textAnchor":103},"137.5",[137,613],{"x1":564,"y1":339,"x2":564,"y2":593,"stroke":76,"strokeWidth":579,"style":580,"markerEnd":581},[79,615,332],{"x":593,"y":616,"fill":147,"style":586},"184.0",[56,618],{"x":81,"y":619,"width":559,"height":560,"rx":561,"fill":76,"stroke":76,"style":108},"198.0",[79,621,623],{"x":564,"y":622,"fill":76,"style":566,"textAnchor":103},"220.0","Needed only after interaction?",[56,625],{"x":570,"y":619,"width":559,"height":560,"rx":561,"fill":116,"stroke":117,"style":118},[79,627,628],{"x":573,"y":622,"fill":76,"style":574,"textAnchor":103},"Prefetch on intent instead",[137,630],{"x1":577,"y1":631,"x2":578,"y2":631,"stroke":76,"strokeWidth":579,"style":580,"markerEnd":581},"215.5",[79,633,320],{"x":584,"y":634,"fill":96,"style":586,"textAnchor":103},"208.5",[137,636],{"x1":564,"y1":637,"x2":564,"y2":638,"stroke":76,"strokeWidth":579,"style":580,"markerEnd":581},"233.0","267.0",[79,640,332],{"x":593,"y":641,"fill":147,"style":586},"255.0",[56,643],{"x":81,"y":644,"width":559,"height":82,"rx":561,"fill":116,"stroke":117,"style":118},"269.0",[79,646,648],{"x":564,"y":647,"fill":76,"style":574,"textAnchor":103},"290.5","No hint",[186,650,652],{"id":651},"verification","Verification",[15,654,655],{},"Record a throttled trace: startup modules should start together shortly after HTML parsing begins, with no staircase. Each module should be fetched once. Compare time to first render (or LCP for client-rendered pages) before and after; flattening a three-level waterfall saves roughly two round trips — 200–400ms on mobile.",[186,657,659],{"id":658},"worked-example-an-unbundled-admin-app","Worked Example: An Unbundled Admin App",[15,661,662],{},"An internal admin app served native ES modules without a bundler. Its startup graph was five levels deep, and on a remote office's high-latency connection the app took 2.4 seconds to render. Generating modulepreload links for the 22 startup modules at deploy time (by walking the import graph) collapsed loading into one batch; render time dropped to 1.1 seconds. Lazy-loaded admin panels were left out of the list and prefetched when their navigation items were hovered.",[186,664,666],{"id":665},"generating-hints-from-the-import-graph","Generating Hints From the Import Graph",[15,668,669,670,673,674,677],{},"Hand-maintained preload lists drift as code changes. Generate them: bundlers do it from their manifests, and for unbundled sites a deploy step can parse each entry's imports recursively (with tools like ",[30,671,672],{},"es-module-lexer",") and emit the closure as link tags or HTTP ",[30,675,676],{},"Link"," headers. Recompute on every deploy and cache the result per entry. For server-rendered apps, emit per-route lists so each page preloads only the modules its route needs, rather than a global list covering every route.",[186,679,681],{"id":680},"measuring-the-waterfall-before-and-after","Measuring the Waterfall Before and After",[15,683,684,685,688],{},"Quantify the problem before adding hints. From Resource Timing, compute for each module request the gap between the page's navigation start and the request's ",[30,686,687],{},"startTime",", and group modules by import depth (entry = 0, its imports = 1, and so on). Without preloading, start times step up by roughly one round trip per level; with preloading, all levels start within a few milliseconds of each other. Sending the deepest level's start time as a RUM metric gives a simple field indicator: if it creeps up after a release, a new import chain was added outside the preloaded set. Pair it with the bundle analyser's import graph to find which module introduced the extra level.",[186,690,692],{"id":691},"common-mistakes","Common Mistakes",[191,694,695,704,710,716],{},[194,696,697,703],{},[197,698,699,700,702],{},"Using ",[30,701,42],{}," for modules."," The response may not be reused.",[194,705,706,709],{},[197,707,708],{},"Preloading lazy routes at startup."," Wastes bandwidth when users never visit them.",[194,711,712,715],{},[197,713,714],{},"Hand-maintained lists."," They go stale after refactors; generate them.",[194,717,718,721],{},[197,719,720],{},"Forgetting hashed filenames."," Hints must point at the deployed hashed URLs, not source paths.",[186,723,725],{"id":724},"edge-cases","Edge Cases",[15,727,728,731],{},[197,729,730],{},"Import maps."," Bare specifiers resolved through import maps can be preloaded using their resolved URLs; some browsers also resolve specifiers in modulepreload through the map.",[15,733,734,737],{},[197,735,736],{},"Service workers."," Preloaded modules go through the worker's fetch handler like other requests; cache-first strategies make repeat loads instant.",[15,739,740,743],{},[197,741,742],{},"Early Hints."," modulepreload can be sent in 103 responses, starting module downloads during server think time.",[15,745,746,749],{},[197,747,748],{},"Old browsers."," modulepreload is supported in current Chromium, Firefox and Safari; Vite ships a tiny polyfill for older engines if needed.",[186,751,753],{"id":752},"faq","FAQ",[755,756,759,763],"details",{"className":757},[758],"faq-item",[760,761,762],"summary",{},"Does modulepreload execute the module?",[15,764,765],{},"No. It fetches and may compile the module, but evaluation happens only when the module is imported. Side effects do not run early.",[755,767,769,772],{"className":768},[758],[760,770,771],{},"Is modulepreload useful with a bundler?",[15,773,774],{},"Yes — bundles still split into chunks that import each other. Bundlers emit modulepreload for those relationships; check the output rather than adding manual hints.",[755,776,778,781],{"className":777},[758],[760,779,780],{},"How many modulepreloads are too many?",[15,782,783],{},"When they delay critical resources. On mobile, more than about 10–15 startup chunks suggests the chunking is too fine; fix chunking rather than adding hints.",[755,785,787,790],{"className":786},[758],[760,788,789],{},"Can modulepreload be used for workers?",[15,791,792],{},"Module workers load their own graphs in the worker context; modulepreload in the document does not populate the worker's module map. Preloading worker modules has limited benefit.",[755,794,796,799],{"className":795},[758],[760,797,798],{},"Does modulepreload help INP?",[15,800,801],{},"Indirectly: earlier compilation can reduce work during startup, and faster startup means fewer long tasks overlapping early interactions.",[755,803,805,808],{"className":804},[758],[760,806,807],{},"What about prefetch for future routes?",[15,809,810,811,814],{},"Use ",[30,812,813],{},"rel=\"prefetch\""," (idle priority) for modules likely needed on the next navigation, and modulepreload for modules needed now.",[755,816,818,821],{"className":817},[758],[760,819,820],{},"Does modulepreload affect caching?",[15,822,823,824,827],{},"Preloaded modules are cached by the HTTP cache like any other request, under their own ",[30,825,826],{},"Cache-Control"," headers. With content-hashed filenames and immutable caching, repeat visits load them from cache and the hints cost nothing.",[755,829,831,834],{"className":830},[758],[760,832,833],{},"Should modulepreload hints go in the HTML or in Link headers?",[15,835,836,837,839],{},"Both work. HTML link elements are easiest to generate per page; HTTP ",[30,838,676],{}," headers can be sent earlier, including in 103 Early Hints, which lets module downloads start before the HTML is even ready.",[186,841,843],{"id":842},"related","Related",[191,845,846,853,860],{},[194,847,848,852],{},[19,849,851],{"href":850},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production\u002F","Using import maps for unbundled production"," — where modulepreload is essential.",[194,854,855,859],{},[19,856,858],{"href":857},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Ffixing-waterfalls-from-nested-dynamic-imports\u002F","Fixing waterfalls from nested dynamic imports"," — waterfalls in lazy code.",[194,861,862,866],{},[19,863,865],{"href":864},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fpreload-vs-preconnect-when-to-use-each\u002F","Preload vs preconnect: when to use each"," — the general hint decision.",[398,868,870],{"type":869},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Use modulepreload for ES Module Graphs\",\n  \"description\": \"How modulepreload differs from preload for JavaScript modules, how it removes discovery waterfalls, and how to avoid over-preloading.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Preload the startup graph's modules\",\n      \"text\": \"Expected outcome: all startup modules download in parallel with the entry.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Let the bundler generate hints\",\n      \"text\": \"Vite and other modern tools emit modulepreload links for static imports and preload dependencies of dynamic imports; tune them rather than hand-writing lists — see controlling Vite modulepreload for dynamic imports.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Exclude non-critical modules\",\n      \"text\": \"Modules needed only after interaction (editors, charts, dialogs) should not be preloaded at startup; prefetch them on intent instead.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Add crossorigin for cross-origin modules\",\n      \"text\": \"Module scripts are CORS requests; modulepreload uses CORS by default, but if modules come from another origin, that origin must send CORS headers, and credentials modes must match.\"\n    }\n  ]\n}\n",[398,872,873],{"type":869},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Use modulepreload for ES Module Graphs\",\n  \"description\": \"How modulepreload differs from preload for JavaScript modules, how it removes discovery waterfalls, and how to avoid over-preloading.\",\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\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs\u002F\"\n  }\n}\n",[398,875,876],{"type":869},"\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\": \"Advanced Caching Strategies & CDN Architecture\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Resource Hints & Early Hints\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Using modulepreload for ES Module Graphs\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs\u002F\"\n    }\n  ]\n}\n",[878,879,880],"style",{},"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 .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);}",{"title":384,"searchDepth":428,"depth":428,"links":882},[883,884,885,891,892,893,894,895,896,897,898],{"id":188,"depth":428,"text":189},{"id":228,"depth":428,"text":229},{"id":371,"depth":428,"text":372,"children":886},[887,888,889,890],{"id":376,"depth":454,"text":377},{"id":498,"depth":454,"text":499},{"id":509,"depth":454,"text":510},{"id":516,"depth":454,"text":517},{"id":651,"depth":428,"text":652},{"id":658,"depth":428,"text":659},{"id":665,"depth":428,"text":666},{"id":680,"depth":428,"text":681},{"id":691,"depth":428,"text":692},{"id":724,"depth":428,"text":725},{"id":752,"depth":428,"text":753},{"id":842,"depth":428,"text":843},"How modulepreload differs from preload for JavaScript modules, how it removes discovery waterfalls, and how to avoid over-preloading.","md",{"slug":902,"type":903,"breadcrumb":904,"datePublished":912,"dateModified":912},"using-modulepreload-for-es-module-graphs","article",[905,908,909,910],{"name":906,"url":907},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":911},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs\u002F","2026-10-06",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs",{"title":916,"description":917},"Using modulepreload to Flatten ES Module Waterfalls","modulepreload fetches and prepares ES modules before they are imported, flattening import waterfalls. When to use it instead of preload, and how much to preload.","advanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs\u002Findex","Wmv4orEZKxpzZyS_HAipxEv6KpGIm4aCGBJwHpW_XOs",[921,925],{"title":922,"path":923,"stem":924},"Using 103 Early Hints Behind a CDN","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-103-early-hints-behind-a-cdn","advanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-103-early-hints-behind-a-cdn\u002Findex",{"title":926,"path":927,"stem":928},"Service Worker Caching Strategies","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Findex",1791308074186]