[{"data":1,"prerenderedAt":1246},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals":1237},{"id":4,"title":5,"body":6,"description":1217,"extension":1218,"meta":1219,"navigation":1231,"path":1232,"seo":1233,"stem":1235,"__hash__":1236},"content\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals\u002Findex.md","Deriving Performance Budgets from Core Web Vitals",{"type":7,"value":8,"toc":1202},"minimark",[9,14,34,46,198,203,232,236,242,252,263,269,273,278,289,472,475,479,485,561,571,685,689,707,711,930,942,1037,1040,1093,1097,1100,1104,1111,1125,1128,1132,1135,1139,1151,1160,1164,1187,1192,1195,1198],[10,11,13],"h1",{"id":12},"how-to-derive-performance-budgets-from-core-web-vitals-thresholds","How to Derive Performance Budgets from Core Web Vitals Thresholds",[15,16,17,18,23,24,28,29,33],"p",{},"This guide turns the thresholds in ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002F","Understanding Core Web Vitals Thresholds"," into budgets a build can enforce, as part of ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement",". The problem it solves: field metrics are the goal, but they are lagging and noisy. By the time CrUX shows a regression, the change that caused it shipped weeks ago. Budgets on the ",[30,31,32],"em",{},"inputs"," — bytes, requests, main-thread time — catch regressions in the pull request that introduces them.",[15,35,36,37,41,42,45],{},"The difficulty is that thresholds are about time on users' devices, while budgets are about artefacts your build produces. Bridging them requires a model: for your mobile p75 conditions, how many bytes and how many milliseconds of work fit inside ",[38,39,40],"code",{},"2.5s"," LCP and ",[38,43,44],{},"200ms"," INP? The answer is template-specific, and deriving it is a one-day exercise that pays back on every release.",[15,47,48],{},[49,50,56,57,56,64,56,68,56,71,56,89,56,97,56,103,56,111,56,118,56,123,56,126,56,130,56,133,56,137,56,143,56,147,56,150,56,152,56,155,56,159,56,163,56,167,56,170,56,174,56,177,56,186,56,190,56,194,56],"svg",{"viewBox":51,"width":52,"role":53,"ariaLabel":54,"style":55},"0 0 760 150","100%","img","Five steps from the Core Web Vitals threshold through field conditions and a timing model to resource budgets and CI enforcement.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[58,59],"rect",{"className":60,"x":62,"y":62,"width":52,"height":52,"fill":63},[61],"svg-canvas","0","#ffffff",[65,66,67],"title",{},"From threshold to enforced budget",[69,70,54],"desc",{},[72,73,74],"defs",{},[75,76,83],"marker",{"id":77,"viewBox":78,"refX":79,"refY":80,"markerWidth":81,"markerHeight":81,"orient":82},"fae11b7418","0 0 10 10","9","5","7","auto-start-reverse",[84,85],"path",{"d":86,"fill":87,"style":88},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[58,90],{"x":91,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":87,"style":96},"1","758","148","10","none","stroke-opacity:0.18",[98,99,67],"text",{"x":100,"y":101,"fill":87,"style":102},"28.0","34.0","font-size:16px;font-weight:700",[58,104],{"x":100,"y":105,"width":106,"height":107,"rx":108,"fill":109,"stroke":109,"style":110},"56.0","108.8","72.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[98,112,117],{"x":113,"y":114,"fill":87,"style":115,"textAnchor":116},"82.4","78.0","font-size:13px;font-weight:700","middle","Threshold",[98,119,122],{"x":113,"y":120,"fill":87,"style":121,"textAnchor":116},"96.0","font-size:12px","LCP 2.5s at p75",[58,124],{"x":125,"y":105,"width":106,"height":107,"rx":108,"fill":109,"stroke":109,"style":110},"176.8",[98,127,129],{"x":128,"y":114,"fill":87,"style":115,"textAnchor":116},"231.2","Field conditions",[98,131,132],{"x":128,"y":120,"fill":87,"style":121,"textAnchor":116},"p75 RTT +",[98,134,136],{"x":128,"y":135,"fill":87,"style":121,"textAnchor":116},"112.0","bandwidth",[58,138],{"x":139,"y":105,"width":106,"height":107,"rx":108,"fill":140,"stroke":141,"style":142},"325.6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[98,144,146],{"x":145,"y":114,"fill":87,"style":115,"textAnchor":116},"380.0","Timing model",[98,148,149],{"x":145,"y":120,"fill":87,"style":121,"textAnchor":116},"phases of the",[98,151,84],{"x":145,"y":135,"fill":87,"style":121,"textAnchor":116},[58,153],{"x":154,"y":105,"width":106,"height":107,"rx":108,"fill":109,"stroke":109,"style":110},"474.4",[98,156,158],{"x":157,"y":114,"fill":87,"style":115,"textAnchor":116},"528.8","Resource",[98,160,162],{"x":157,"y":161,"fill":87,"style":115,"textAnchor":116},"95.0","budgets",[98,164,166],{"x":157,"y":165,"fill":87,"style":121,"textAnchor":116},"113.0","KB",[58,168],{"x":169,"y":105,"width":106,"height":107,"rx":108,"fill":109,"stroke":109,"style":110},"623.2",[98,171,173],{"x":172,"y":114,"fill":87,"style":115,"textAnchor":116},"677.6","CI gate",[98,175,176],{"x":172,"y":120,"fill":87,"style":121,"textAnchor":116},"fail the PR",[178,179],"line",{"x1":180,"y1":181,"x2":182,"y2":181,"stroke":87,"strokeWidth":183,"style":184,"markerEnd":185},"136.8","92.0","174.8","1.5","stroke-opacity:0.6","url(#fae11b7418)",[178,187],{"x1":188,"y1":181,"x2":189,"y2":181,"stroke":87,"strokeWidth":183,"style":184,"markerEnd":185},"285.6","323.6",[178,191],{"x1":192,"y1":181,"x2":193,"y2":181,"stroke":87,"strokeWidth":183,"style":184,"markerEnd":185},"434.4","472.4",[178,195],{"x1":196,"y1":181,"x2":197,"y2":181,"stroke":87,"strokeWidth":183,"style":184,"markerEnd":185},"583.2","621.2",[199,200,202],"h2",{"id":201},"rapid-diagnosis-do-you-need-derived-budgets","Rapid Diagnosis: Do You Need Derived Budgets?",[204,205,206,214,220,226],"ul",{},[207,208,209,213],"li",{},[210,211,212],"strong",{},"Regressions are found in CrUX, not in review."," If your team discovers performance regressions from the monthly report, you need input budgets.",[207,215,216,219],{},[210,217,218],{},"Budgets exist but nobody believes them."," A \"200KB JavaScript\" budget copied from a blog post, unconnected to your conditions, gets raised every time it fails.",[207,221,222,225],{},[210,223,224],{},"Templates differ widely."," A blog post and a product configurator cannot share one budget; derived budgets are per template.",[207,227,228,231],{},[210,229,230],{},"You cannot say which resource \"owns\" LCP time."," Without a timing model, you cannot argue whether 40KB more CSS is acceptable.",[199,233,235],{"id":234},"root-cause-analysis-why-arbitrary-budgets-fail","Root Cause Analysis: Why Arbitrary Budgets Fail",[15,237,238,241],{},[210,239,240],{},"1. They ignore your users' conditions."," 170KB of compressed JavaScript is fine for desktop users on fibre and fatal for low-end Android users on congested 4G. A budget without a target device and network is a guess.",[15,243,244,247,248,251],{},[210,245,246],{},"2. They budget the wrong resource."," Total page weight correlates poorly with LCP. What matters is the bytes and round trips ",[30,249,250],{},"on the critical path"," to the LCP element, plus main-thread time before it can paint.",[15,253,254,257,258,262],{},[210,255,256],{},"3. They treat compressed size as cost."," JavaScript's cost is mostly parse, compile and execute — proportional to uncompressed size and code complexity, not gzip size. ",[19,259,261],{"href":260},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets\u002Fwhy-compressed-size-is-the-wrong-budget\u002F","Why compressed size is the wrong budget"," explores this.",[15,264,265,268],{},[210,266,267],{},"4. They have no headroom."," A budget equal to the threshold leaves nothing for variance, third parties added later, or a slow day on the CDN.",[199,270,272],{"id":271},"step-by-step-derivation","Step-by-Step Derivation",[274,275,277],"h3",{"id":276},"_1-pin-down-p75-field-conditions","1. Pin down p75 field conditions",[15,279,280,281,284,285,288],{},"From your RUM, take the p75 round-trip time and effective downlink for mobile visits to the template (Network Information API ",[38,282,283],{},"rtt"," and ",[38,286,287],{},"downlink"," where available, or Navigation Timing deltas). Also take the device-class mix to choose a CPU throttling factor.",[290,291,296],"pre",{"className":292,"code":293,"language":294,"meta":295,"style":295},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Field conditions from RUM rows (Chromium only for rtt\u002Fdownlink).\nconst p = (arr, q) => arr.sort((a, b) => a - b)[Math.floor(arr.length * q)];\nconst rtt = p(rows.map((r) => r.rtt), 0.75);              \u002F\u002F e.g. 175 ms\nconst downlinkMbps = p(rows.map((r) => r.downlink), 0.25); \u002F\u002F slow quartile, e.g. 3.2 Mbps\n\u002F\u002F trade-off: navigator.connection values are coarse (rounded, capped) and\n\u002F\u002F Chromium-only. Cross-check with TTFB and resource timings from all browsers.\n","javascript","",[38,297,298,306,385,424,460,466],{"__ignoreMap":295},[299,300,302],"span",{"class":178,"line":301},1,[299,303,305],{"class":304},"sjfSM","\u002F\u002F Field conditions from RUM rows (Chromium only for rtt\u002Fdownlink).\n",[299,307,309,313,317,320,324,328,331,334,337,340,343,346,349,351,353,356,358,360,363,366,369,372,375,379,382],{"class":178,"line":308},2,[299,310,312],{"class":311},"sPARh","const",[299,314,316],{"class":315},"smZ65"," p",[299,318,319],{"class":311}," =",[299,321,323],{"class":322},"saISM"," (",[299,325,327],{"class":326},"sQw3B","arr",[299,329,330],{"class":322},", ",[299,332,333],{"class":326},"q",[299,335,336],{"class":322},") ",[299,338,339],{"class":311},"=>",[299,341,342],{"class":322}," arr.",[299,344,345],{"class":315},"sort",[299,347,348],{"class":322},"((",[299,350,19],{"class":326},[299,352,330],{"class":322},[299,354,355],{"class":326},"b",[299,357,336],{"class":322},[299,359,339],{"class":311},[299,361,362],{"class":322}," a ",[299,364,365],{"class":311},"-",[299,367,368],{"class":322}," b)[Math.",[299,370,371],{"class":315},"floor",[299,373,374],{"class":322},"(arr.",[299,376,378],{"class":377},"sPXB4","length",[299,380,381],{"class":311}," *",[299,383,384],{"class":322}," q)];\n",[299,386,388,390,393,395,397,400,403,405,408,410,412,415,418,421],{"class":178,"line":387},3,[299,389,312],{"class":311},[299,391,392],{"class":377}," rtt",[299,394,319],{"class":311},[299,396,316],{"class":315},[299,398,399],{"class":322},"(rows.",[299,401,402],{"class":315},"map",[299,404,348],{"class":322},[299,406,407],{"class":326},"r",[299,409,336],{"class":322},[299,411,339],{"class":311},[299,413,414],{"class":322}," r.rtt), ",[299,416,417],{"class":377},"0.75",[299,419,420],{"class":322},");              ",[299,422,423],{"class":304},"\u002F\u002F e.g. 175 ms\n",[299,425,427,429,432,434,436,438,440,442,444,446,448,451,454,457],{"class":178,"line":426},4,[299,428,312],{"class":311},[299,430,431],{"class":377}," downlinkMbps",[299,433,319],{"class":311},[299,435,316],{"class":315},[299,437,399],{"class":322},[299,439,402],{"class":315},[299,441,348],{"class":322},[299,443,407],{"class":326},[299,445,336],{"class":322},[299,447,339],{"class":311},[299,449,450],{"class":322}," r.downlink), ",[299,452,453],{"class":377},"0.25",[299,455,456],{"class":322},"); ",[299,458,459],{"class":304},"\u002F\u002F slow quartile, e.g. 3.2 Mbps\n",[299,461,463],{"class":178,"line":462},5,[299,464,465],{"class":304},"\u002F\u002F trade-off: navigator.connection values are coarse (rounded, capped) and\n",[299,467,469],{"class":178,"line":468},6,[299,470,471],{"class":304},"\u002F\u002F Chromium-only. Cross-check with TTFB and resource timings from all browsers.\n",[15,473,474],{},"Expected outcome: a concrete profile such as \"175ms RTT, 3.2Mbps, 4x CPU\" that becomes your lab throttling setting too.",[274,476,478],{"id":477},"_2-model-the-critical-path-to-lcp","2. Model the critical path to LCP",[15,480,481,482,484],{},"Count the round trips and bytes that must complete before the LCP element paints, and assign each phase a share of the ",[38,483,40],{}," budget minus headroom.",[486,487,488,504],"table",{},[489,490,491],"thead",{},[492,493,494,498,501],"tr",{},[495,496,497],"th",{},"Phase",[495,499,500],{},"Budget (of 2.1s target)",[495,502,503],{},"What it implies at 175ms RTT, 3.2Mbps",[505,506,507,519,530,541,551],"tbody",{},[492,508,509,513,516],{},[510,511,512],"td",{},"Connection + TTFB",[510,514,515],{},"600ms",[510,517,518],{},"≤ 3 RTT setup + server ≤ 75ms",[492,520,521,524,527],{},[510,522,523],{},"Render-blocking CSS",[510,525,526],{},"300ms",[510,528,529],{},"≤ 1 request, ≤ 40KB compressed",[492,531,532,535,538],{},[510,533,534],{},"LCP image load",[510,536,537],{},"700ms",[510,539,540],{},"≤ ~150KB at 3.2Mbps after contention",[492,542,543,546,548],{},[510,544,545],{},"JS before paint (if any)",[510,547,526],{},[510,549,550],{},"≤ ~100KB uncompressed on the path",[492,552,553,556,558],{},[510,554,555],{},"Render delay",[510,557,44],{},[510,559,560],{},"layout + paint on 4x CPU",[15,562,563,564,567,568,570],{},"The target is ",[38,565,566],{},"2.1s",", not ",[38,569,40],{},": roughly 15% headroom for variance.",[15,572,573],{},[49,574,56,577,56,580,56,583,56,585,56,588,56,590,56,597,56,601,56,608,56,614,56,618,56,622,56,626,56,630,56,633,56,637,56,641,56,645,56,650,56,655,56,659,56,663,56,667,56,670,56,673,56,680,56],{"viewBox":575,"width":52,"role":53,"ariaLabel":576,"style":55},"0 0 760 188","Timeline allocating a 2.1 second LCP target across connection, CSS, image load, script and render phases, with 2.5 second threshold marked.",[58,578],{"className":579,"x":62,"y":62,"width":52,"height":52,"fill":63},[61],[65,581,582],{},"Budgeted LCP critical path at p75 mobile conditions",[69,584,576],{},[58,586],{"x":91,"y":91,"width":92,"height":587,"rx":94,"fill":95,"stroke":87,"style":96},"186",[98,589,582],{"x":100,"y":101,"fill":87,"style":102},[178,591],{"x1":592,"y1":107,"x2":592,"y2":593,"stroke":141,"strokeWidth":183,"strokeDashArray":594,"style":596},"701.3","118.0",[595,595],"4","stroke-opacity:0.9",[98,598,600],{"x":100,"y":120,"fill":87,"style":599},"font-size:12.5px;font-weight:700","Budget",[58,602],{"x":603,"y":114,"width":604,"height":605,"rx":606,"fill":87,"stroke":87,"style":607},"85.0","146.6","26.0","3","fill-opacity:0.06;stroke-opacity:0.4",[98,609,613],{"x":610,"y":611,"fill":87,"style":612,"textAnchor":116},"158.3","95.5","font-size:11.5px","connect + TTFB",[58,615],{"x":616,"y":114,"width":617,"height":605,"rx":606,"fill":109,"stroke":109,"style":110},"233.1","72.5",[98,619,621],{"x":620,"y":611,"fill":87,"style":612,"textAnchor":116},"269.4","CSS",[58,623],{"x":624,"y":114,"width":625,"height":605,"rx":606,"fill":140,"stroke":141,"style":142},"307.2","171.3",[98,627,629],{"x":628,"y":611,"fill":87,"style":612,"textAnchor":116},"392.8","LCP image",[58,631],{"x":632,"y":114,"width":617,"height":605,"rx":606,"fill":109,"stroke":109,"style":110},"479.9",[98,634,636],{"x":635,"y":611,"fill":87,"style":612,"textAnchor":116},"516.2","JS",[58,638],{"x":639,"y":114,"width":640,"height":605,"rx":606,"fill":87,"stroke":87,"style":607},"554.0","47.9",[98,642,644],{"x":643,"y":611,"fill":87,"style":612,"textAnchor":116},"577.9","render",[178,646],{"x1":647,"y1":593,"x2":648,"y2":593,"stroke":87,"style":649},"84.3","726.0","stroke-opacity:0.4",[98,651,654],{"x":161,"y":652,"fill":653,"style":612,"textAnchor":116},"135.0","#51617a","0ms",[98,656,658],{"x":657,"y":652,"fill":653,"style":612,"textAnchor":116},"207.7","500ms",[98,660,662],{"x":661,"y":652,"fill":653,"style":612,"textAnchor":116},"331.1","1000ms",[98,664,666],{"x":665,"y":652,"fill":653,"style":612,"textAnchor":116},"454.5","1500ms",[98,668,669],{"x":643,"y":652,"fill":653,"style":612,"textAnchor":116},"2000ms",[98,671,672],{"x":592,"y":652,"fill":653,"style":612,"textAnchor":116},"2500ms",[98,674,679],{"x":675,"y":676,"fill":677,"style":678,"textAnchor":116},"693.4","68.0","#8a6608","font-size:12px;font-weight:700","2.5s threshold",[98,681,684],{"x":100,"y":682,"fill":653,"style":683},"166.0","font-size:12.5px","The 400ms gap before the threshold is headroom for variance and future third parties.",[274,686,688],{"id":687},"_3-derive-the-inp-and-cls-budgets","3. Derive the INP and CLS budgets",[15,690,691,692,695,696,699,700,702,703,706],{},"INP budgets are main-thread budgets: no task over ",[38,693,694],{},"50ms"," during the interaction window, handler processing under ",[38,697,698],{},"100ms"," on the throttled profile, and a cap on total blocking time during load (for example ",[38,701,526],{}," TBT at 4x CPU) so early interactions are not stuck behind startup work. CLS budgets are structural: every image and embed has dimensions, no late-inserted content above existing content, and a lab CLS of ",[38,704,705],{},"0.05"," or less for scripted loads.",[274,708,710],{"id":709},"_4-encode-budgets-per-template-and-enforce-them","4. Encode budgets per template and enforce them",[290,712,716],{"className":713,"code":714,"language":715,"meta":295,"style":295},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","[\n  {\n    \"path\": \"\u002Fproducts\u002F*\",\n    \"timings\": [{ \"metric\": \"largest-contentful-paint\", \"budget\": 2100 },\n                { \"metric\": \"total-blocking-time\", \"budget\": 300 },\n                { \"metric\": \"cumulative-layout-shift\", \"budget\": 0.05 }],\n    \"resourceSizes\": [{ \"resourceType\": \"script\", \"budget\": 170 },\n                      { \"resourceType\": \"stylesheet\", \"budget\": 40 },\n                      { \"resourceType\": \"image\", \"budget\": 300 }],\n    \"resourceCounts\": [{ \"resourceType\": \"third-party\", \"budget\": 8 }]\n  }\n]\n","json",[38,717,718,723,728,744,773,796,818,845,869,891,918,924],{"__ignoreMap":295},[299,719,720],{"class":178,"line":301},[299,721,722],{"class":322},"[\n",[299,724,725],{"class":178,"line":308},[299,726,727],{"class":322},"  {\n",[299,729,730,734,737,741],{"class":178,"line":387},[299,731,733],{"class":732},"sZBmE","    \"path\"",[299,735,736],{"class":322},": ",[299,738,740],{"class":739},"sZ8jY","\"\u002Fproducts\u002F*\"",[299,742,743],{"class":322},",\n",[299,745,746,749,752,755,757,760,762,765,767,770],{"class":178,"line":426},[299,747,748],{"class":732},"    \"timings\"",[299,750,751],{"class":322},": [{ ",[299,753,754],{"class":732},"\"metric\"",[299,756,736],{"class":322},[299,758,759],{"class":739},"\"largest-contentful-paint\"",[299,761,330],{"class":322},[299,763,764],{"class":732},"\"budget\"",[299,766,736],{"class":322},[299,768,769],{"class":377},"2100",[299,771,772],{"class":322}," },\n",[299,774,775,778,780,782,785,787,789,791,794],{"class":178,"line":462},[299,776,777],{"class":322},"                { ",[299,779,754],{"class":732},[299,781,736],{"class":322},[299,783,784],{"class":739},"\"total-blocking-time\"",[299,786,330],{"class":322},[299,788,764],{"class":732},[299,790,736],{"class":322},[299,792,793],{"class":377},"300",[299,795,772],{"class":322},[299,797,798,800,802,804,807,809,811,813,815],{"class":178,"line":468},[299,799,777],{"class":322},[299,801,754],{"class":732},[299,803,736],{"class":322},[299,805,806],{"class":739},"\"cumulative-layout-shift\"",[299,808,330],{"class":322},[299,810,764],{"class":732},[299,812,736],{"class":322},[299,814,705],{"class":377},[299,816,817],{"class":322}," }],\n",[299,819,821,824,826,829,831,834,836,838,840,843],{"class":178,"line":820},7,[299,822,823],{"class":732},"    \"resourceSizes\"",[299,825,751],{"class":322},[299,827,828],{"class":732},"\"resourceType\"",[299,830,736],{"class":322},[299,832,833],{"class":739},"\"script\"",[299,835,330],{"class":322},[299,837,764],{"class":732},[299,839,736],{"class":322},[299,841,842],{"class":377},"170",[299,844,772],{"class":322},[299,846,848,851,853,855,858,860,862,864,867],{"class":178,"line":847},8,[299,849,850],{"class":322},"                      { ",[299,852,828],{"class":732},[299,854,736],{"class":322},[299,856,857],{"class":739},"\"stylesheet\"",[299,859,330],{"class":322},[299,861,764],{"class":732},[299,863,736],{"class":322},[299,865,866],{"class":377},"40",[299,868,772],{"class":322},[299,870,872,874,876,878,881,883,885,887,889],{"class":178,"line":871},9,[299,873,850],{"class":322},[299,875,828],{"class":732},[299,877,736],{"class":322},[299,879,880],{"class":739},"\"image\"",[299,882,330],{"class":322},[299,884,764],{"class":732},[299,886,736],{"class":322},[299,888,793],{"class":377},[299,890,817],{"class":322},[299,892,894,897,899,901,903,906,908,910,912,915],{"class":178,"line":893},10,[299,895,896],{"class":732},"    \"resourceCounts\"",[299,898,751],{"class":322},[299,900,828],{"class":732},[299,902,736],{"class":322},[299,904,905],{"class":739},"\"third-party\"",[299,907,330],{"class":322},[299,909,764],{"class":732},[299,911,736],{"class":322},[299,913,914],{"class":377},"8",[299,916,917],{"class":322}," }]\n",[299,919,921],{"class":178,"line":920},11,[299,922,923],{"class":322},"  }\n",[299,925,927],{"class":178,"line":926},12,[299,928,929],{"class":322},"]\n",[15,931,932,933,936,937,941],{},"Use this ",[38,934,935],{},"budget.json"," with Lighthouse CI, and keep a separate bundle-size check (such as ",[19,938,940],{"href":939},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets\u002Fenforcing-bundle-budgets-with-size-limit\u002F","size-limit",") that measures uncompressed JavaScript per entry point, because Lighthouse's resource sizes are transfer sizes.",[290,943,945],{"className":292,"code":944,"language":294,"meta":295,"style":295},"\u002F\u002F lighthouserc.js — use the budget file on representative URLs per template.\nmodule.exports = { ci: {\n  collect: { url: ['http:\u002F\u002Flocalhost:4173\u002Fproducts\u002Fsample', 'http:\u002F\u002Flocalhost:4173\u002Fblog\u002Fsample'],\n             settings: { throttlingMethod: 'devtools', throttling: { rttMs: 175, throughputKbps: 3200, cpuSlowdownMultiplier: 4 } } },\n  assert: { budgetsFile: '.\u002Fbudget.json' },\n} };\n\u002F\u002F trade-off: devtools throttling is more realistic than simulated throttling\n\u002F\u002F but noisier; run 3-5 times and assert on the median to avoid flaky builds.\n",[38,946,947,952,968,984,1012,1022,1027,1032],{"__ignoreMap":295},[299,948,949],{"class":178,"line":301},[299,950,951],{"class":304},"\u002F\u002F lighthouserc.js — use the budget file on representative URLs per template.\n",[299,953,954,957,960,963,965],{"class":178,"line":308},[299,955,956],{"class":377},"module",[299,958,959],{"class":322},".",[299,961,962],{"class":377},"exports",[299,964,319],{"class":311},[299,966,967],{"class":322}," { ci: {\n",[299,969,970,973,976,978,981],{"class":178,"line":387},[299,971,972],{"class":322},"  collect: { url: [",[299,974,975],{"class":739},"'http:\u002F\u002Flocalhost:4173\u002Fproducts\u002Fsample'",[299,977,330],{"class":322},[299,979,980],{"class":739},"'http:\u002F\u002Flocalhost:4173\u002Fblog\u002Fsample'",[299,982,983],{"class":322},"],\n",[299,985,986,989,992,995,998,1001,1004,1007,1009],{"class":178,"line":426},[299,987,988],{"class":322},"             settings: { throttlingMethod: ",[299,990,991],{"class":739},"'devtools'",[299,993,994],{"class":322},", throttling: { rttMs: ",[299,996,997],{"class":377},"175",[299,999,1000],{"class":322},", throughputKbps: ",[299,1002,1003],{"class":377},"3200",[299,1005,1006],{"class":322},", cpuSlowdownMultiplier: ",[299,1008,595],{"class":377},[299,1010,1011],{"class":322}," } } },\n",[299,1013,1014,1017,1020],{"class":178,"line":462},[299,1015,1016],{"class":322},"  assert: { budgetsFile: ",[299,1018,1019],{"class":739},"'.\u002Fbudget.json'",[299,1021,772],{"class":322},[299,1023,1024],{"class":178,"line":468},[299,1025,1026],{"class":322},"} };\n",[299,1028,1029],{"class":178,"line":820},[299,1030,1031],{"class":304},"\u002F\u002F trade-off: devtools throttling is more realistic than simulated throttling\n",[299,1033,1034],{"class":178,"line":847},[299,1035,1036],{"class":304},"\u002F\u002F but noisier; run 3-5 times and assert on the median to avoid flaky builds.\n",[15,1038,1039],{},"Expected outcome: a pull request that adds 60KB of script to the product template fails before merge, with a message tied to a specific, defensible number.",[15,1041,1042],{},[49,1043,56,1046,56,1049,56,1052,56,1054,56,1057,56,1059,56,1063,56,1068,56,1073,56,1076,56,1080,56,1083,56,1086,56,1090,56],{"viewBox":1044,"width":52,"role":53,"ariaLabel":1045,"style":55},"0 0 760 218","Layered view of bundle-size checks, lab Lighthouse budgets and field RUM targets, from fastest to slowest feedback.",[58,1047],{"className":1048,"x":62,"y":62,"width":52,"height":52,"fill":63},[61],[65,1050,1051],{},"Three layers of budget, three speeds of feedback",[69,1053,1045],{},[58,1055],{"x":91,"y":91,"width":92,"height":1056,"rx":94,"fill":95,"stroke":87,"style":96},"216",[98,1058,1051],{"x":100,"y":101,"fill":87,"style":102},[58,1060],{"x":100,"y":105,"width":1061,"height":1062,"rx":108,"fill":109,"stroke":109,"style":110},"704.0","40.0",[98,1064,1067],{"x":1065,"y":1066,"fill":87,"style":115},"42.0","80.5","Bundle size check",[98,1069,1072],{"x":1070,"y":1066,"fill":87,"style":121,"textAnchor":1071},"200.8","start","every commit, seconds — uncompressed JS per entry point",[58,1074],{"x":100,"y":1075,"width":1061,"height":1062,"rx":108,"fill":109,"stroke":109,"style":110},"106.0",[98,1077,1079],{"x":1065,"y":1078,"fill":87,"style":115},"130.5","Lab Lighthouse budget",[98,1081,1082],{"x":1070,"y":1078,"fill":87,"style":121,"textAnchor":1071},"every PR, minutes — timings and resource sizes per template",[58,1084],{"x":100,"y":1085,"width":1061,"height":1062,"rx":108,"fill":140,"stroke":141,"style":142},"156.0",[98,1087,1089],{"x":1065,"y":1088,"fill":87,"style":115},"180.5","Field RUM target",[98,1091,1092],{"x":1070,"y":1088,"fill":87,"style":121,"textAnchor":1071},"daily, p75 per template — the number that actually ships",[199,1094,1096],{"id":1095},"verification","Verification",[15,1098,1099],{},"Validate the model before trusting it: take a page that currently passes in the field and one that fails, and check that the lab budget run passes the first and fails the second. If both pass, your lab conditions are too generous — tighten throttling until the lab separates them. Revisit the derivation quarterly, or whenever the device mix in your RUM shifts noticeably.",[199,1101,1103],{"id":1102},"worked-example-a-product-template","Worked Example: A Product Template",[15,1105,1106,1107,1110],{},"A retailer's product template fails LCP on mobile at ",[38,1108,1109],{},"2.9s"," p75. RUM gives p75 mobile conditions of 160ms RTT and 4Mbps effective downlink, and the device mix suggests 4x CPU throttling. The critical path today is: HTML (TTFB 480ms), two blocking stylesheets totalling 95KB compressed, a web font for the product title, 240KB of JavaScript before the gallery component renders the hero, and a 210KB hero image.",[15,1112,1113,1114,1116,1117,1120,1121,1124],{},"Working backwards from a ",[38,1115,566],{}," target: connection and TTFB keep 600ms. The two stylesheets at 4Mbps after contention take roughly 350ms — over the 300ms allocation, so the budget becomes \"one stylesheet, ≤ 40KB, critical CSS inlined\". The JavaScript must go: a gallery that renders the hero only after 240KB of script cannot meet any reasonable budget, so the hero ",[38,1118,1119],{},"\u003Cimg>"," moves into server HTML and the gallery enhances it afterwards. That frees the 300ms JS allocation entirely. The 210KB image at 4Mbps is ~420ms of transfer plus contention; AVIF at the right ",[38,1122,1123],{},"sizes"," brings it to 90KB, comfortably inside the 700ms image allocation.",[15,1126,1127],{},"The resulting budget file for the template reads: stylesheet ≤ 40KB, script on the LCP path 0KB, LCP image ≤ 120KB, third-party requests before LCP 0. None of these numbers came from a blog post; each can be defended by pointing at the timing model, and each failed check points at a specific phase of LCP.",[199,1129,1131],{"id":1130},"keeping-budgets-credible-over-time","Keeping Budgets Credible Over Time",[15,1133,1134],{},"Budgets lose authority when they are raised casually. Require that any budget increase comes with a field justification — evidence that the template still has headroom at p75 — and an owner. Track \"budget headroom remaining\" per template as a visible number; when headroom approaches zero, new features must pay for themselves by removing weight elsewhere. That conversation is the real value of a budget: it makes performance a design constraint rather than a cleanup task.",[199,1136,1138],{"id":1137},"faq","FAQ",[1140,1141,1144,1148],"details",{"className":1142},[1143],"faq-item",[1145,1146,1147],"summary",{},"Should budgets be per page or per template?",[15,1149,1150],{},"Per template. Pages built from the same template share code, CSS and structure, so their budgets and their fixes are the same. Per-page budgets multiply maintenance without adding information, except for a few unusual pages that deserve their own entry.",[1140,1152,1154,1157],{"className":1153},[1143],[1145,1155,1156],{},"How much headroom is enough?",[15,1158,1159],{},"Start with 15–20% below each threshold for timing budgets. Increase it for templates with heavy third-party content, which varies more, and reduce it only if your RUM shows the template is consistently well inside the threshold at p75.",[199,1161,1163],{"id":1162},"related","Related",[204,1165,1166,1173,1180],{},[207,1167,1168,1172],{},[19,1169,1171],{"href":1170},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets\u002F","JavaScript performance budgets"," — enforcing the script side of the budget.",[207,1174,1175,1179],{},[19,1176,1178],{"href":1177},"\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fbest-lighthouse-ci-setup-for-frontend-pipelines\u002F","Best Lighthouse CI setup for frontend pipelines"," — running the lab gate reliably.",[207,1181,1182,1186],{},[19,1183,1185],{"href":1184},"\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fpassing-desktop-but-failing-mobile-core-web-vitals\u002F","Passing desktop but failing mobile Core Web Vitals"," — choosing the conditions your budget models.",[1188,1189,1191],"script",{"type":1190},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Derive Performance Budgets from Core Web Vitals Thresholds\",\n  \"description\": \"Work backwards from the Core Web Vitals thresholds to resource budgets your team can check in every pull request.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Pin down p75 field conditions\",\n      \"text\": \"From your RUM, take the p75 round-trip time and effective downlink for mobile visits to the template (Network Information API rtt and downlink where available, or Navigation Timing deltas).\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Model the critical path to LCP\",\n      \"text\": \"Count the round trips and bytes that must complete before the LCP element paints, and assign each phase a share of the 2.5s budget minus headroom.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Derive the INP and CLS budgets\",\n      \"text\": \"INP budgets are main-thread budgets: no task over 50ms during the interaction window, handler processing under 100ms on the throttled profile, and a cap on total blocking time during load (for example 300ms TBT at 4x CPU) so early interactions are not stuck behind startup work.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Encode budgets per template and enforce them\",\n      \"text\": \"Use this budget.json with Lighthouse CI, and keep a separate bundle-size check (such as size-limit) that measures uncompressed JavaScript per entry point, because Lighthouse's resource sizes are transfer sizes.\"\n    }\n  ]\n}\n",[1188,1193,1194],{"type":1190},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Derive Performance Budgets from Core Web Vitals Thresholds\",\n  \"description\": \"Work backwards from the Core Web Vitals thresholds to resource budgets your team can check in every pull request.\",\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\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals\u002F\"\n  }\n}\n",[1188,1196,1197],{"type":1190},"\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\": \"Core Web Vitals & Measurement\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Understanding Core Web Vitals Thresholds\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Deriving Performance Budgets from Core Web Vitals\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals\u002F\"\n    }\n  ]\n}\n",[1199,1200,1201],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}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 .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}",{"title":295,"searchDepth":308,"depth":308,"links":1203},[1204,1205,1206,1212,1213,1214,1215,1216],{"id":201,"depth":308,"text":202},{"id":234,"depth":308,"text":235},{"id":271,"depth":308,"text":272,"children":1207},[1208,1209,1210,1211],{"id":276,"depth":387,"text":277},{"id":477,"depth":387,"text":478},{"id":687,"depth":387,"text":688},{"id":709,"depth":387,"text":710},{"id":1095,"depth":308,"text":1096},{"id":1102,"depth":308,"text":1103},{"id":1130,"depth":308,"text":1131},{"id":1137,"depth":308,"text":1138},{"id":1162,"depth":308,"text":1163},"Work backwards from the Core Web Vitals thresholds to resource budgets your team can check in every pull request.","md",{"slug":1220,"type":1221,"breadcrumb":1222,"datePublished":1230,"dateModified":1230},"deriving-performance-budgets-from-core-web-vitals","article",[1223,1226,1227,1228],{"name":1224,"url":1225},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1229},"\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals\u002F","2026-10-06",true,"\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals",{"title":5,"description":1234},"Translate LCP 2.5s, INP 200ms and CLS 0.1 into concrete byte, request and main-thread budgets per template, then enforce them in CI.","core-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fderiving-performance-budgets-from-core-web-vitals\u002Findex","-94NJMqSLOhailGzrHEEOrdOzpzre9omTu24cpmX5BQ",[1238,1242],{"title":1239,"path":1240,"stem":1241,"children":-1},"CrUX URL-Level vs Origin-Level Data","\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fcrux-url-level-vs-origin-level-data","core-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fcrux-url-level-vs-origin-level-data\u002Findex",{"title":1243,"path":1244,"stem":1245,"children":-1},"Passing Desktop but Failing Mobile Core Web Vitals","\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fpassing-desktop-but-failing-mobile-core-web-vitals","core-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fpassing-desktop-but-failing-mobile-core-web-vitals\u002Findex",1791308070295]