[{"data":1,"prerenderedAt":2304},["ShallowReactive",2],{"page:\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fsubsetting-variable-fonts-for-faster-first-render":3,"all-docs-nav":2059},{"id":4,"title":5,"body":6,"breadcrumb":2034,"dateModified":2043,"datePublished":2043,"description":2044,"extension":2045,"faq":2046,"meta":2053,"navigation":118,"path":2054,"seo":2055,"slug":12,"stem":2056,"type":2057,"__hash__":2058},"content\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fsubsetting-variable-fonts-for-faster-first-render\u002Findex.md","Subsetting Variable Fonts for Faster First Render",{"type":7,"value":8,"toc":2015},"minimark",[9,13,17,26,31,53,57,60,551,554,688,692,818,833,844,847,912,920,924,1254,1257,1381,1385,1392,1588,1594,1597,1600,1604,1607,1691,1777,1792,1796,1799,1858,1861,1865,1917,1921,1926,1930,1935,1938,1942,1945,1949,1952,1956,1959,1963,1971,1975,2011],[10,11,5],"h1",{"id":12},"subsetting-variable-fonts-for-faster-first-render",[14,15,16],"p",{},"A variable font is already an efficiency win: one file replaces five static weights. It is also usually much larger than it needs to be, because it ships every glyph in Latin Extended plus axis data for variations your design never uses. A typical 128 KB variable face carries perhaps 25 KB of glyphs your site actually renders.",[14,18,19,20,25],{},"Subsetting removes the rest. This guide derives the character set from your real content, generates the subset during the build, pins unused axes, and measures the effect on first render. It sits under ",[21,22,24],"a",{"href":23},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002F","Font Loading Strategies for Static Sites",".",[27,28,30],"h2",{"id":29},"prerequisites","Prerequisites",[32,33,34,38,50],"ul",{},[35,36,37],"li",{},"A self-hosted variable font — you cannot subset a file served from someone else's stylesheet.",[35,39,40,41,45,46,49],{},"Python with ",[42,43,44],"code",{},"fonttools"," (",[42,47,48],{},"pip install \"fonttools[woff]\" brotli","), or a Node equivalent, available to the build.",[35,51,52],{},"The built HTML, so the character set can be derived from what you actually publish.",[27,54,56],{"id":55},"derive-the-character-set-from-the-content","Derive the Character Set From the Content",[14,58,59],{},"Guessing the character set is how a site ends up rendering an em dash as a fallback glyph in one paragraph. Extract it from the build output instead:",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F scripts\u002Ffont-charset.mjs — writes the unique code points the site renders\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { globSync } from 'node:fs';\n\nconst chars = new Set();\nfor (const file of globSync('dist\u002F**\u002F*.html')) {\n  const text = readFileSync(file, 'utf8')\n    .replace(\u002F\u003Cscript[\\s\\S]*?\u003C\\\u002Fscript>\u002Fg, ' ')\n    .replace(\u002F\u003Cstyle[\\s\\S]*?\u003C\\\u002Fstyle>\u002Fg, ' ')\n    .replace(\u002F\u003C[^>]+>\u002Fg, ' ');\n  for (const ch of text) chars.add(ch);\n}\n\u002F\u002F Safety margin: characters an author may add tomorrow\nfor (const ch of '€£¥©®™°±×÷–—…‘’“”«»†‡§¶←→↑↓⌘⏎✓✗│─└├') chars.add(ch);\n\nconst codepoints = [...chars]\n  .filter((c) => c.codePointAt(0) > 31)\n  .map((c) => 'U+' + c.codePointAt(0).toString(16).toUpperCase())\n  .sort();\nwriteFileSync('build\u002Fcharset.txt', codepoints.join(','));\nconsole.log(`${codepoints.length} code points in use`);\n","javascript","",[42,68,69,78,99,113,120,143,171,194,241,276,315,339,345,351,374,379,398,440,489,499,524],{"__ignoreMap":66},[70,71,74],"span",{"class":72,"line":73},"line",1,[70,75,77],{"class":76},"sJ8bj","\u002F\u002F scripts\u002Ffont-charset.mjs — writes the unique code points the site renders\n",[70,79,81,85,89,92,96],{"class":72,"line":80},2,[70,82,84],{"class":83},"szBVR","import",[70,86,88],{"class":87},"sVt8B"," { readFileSync, writeFileSync } ",[70,90,91],{"class":83},"from",[70,93,95],{"class":94},"sZZnC"," 'node:fs'",[70,97,98],{"class":87},";\n",[70,100,102,104,107,109,111],{"class":72,"line":101},3,[70,103,84],{"class":83},[70,105,106],{"class":87}," { globSync } ",[70,108,91],{"class":83},[70,110,95],{"class":94},[70,112,98],{"class":87},[70,114,116],{"class":72,"line":115},4,[70,117,119],{"emptyLinePlaceholder":118},true,"\n",[70,121,123,126,130,133,136,140],{"class":72,"line":122},5,[70,124,125],{"class":83},"const",[70,127,129],{"class":128},"sj4cs"," chars",[70,131,132],{"class":83}," =",[70,134,135],{"class":83}," new",[70,137,139],{"class":138},"sScJk"," Set",[70,141,142],{"class":87},"();\n",[70,144,146,149,151,153,156,159,162,165,168],{"class":72,"line":145},6,[70,147,148],{"class":83},"for",[70,150,45],{"class":87},[70,152,125],{"class":83},[70,154,155],{"class":128}," file",[70,157,158],{"class":83}," of",[70,160,161],{"class":138}," globSync",[70,163,164],{"class":87},"(",[70,166,167],{"class":94},"'dist\u002F**\u002F*.html'",[70,169,170],{"class":87},")) {\n",[70,172,174,177,180,182,185,188,191],{"class":72,"line":173},7,[70,175,176],{"class":83},"  const",[70,178,179],{"class":128}," text",[70,181,132],{"class":83},[70,183,184],{"class":138}," readFileSync",[70,186,187],{"class":87},"(file, ",[70,189,190],{"class":94},"'utf8'",[70,192,193],{"class":87},")\n",[70,195,197,200,203,205,208,212,215,218,221,225,228,230,233,236,239],{"class":72,"line":196},8,[70,198,199],{"class":87},"    .",[70,201,202],{"class":138},"replace",[70,204,164],{"class":87},[70,206,207],{"class":94},"\u002F",[70,209,211],{"class":210},"sA_wV","\u003Cscript",[70,213,214],{"class":128},"[\\s\\S]",[70,216,217],{"class":83},"*?",[70,219,220],{"class":210},"\u003C",[70,222,224],{"class":223},"snhLl","\\\u002F",[70,226,227],{"class":210},"script>",[70,229,207],{"class":94},[70,231,232],{"class":83},"g",[70,234,235],{"class":87},", ",[70,237,238],{"class":94},"' '",[70,240,193],{"class":87},[70,242,244,246,248,250,252,255,257,259,261,263,266,268,270,272,274],{"class":72,"line":243},9,[70,245,199],{"class":87},[70,247,202],{"class":138},[70,249,164],{"class":87},[70,251,207],{"class":94},[70,253,254],{"class":210},"\u003Cstyle",[70,256,214],{"class":128},[70,258,217],{"class":83},[70,260,220],{"class":210},[70,262,224],{"class":223},[70,264,265],{"class":210},"style>",[70,267,207],{"class":94},[70,269,232],{"class":83},[70,271,235],{"class":87},[70,273,238],{"class":94},[70,275,193],{"class":87},[70,277,279,281,283,285,287,289,292,295,298,301,304,306,308,310,312],{"class":72,"line":278},10,[70,280,199],{"class":87},[70,282,202],{"class":138},[70,284,164],{"class":87},[70,286,207],{"class":94},[70,288,220],{"class":210},[70,290,291],{"class":128},"[",[70,293,294],{"class":83},"^",[70,296,297],{"class":128},">]",[70,299,300],{"class":83},"+",[70,302,303],{"class":210},">",[70,305,207],{"class":94},[70,307,232],{"class":83},[70,309,235],{"class":87},[70,311,238],{"class":94},[70,313,314],{"class":87},");\n",[70,316,318,321,323,325,328,330,333,336],{"class":72,"line":317},11,[70,319,320],{"class":83},"  for",[70,322,45],{"class":87},[70,324,125],{"class":83},[70,326,327],{"class":128}," ch",[70,329,158],{"class":83},[70,331,332],{"class":87}," text) chars.",[70,334,335],{"class":138},"add",[70,337,338],{"class":87},"(ch);\n",[70,340,342],{"class":72,"line":341},12,[70,343,344],{"class":87},"}\n",[70,346,348],{"class":72,"line":347},13,[70,349,350],{"class":76},"\u002F\u002F Safety margin: characters an author may add tomorrow\n",[70,352,354,356,358,360,362,364,367,370,372],{"class":72,"line":353},14,[70,355,148],{"class":83},[70,357,45],{"class":87},[70,359,125],{"class":83},[70,361,327],{"class":128},[70,363,158],{"class":83},[70,365,366],{"class":94}," '€£¥©®™°±×÷–—…‘’“”«»†‡§¶←→↑↓⌘⏎✓✗│─└├'",[70,368,369],{"class":87},") chars.",[70,371,335],{"class":138},[70,373,338],{"class":87},[70,375,377],{"class":72,"line":376},15,[70,378,119],{"emptyLinePlaceholder":118},[70,380,382,384,387,389,392,395],{"class":72,"line":381},16,[70,383,125],{"class":83},[70,385,386],{"class":128}," codepoints",[70,388,132],{"class":83},[70,390,391],{"class":87}," [",[70,393,394],{"class":83},"...",[70,396,397],{"class":87},"chars]\n",[70,399,401,404,407,410,414,417,420,423,426,428,431,433,435,438],{"class":72,"line":400},17,[70,402,403],{"class":87},"  .",[70,405,406],{"class":138},"filter",[70,408,409],{"class":87},"((",[70,411,413],{"class":412},"s4XuR","c",[70,415,416],{"class":87},") ",[70,418,419],{"class":83},"=>",[70,421,422],{"class":87}," c.",[70,424,425],{"class":138},"codePointAt",[70,427,164],{"class":87},[70,429,430],{"class":128},"0",[70,432,416],{"class":87},[70,434,303],{"class":83},[70,436,437],{"class":128}," 31",[70,439,193],{"class":87},[70,441,443,445,448,450,452,454,456,459,462,464,466,468,470,473,476,478,481,483,486],{"class":72,"line":442},18,[70,444,403],{"class":87},[70,446,447],{"class":138},"map",[70,449,409],{"class":87},[70,451,413],{"class":412},[70,453,416],{"class":87},[70,455,419],{"class":83},[70,457,458],{"class":94}," 'U+'",[70,460,461],{"class":83}," +",[70,463,422],{"class":87},[70,465,425],{"class":138},[70,467,164],{"class":87},[70,469,430],{"class":128},[70,471,472],{"class":87},").",[70,474,475],{"class":138},"toString",[70,477,164],{"class":87},[70,479,480],{"class":128},"16",[70,482,472],{"class":87},[70,484,485],{"class":138},"toUpperCase",[70,487,488],{"class":87},"())\n",[70,490,492,494,497],{"class":72,"line":491},19,[70,493,403],{"class":87},[70,495,496],{"class":138},"sort",[70,498,142],{"class":87},[70,500,502,505,507,510,513,516,518,521],{"class":72,"line":501},20,[70,503,504],{"class":138},"writeFileSync",[70,506,164],{"class":87},[70,508,509],{"class":94},"'build\u002Fcharset.txt'",[70,511,512],{"class":87},", codepoints.",[70,514,515],{"class":138},"join",[70,517,164],{"class":87},[70,519,520],{"class":94},"','",[70,522,523],{"class":87},"));\n",[70,525,527,530,533,535,538,541,543,546,549],{"class":72,"line":526},21,[70,528,529],{"class":87},"console.",[70,531,532],{"class":138},"log",[70,534,164],{"class":87},[70,536,537],{"class":94},"`${",[70,539,540],{"class":87},"codepoints",[70,542,25],{"class":94},[70,544,545],{"class":128},"length",[70,547,548],{"class":94},"} code points in use`",[70,550,314],{"class":87},[14,552,553],{},"The safety margin matters more than it looks. A site that publishes weekly will introduce a character the subset does not contain within a month, and the symptom — one glyph in a different typeface — is subtle enough to survive review.",[555,556,557,684],"figure",{},[558,559,566,567,566,571,566,575,566,581],"svg",{"viewBox":560,"role":561,"ariaLabelledBy":562,"xmlns":565},"0 0 760 300","img",[563,564],"fs-where-title","fs-where-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[568,569,570],"title",{"id":563},"Where the bytes go in a variable font",[572,573,574],"desc",{"id":564},"A breakdown of a 128 kilobyte variable font: 31 kilobytes are glyphs the site renders, 58 kilobytes are Latin Extended and Cyrillic glyphs it never uses, 24 kilobytes are variation data for unused axes, and 15 kilobytes are hinting and metadata. Subsetting and axis pinning remove 97 kilobytes.",[576,577],"rect",{"x":430,"y":430,"width":578,"height":579,"fill":580},"760","300","#ffffff",[232,582,584,585,584,593,584,602,584,608,584,613,584,620,584,624,584,627,584,633,584,637,584,640,584,646,584,650,584,653,584,658,584,662,584,666,584,670,584,675,584,680,566],{"style":583},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[586,587,592],"text",{"x":588,"y":589,"fill":590,"style":591},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","128 KB shipped, 31 KB used",[576,594],{"x":595,"y":595,"width":596,"height":595,"rx":597,"fill":598,"opacity":599,"stroke":600,"style":601},"60","160","8","#8ac926","0.2","#5a8a16","stroke-width:1.5px",[586,603,607],{"x":604,"y":605,"fill":590,"style":606},"140","86","font-size:12px;font-weight:700;text-anchor:middle","glyphs in use",[586,609,612],{"x":604,"y":610,"fill":590,"style":611},"106","font-size:13px;font-weight:700;text-anchor:middle","31 KB",[576,614],{"x":615,"y":595,"width":616,"height":595,"rx":597,"fill":617,"opacity":618,"stroke":619,"style":601},"228","230","#ff595e","0.14","#d83b41",[586,621,623],{"x":622,"y":605,"fill":590,"style":606},"343","unused glyph ranges",[586,625,626],{"x":622,"y":610,"fill":590,"style":611},"58 KB",[576,628],{"x":629,"y":595,"width":604,"height":595,"rx":597,"fill":630,"opacity":631,"stroke":632,"style":601},"466","#ffca3a","0.3","#a97b00",[586,634,636],{"x":635,"y":605,"fill":590,"style":606},"536","unused axes",[586,638,639],{"x":635,"y":610,"fill":590,"style":611},"24 KB",[576,641],{"x":642,"y":595,"width":605,"height":595,"rx":597,"fill":643,"opacity":644,"stroke":645,"style":601},"614","#d9e2ef","0.55","#556071",[586,647,649],{"x":648,"y":605,"fill":590,"style":606},"657","metadata",[586,651,652],{"x":648,"y":610,"fill":645,"style":606},"15 KB",[586,654,657],{"x":595,"y":655,"fill":590,"style":656},"164","font-size:13px;font-weight:700","After subsetting and pinning",[576,659],{"x":595,"y":660,"width":596,"height":661,"rx":597,"fill":598,"opacity":599,"stroke":600,"style":601},"180","46",[586,663,665],{"x":604,"y":664,"fill":600,"style":611},"208","31 KB total",[576,667],{"x":615,"y":660,"width":668,"height":661,"rx":597,"fill":643,"opacity":669,"stroke":645,"style":601},"472","0.35",[586,671,674],{"x":672,"y":664,"fill":645,"style":673},"464","font-size:12px;text-anchor:middle","97 KB removed — never sent, never parsed, never decoded",[586,676,679],{"x":595,"y":677,"fill":645,"style":678},"266","font-size:11px","Measured with fonttools on a 900-page English-language documentation site",[586,681,683],{"x":595,"y":682,"fill":645,"style":678},"288","The unused-axis slice assumes weight is the only axis the design varies",[685,686,687],"figcaption",{},"Most of a variable font is coverage for languages and design variations a given site never uses. Neither is free: both are downloaded and both are parsed before the first glyph paints.",[27,689,691],{"id":690},"subset-and-pin-in-the-build","Subset and Pin in the Build",[61,693,697],{"className":694,"code":695,"language":696,"meta":66,"style":66},"language-bash shiki shiki-themes github-light github-dark","#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\nCHARSET=$(cat build\u002Fcharset.txt)\n\npyftsubset fonts\u002Fsrc\u002Finter-var.ttf \\\n  --unicodes=\"$CHARSET\" \\\n  --layout-features='kern,liga,calt,tnum' \\\n  --variations='wght=400:700' \\\n  --flavor=woff2 \\\n  --desubroutinize \\\n  --output-file=dist\u002Ffonts\u002Finter-subset.woff2\n\nls -l dist\u002Ffonts\u002Finter-subset.woff2\n","bash",[42,698,699,704,715,734,738,749,764,774,784,791,798,803,807],{"__ignoreMap":66},[70,700,701],{"class":72,"line":73},[70,702,703],{"class":76},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[70,705,706,709,712],{"class":72,"line":80},[70,707,708],{"class":128},"set",[70,710,711],{"class":128}," -euo",[70,713,714],{"class":94}," pipefail\n",[70,716,717,720,723,726,729,732],{"class":72,"line":101},[70,718,719],{"class":87},"CHARSET",[70,721,722],{"class":83},"=",[70,724,725],{"class":87},"$(",[70,727,728],{"class":138},"cat",[70,730,731],{"class":94}," build\u002Fcharset.txt",[70,733,193],{"class":87},[70,735,736],{"class":72,"line":115},[70,737,119],{"emptyLinePlaceholder":118},[70,739,740,743,746],{"class":72,"line":122},[70,741,742],{"class":138},"pyftsubset",[70,744,745],{"class":94}," fonts\u002Fsrc\u002Finter-var.ttf",[70,747,748],{"class":128}," \\\n",[70,750,751,754,757,760,762],{"class":72,"line":145},[70,752,753],{"class":128},"  --unicodes=",[70,755,756],{"class":94},"\"",[70,758,759],{"class":87},"$CHARSET",[70,761,756],{"class":94},[70,763,748],{"class":128},[70,765,766,769,772],{"class":72,"line":173},[70,767,768],{"class":128},"  --layout-features=",[70,770,771],{"class":94},"'kern,liga,calt,tnum'",[70,773,748],{"class":128},[70,775,776,779,782],{"class":72,"line":196},[70,777,778],{"class":128},"  --variations=",[70,780,781],{"class":94},"'wght=400:700'",[70,783,748],{"class":128},[70,785,786,789],{"class":72,"line":243},[70,787,788],{"class":128},"  --flavor=woff2",[70,790,748],{"class":128},[70,792,793,796],{"class":72,"line":278},[70,794,795],{"class":128},"  --desubroutinize",[70,797,748],{"class":128},[70,799,800],{"class":72,"line":317},[70,801,802],{"class":128},"  --output-file=dist\u002Ffonts\u002Finter-subset.woff2\n",[70,804,805],{"class":72,"line":341},[70,806,119],{"emptyLinePlaceholder":118},[70,808,809,812,815],{"class":72,"line":347},[70,810,811],{"class":138},"ls",[70,813,814],{"class":128}," -l",[70,816,817],{"class":94}," dist\u002Ffonts\u002Finter-subset.woff2\n",[14,819,820,821,824,825,828,829,832],{},"Three flags do the work. ",[42,822,823],{},"--unicodes"," restricts the glyph set. ",[42,826,827],{},"--layout-features"," keeps only the OpenType features you use — kerning, standard ligatures, contextual alternates and tabular numerals for tables — and drops the rest. ",[42,830,831],{},"--variations"," pins the weight axis to the 400-700 range and removes every other axis's interpolation data.",[14,834,835,836,839,840,843],{},"Be careful with features. Dropping ",[42,837,838],{},"calt"," breaks contextual alternates that some faces rely on for basic letterforms; dropping ",[42,841,842],{},"tnum"," makes numbers in tables jitter between rows. Keep the four above unless you have checked what the face uses.",[14,845,846],{},"Run it as a build step so the subset always matches current content:",[61,848,852],{"className":849,"code":850,"language":851,"meta":66,"style":66},"language-json shiki shiki-themes github-light github-dark","{\n  \"scripts\": {\n    \"prebuild\": \"node scripts\u002Ffont-charset.mjs || true\",\n    \"build\": \"astro build && bash scripts\u002Fsubset-fonts.sh\",\n    \"postbuild\": \"node scripts\u002Fcheck-font-coverage.mjs\"\n  }\n}\n","json",[42,853,854,859,867,881,893,903,908],{"__ignoreMap":66},[70,855,856],{"class":72,"line":73},[70,857,858],{"class":87},"{\n",[70,860,861,864],{"class":72,"line":80},[70,862,863],{"class":128},"  \"scripts\"",[70,865,866],{"class":87},": {\n",[70,868,869,872,875,878],{"class":72,"line":101},[70,870,871],{"class":128},"    \"prebuild\"",[70,873,874],{"class":87},": ",[70,876,877],{"class":94},"\"node scripts\u002Ffont-charset.mjs || true\"",[70,879,880],{"class":87},",\n",[70,882,883,886,888,891],{"class":72,"line":115},[70,884,885],{"class":128},"    \"build\"",[70,887,874],{"class":87},[70,889,890],{"class":94},"\"astro build && bash scripts\u002Fsubset-fonts.sh\"",[70,892,880],{"class":87},[70,894,895,898,900],{"class":72,"line":122},[70,896,897],{"class":128},"    \"postbuild\"",[70,899,874],{"class":87},[70,901,902],{"class":94},"\"node scripts\u002Fcheck-font-coverage.mjs\"\n",[70,904,905],{"class":72,"line":145},[70,906,907],{"class":87},"  }\n",[70,909,910],{"class":72,"line":173},[70,911,344],{"class":87},[14,913,914,915,919],{},"The order is deliberate: the charset script reads the ",[916,917,918],"em",{},"previous"," build's HTML on a cold run, then the post-build coverage check catches anything new the current build introduced.",[27,921,923],{"id":922},"verify-coverage-before-shipping","Verify Coverage Before Shipping",[61,925,927],{"className":63,"code":926,"language":65,"meta":66,"style":66},"\u002F\u002F scripts\u002Fcheck-font-coverage.mjs — fails the build on a missing glyph\nimport { readFileSync } from 'node:fs';\nimport { globSync } from 'node:fs';\nimport * as fontkit from 'fontkit';\n\nconst font = fontkit.openSync('dist\u002Ffonts\u002Finter-subset.woff2');\nconst missing = new Set();\n\nfor (const file of globSync('dist\u002F**\u002F*.html')) {\n  const text = readFileSync(file, 'utf8').replace(\u002F\u003C[^>]+>\u002Fg, ' ');\n  for (const ch of text) {\n    const cp = ch.codePointAt(0);\n    if (cp > 31 && !font.hasGlyphForCodePoint(cp)) missing.add(ch);\n  }\n}\n\nif (missing.size) {\n  console.error(`font subset is missing ${missing.size} character(s): ${[...missing].join(' ')}`);\n  process.exit(1);\n}\nconsole.log('font coverage: OK');\n",[42,928,929,934,947,959,979,983,1005,1020,1024,1044,1088,1103,1124,1155,1159,1163,1167,1175,1222,1237,1241],{"__ignoreMap":66},[70,930,931],{"class":72,"line":73},[70,932,933],{"class":76},"\u002F\u002F scripts\u002Fcheck-font-coverage.mjs — fails the build on a missing glyph\n",[70,935,936,938,941,943,945],{"class":72,"line":80},[70,937,84],{"class":83},[70,939,940],{"class":87}," { readFileSync } ",[70,942,91],{"class":83},[70,944,95],{"class":94},[70,946,98],{"class":87},[70,948,949,951,953,955,957],{"class":72,"line":101},[70,950,84],{"class":83},[70,952,106],{"class":87},[70,954,91],{"class":83},[70,956,95],{"class":94},[70,958,98],{"class":87},[70,960,961,963,966,969,972,974,977],{"class":72,"line":115},[70,962,84],{"class":83},[70,964,965],{"class":128}," *",[70,967,968],{"class":83}," as",[70,970,971],{"class":87}," fontkit ",[70,973,91],{"class":83},[70,975,976],{"class":94}," 'fontkit'",[70,978,98],{"class":87},[70,980,981],{"class":72,"line":122},[70,982,119],{"emptyLinePlaceholder":118},[70,984,985,987,990,992,995,998,1000,1003],{"class":72,"line":145},[70,986,125],{"class":83},[70,988,989],{"class":128}," font",[70,991,132],{"class":83},[70,993,994],{"class":87}," fontkit.",[70,996,997],{"class":138},"openSync",[70,999,164],{"class":87},[70,1001,1002],{"class":94},"'dist\u002Ffonts\u002Finter-subset.woff2'",[70,1004,314],{"class":87},[70,1006,1007,1009,1012,1014,1016,1018],{"class":72,"line":173},[70,1008,125],{"class":83},[70,1010,1011],{"class":128}," missing",[70,1013,132],{"class":83},[70,1015,135],{"class":83},[70,1017,139],{"class":138},[70,1019,142],{"class":87},[70,1021,1022],{"class":72,"line":196},[70,1023,119],{"emptyLinePlaceholder":118},[70,1025,1026,1028,1030,1032,1034,1036,1038,1040,1042],{"class":72,"line":243},[70,1027,148],{"class":83},[70,1029,45],{"class":87},[70,1031,125],{"class":83},[70,1033,155],{"class":128},[70,1035,158],{"class":83},[70,1037,161],{"class":138},[70,1039,164],{"class":87},[70,1041,167],{"class":94},[70,1043,170],{"class":87},[70,1045,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086],{"class":72,"line":278},[70,1047,176],{"class":83},[70,1049,179],{"class":128},[70,1051,132],{"class":83},[70,1053,184],{"class":138},[70,1055,187],{"class":87},[70,1057,190],{"class":94},[70,1059,472],{"class":87},[70,1061,202],{"class":138},[70,1063,164],{"class":87},[70,1065,207],{"class":94},[70,1067,220],{"class":210},[70,1069,291],{"class":128},[70,1071,294],{"class":83},[70,1073,297],{"class":128},[70,1075,300],{"class":83},[70,1077,303],{"class":210},[70,1079,207],{"class":94},[70,1081,232],{"class":83},[70,1083,235],{"class":87},[70,1085,238],{"class":94},[70,1087,314],{"class":87},[70,1089,1090,1092,1094,1096,1098,1100],{"class":72,"line":317},[70,1091,320],{"class":83},[70,1093,45],{"class":87},[70,1095,125],{"class":83},[70,1097,327],{"class":128},[70,1099,158],{"class":83},[70,1101,1102],{"class":87}," text) {\n",[70,1104,1105,1108,1111,1113,1116,1118,1120,1122],{"class":72,"line":341},[70,1106,1107],{"class":83},"    const",[70,1109,1110],{"class":128}," cp",[70,1112,132],{"class":83},[70,1114,1115],{"class":87}," ch.",[70,1117,425],{"class":138},[70,1119,164],{"class":87},[70,1121,430],{"class":128},[70,1123,314],{"class":87},[70,1125,1126,1129,1132,1134,1136,1139,1142,1145,1148,1151,1153],{"class":72,"line":347},[70,1127,1128],{"class":83},"    if",[70,1130,1131],{"class":87}," (cp ",[70,1133,303],{"class":83},[70,1135,437],{"class":128},[70,1137,1138],{"class":83}," &&",[70,1140,1141],{"class":83}," !",[70,1143,1144],{"class":87},"font.",[70,1146,1147],{"class":138},"hasGlyphForCodePoint",[70,1149,1150],{"class":87},"(cp)) missing.",[70,1152,335],{"class":138},[70,1154,338],{"class":87},[70,1156,1157],{"class":72,"line":353},[70,1158,907],{"class":87},[70,1160,1161],{"class":72,"line":376},[70,1162,344],{"class":87},[70,1164,1165],{"class":72,"line":381},[70,1166,119],{"emptyLinePlaceholder":118},[70,1168,1169,1172],{"class":72,"line":400},[70,1170,1171],{"class":83},"if",[70,1173,1174],{"class":87}," (missing.size) {\n",[70,1176,1177,1180,1183,1185,1188,1191,1193,1196,1199,1201,1203,1205,1208,1210,1212,1214,1217,1220],{"class":72,"line":442},[70,1178,1179],{"class":87},"  console.",[70,1181,1182],{"class":138},"error",[70,1184,164],{"class":87},[70,1186,1187],{"class":94},"`font subset is missing ${",[70,1189,1190],{"class":87},"missing",[70,1192,25],{"class":94},[70,1194,1195],{"class":87},"size",[70,1197,1198],{"class":94},"} character(s): ${",[70,1200,291],{"class":94},[70,1202,394],{"class":83},[70,1204,1190],{"class":87},[70,1206,1207],{"class":94},"].",[70,1209,515],{"class":138},[70,1211,164],{"class":94},[70,1213,238],{"class":94},[70,1215,1216],{"class":94},")",[70,1218,1219],{"class":94},"}`",[70,1221,314],{"class":87},[70,1223,1224,1227,1230,1232,1235],{"class":72,"line":491},[70,1225,1226],{"class":87},"  process.",[70,1228,1229],{"class":138},"exit",[70,1231,164],{"class":87},[70,1233,1234],{"class":128},"1",[70,1236,314],{"class":87},[70,1238,1239],{"class":72,"line":501},[70,1240,344],{"class":87},[70,1242,1243,1245,1247,1249,1252],{"class":72,"line":526},[70,1244,529],{"class":87},[70,1246,532],{"class":138},[70,1248,164],{"class":87},[70,1250,1251],{"class":94},"'font coverage: OK'",[70,1253,314],{"class":87},[14,1255,1256],{},"This check is the difference between subsetting being safe and being a slow-burning source of typographic bugs. It costs about a second and it fails the build with the exact characters that are missing.",[555,1258,1259,1378],{},[558,1260,566,1265,566,1268,566,1271,566,1274,566,1364],{"viewBox":1261,"role":561,"ariaLabelledBy":1262,"xmlns":565},"0 0 760 280",[1263,1264],"fs-pipeline-title","fs-pipeline-desc",[568,1266,1267],{"id":1263},"Font subsetting as a build pipeline",[572,1269,1270],{"id":1264},"A pipeline: the built HTML feeds a charset extractor, which feeds pyftsubset along with the original font file, producing a subset WOFF2. A coverage check then compares the subset against the HTML and fails the build if any character is missing.",[576,1272],{"x":430,"y":430,"width":578,"height":1273,"fill":580},"280",[232,1275,584,1276,584,1279,584,1285,584,1290,584,1295,584,1299,584,1303,584,1306,584,1311,584,1314,584,1317,584,1322,584,1326,584,1328,584,1333,584,1338,584,1342,566],{"style":583},[586,1277,1278],{"x":588,"y":589,"fill":590,"style":591},"Derived from content, verified against content",[576,1280],{"x":1281,"y":1282,"width":604,"height":1283,"rx":1284,"fill":643,"opacity":644,"stroke":645,"style":601},"30","70","56","10",[586,1286,1289],{"x":1287,"y":1288,"fill":590,"style":606},"100","96","built HTML",[586,1291,1294],{"x":1287,"y":1292,"fill":645,"style":1293},"114","font-size:11px;text-anchor:middle","every page",[576,1296],{"x":1297,"y":1282,"width":604,"height":1283,"rx":1284,"fill":1298,"opacity":618,"stroke":1298,"style":601},"200","#1982c4",[586,1300,1302],{"x":1301,"y":1288,"fill":590,"style":606},"270","charset",[586,1304,1305],{"x":1301,"y":1292,"fill":645,"style":1293},"+ safety margin",[576,1307],{"x":1308,"y":1282,"width":1309,"height":1283,"rx":1284,"fill":1310,"opacity":618,"stroke":1310,"style":601},"370","150","#6a4c93",[586,1312,742],{"x":1313,"y":1288,"fill":590,"style":606},"445",[586,1315,1316],{"x":1313,"y":1292,"fill":645,"style":1293},"+ axis pinning",[576,1318],{"x":1319,"y":1282,"width":1320,"height":1283,"rx":1284,"fill":598,"opacity":1321,"stroke":600,"style":601},"550","170","0.18",[586,1323,1325],{"x":1324,"y":1288,"fill":590,"style":606},"635","subset.woff2",[586,1327,612],{"x":1324,"y":1292,"fill":600,"style":1293},[576,1329],{"x":1308,"y":1330,"width":1331,"height":1283,"rx":1284,"fill":630,"opacity":1332,"stroke":632,"style":601},"168","350","0.28",[586,1334,1337],{"x":1335,"y":1336,"fill":590,"style":606},"545","194","coverage check — fails the build on a missing glyph",[586,1339,1341],{"x":1335,"y":1340,"fill":645,"style":1293},"212","compares the subset back against the same HTML",[232,1343,1346,1347,1346,1352,1346,1355,1346,1358,1346,1361,584],{"stroke":645,"fill":1344,"style":1345},"none","stroke-width:2px","\n      ",[1348,1349],"path",{"d":1350,"style":1351},"M172 98 L198 98","marker-end:url(#fs-arrow)",[1348,1353],{"d":1354,"style":1351},"M342 98 L368 98",[1348,1356],{"d":1357,"style":1351},"M522 98 L548 98",[1348,1359],{"d":1360,"style":1351},"M635 128 L590 166",[1348,1362],{"d":1363,"style":1351},"M100 128 L380 190",[1365,1366,584,1367,566],"defs",{},[1368,1369,1346,1375,584],"marker",{"id":1370,"viewBox":1371,"refX":597,"refY":1372,"markerWidth":1373,"markerHeight":1373,"orient":1374},"fs-arrow","0 0 10 10","5","7","auto-start-reverse",[1348,1376],{"d":1377,"fill":645},"M0 0 L10 5 L0 10 z",[685,1379,1380],{},"The loop closes on itself: the same HTML that produced the character set is what verifies the subset covers it, so a new character in a new article fails the build rather than the page.",[27,1382,1384],{"id":1383},"splitting-into-unicode-ranges","Splitting Into Unicode Ranges",[14,1386,1387,1388,1391],{},"Subsetting to one file is right for a single-language site. When a site publishes in several scripts, a single subset is either huge or incomplete, and the better pattern is several subsets selected by ",[42,1389,1390],{},"unicode-range"," so browsers download only what a given page needs.",[61,1393,1397],{"className":1394,"code":1395,"language":1396,"meta":66,"style":66},"language-css shiki shiki-themes github-light github-dark","@font-face {\n  font-family: 'Inter';\n  src: url('\u002Ffonts\u002Finter-latin.woff2') format('woff2');\n  unicode-range: U+0000-00FF, U+2018-201F, U+2020-2027, U+20AC;\n  font-weight: 100 900;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('\u002Ffonts\u002Finter-latin-ext.woff2') format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF;\n  font-weight: 100 900;\n  font-display: swap;\n}\n","css",[42,1398,1399,1407,1419,1446,1473,1487,1499,1503,1507,1513,1523,1546,1562,1574,1584],{"__ignoreMap":66},[70,1400,1401,1404],{"class":72,"line":73},[70,1402,1403],{"class":83},"@font-face",[70,1405,1406],{"class":87}," {\n",[70,1408,1409,1412,1414,1417],{"class":72,"line":80},[70,1410,1411],{"class":128},"  font-family",[70,1413,874],{"class":87},[70,1415,1416],{"class":94},"'Inter'",[70,1418,98],{"class":87},[70,1420,1421,1424,1426,1429,1431,1434,1436,1439,1441,1444],{"class":72,"line":101},[70,1422,1423],{"class":128},"  src",[70,1425,874],{"class":87},[70,1427,1428],{"class":128},"url",[70,1430,164],{"class":87},[70,1432,1433],{"class":94},"'\u002Ffonts\u002Finter-latin.woff2'",[70,1435,416],{"class":87},[70,1437,1438],{"class":128},"format",[70,1440,164],{"class":87},[70,1442,1443],{"class":94},"'woff2'",[70,1445,314],{"class":87},[70,1447,1448,1451,1453,1456,1458,1461,1463,1466,1468,1471],{"class":72,"line":115},[70,1449,1450],{"class":128},"  unicode-range",[70,1452,874],{"class":87},[70,1454,1455],{"class":128},"U+0000-00FF",[70,1457,235],{"class":87},[70,1459,1460],{"class":128},"U+2018-201F",[70,1462,235],{"class":87},[70,1464,1465],{"class":128},"U+2020-2027",[70,1467,235],{"class":87},[70,1469,1470],{"class":128},"U+20AC",[70,1472,98],{"class":87},[70,1474,1475,1478,1480,1482,1485],{"class":72,"line":122},[70,1476,1477],{"class":128},"  font-weight",[70,1479,874],{"class":87},[70,1481,1287],{"class":128},[70,1483,1484],{"class":128}," 900",[70,1486,98],{"class":87},[70,1488,1489,1492,1494,1497],{"class":72,"line":145},[70,1490,1491],{"class":128},"  font-display",[70,1493,874],{"class":87},[70,1495,1496],{"class":128},"swap",[70,1498,98],{"class":87},[70,1500,1501],{"class":72,"line":173},[70,1502,344],{"class":87},[70,1504,1505],{"class":72,"line":196},[70,1506,119],{"emptyLinePlaceholder":118},[70,1508,1509,1511],{"class":72,"line":243},[70,1510,1403],{"class":83},[70,1512,1406],{"class":87},[70,1514,1515,1517,1519,1521],{"class":72,"line":278},[70,1516,1411],{"class":128},[70,1518,874],{"class":87},[70,1520,1416],{"class":94},[70,1522,98],{"class":87},[70,1524,1525,1527,1529,1531,1533,1536,1538,1540,1542,1544],{"class":72,"line":317},[70,1526,1423],{"class":128},[70,1528,874],{"class":87},[70,1530,1428],{"class":128},[70,1532,164],{"class":87},[70,1534,1535],{"class":94},"'\u002Ffonts\u002Finter-latin-ext.woff2'",[70,1537,416],{"class":87},[70,1539,1438],{"class":128},[70,1541,164],{"class":87},[70,1543,1443],{"class":94},[70,1545,314],{"class":87},[70,1547,1548,1550,1552,1555,1557,1560],{"class":72,"line":341},[70,1549,1450],{"class":128},[70,1551,874],{"class":87},[70,1553,1554],{"class":128},"U+0100-024F",[70,1556,235],{"class":87},[70,1558,1559],{"class":128},"U+1E00-1EFF",[70,1561,98],{"class":87},[70,1563,1564,1566,1568,1570,1572],{"class":72,"line":347},[70,1565,1477],{"class":128},[70,1567,874],{"class":87},[70,1569,1287],{"class":128},[70,1571,1484],{"class":128},[70,1573,98],{"class":87},[70,1575,1576,1578,1580,1582],{"class":72,"line":353},[70,1577,1491],{"class":128},[70,1579,874],{"class":87},[70,1581,1496],{"class":128},[70,1583,98],{"class":87},[70,1585,1586],{"class":72,"line":376},[70,1587,344],{"class":87},[14,1589,1590,1591,1593],{},"The browser parses ",[42,1592,1390],{}," before requesting anything and fetches only the files whose ranges the page actually uses. An English article downloads 31 KB; a Polish one downloads that plus a 9 KB extended file. Nobody downloads Cyrillic unless they read a Cyrillic page.",[14,1595,1596],{},"Two cautions. Ranges must not overlap, or a browser may fetch both files for the same character. And a page mixing scripts — an English article quoting a Greek term — will fetch two files, which is correct but worth knowing when a single page's font weight looks anomalous in a report.",[14,1598,1599],{},"For a site that is genuinely single-language, skip this. One file is simpler, and the second request's connection cost can outweigh the bytes saved on a short page.",[27,1601,1603],{"id":1602},"measured-impact","Measured Impact",[14,1605,1606],{},"A 900-page English documentation site, one variable face, Chrome 4× CPU throttle on a fast 3G profile, median of five runs:",[1608,1609,1610,1629],"table",{},[1611,1612,1613],"thead",{},[1614,1615,1616,1620,1623,1626],"tr",{},[1617,1618,1619],"th",{},"Configuration",[1617,1621,1622],{},"Font transfer",[1617,1624,1625],{},"First render of styled text",[1617,1627,1628],{},"LCP",[1630,1631,1632,1647,1661,1678],"tbody",{},[1614,1633,1634,1638,1641,1644],{},[1635,1636,1637],"td",{},"Full variable font, self-hosted",[1635,1639,1640],{},"128 KB",[1635,1642,1643],{},"1.42 s",[1635,1645,1646],{},"2.30 s",[1614,1648,1649,1652,1655,1658],{},[1635,1650,1651],{},"Subset to used glyphs",[1635,1653,1654],{},"47 KB",[1635,1656,1657],{},"0.94 s",[1635,1659,1660],{},"2.02 s",[1614,1662,1663,1670,1672,1675],{},[1635,1664,1665,1666,1669],{},"Subset + axis pinning (",[42,1667,1668],{},"wght"," only)",[1635,1671,612],{},[1635,1673,1674],{},"0.78 s",[1635,1676,1677],{},"1.94 s",[1614,1679,1680,1683,1685,1688],{},[1635,1681,1682],{},"Subset + pinning + preload",[1635,1684,612],{},[1635,1686,1687],{},"0.61 s",[1635,1689,1690],{},"1.88 s",[555,1692,1693,1774],{},[558,1694,566,1698,566,1701,566,1704,566,1706],{"viewBox":1261,"role":561,"ariaLabelledBy":1695,"xmlns":565},[1696,1697],"fs-bars-title","fs-bars-desc",[568,1699,1700],{"id":1696},"Font transfer size and time to styled text",[572,1702,1703],{"id":1697},"A horizontal bar chart on a shared scale. The full font transfers 128 kilobytes and styled text appears at 1.42 seconds. Subsetting drops it to 47 kilobytes and 0.94 seconds. Adding axis pinning gives 31 kilobytes and 0.78 seconds. Adding a preload gives 0.61 seconds at the same size.",[576,1705],{"x":430,"y":430,"width":578,"height":1273,"fill":580},[232,1707,584,1708,584,1712,584,1715,584,1720,584,1724,584,1729,584,1733,584,1738,584,1743,584,1746,584,1750,584,1754,584,1757,584,1761,584,1764,584,1767,584,1770,566],{"style":583},[586,1709,1711],{"x":588,"y":1710,"fill":590,"style":591},"26","Bytes removed are milliseconds removed",[72,1713],{"x1":1301,"y1":1714,"x2":1301,"y2":1340,"stroke":643,"style":601},"44",[586,1716,1719],{"x":595,"y":1717,"fill":590,"style":1718},"76","font-size:12px;font-weight:700","Full font",[576,1721],{"x":1301,"y":595,"width":1722,"height":589,"rx":1372,"fill":617,"opacity":1723,"stroke":619,"style":601},"420","0.16",[586,1725,1728],{"x":1726,"y":1727,"fill":619,"style":606},"480","79","128 KB · 1.42 s",[586,1730,1732],{"x":595,"y":1731,"fill":590,"style":1718},"126","Subset",[576,1734],{"x":1301,"y":1735,"width":1736,"height":589,"rx":1372,"fill":630,"opacity":1737,"stroke":632,"style":601},"110","154","0.34",[586,1739,1742],{"x":1740,"y":1741,"fill":590,"style":606},"347","129","47 KB · 0.94 s",[586,1744,1316],{"x":595,"y":1745,"fill":590,"style":1718},"176",[576,1747],{"x":1301,"y":596,"width":1748,"height":589,"rx":1372,"fill":598,"opacity":1749,"stroke":600,"style":601},"102","0.24",[586,1751,612],{"x":1752,"y":1753,"fill":590,"style":606},"321","179",[586,1755,1674],{"x":1756,"y":1753,"fill":600,"style":1718},"386",[586,1758,1760],{"x":595,"y":1759,"fill":590,"style":1718},"226","+ preload",[576,1762],{"x":1301,"y":1763,"width":1748,"height":589,"rx":1372,"fill":598,"opacity":631,"stroke":600,"style":601},"210",[586,1765,612],{"x":1752,"y":1766,"fill":590,"style":606},"229",[586,1768,1769],{"x":1756,"y":1766,"fill":600,"style":1718},"0.61 s · earliest styled paint",[586,1771,1773],{"x":1772,"y":677,"fill":645,"style":678},"213","Shared linear scale on transfer size · Chrome 4× CPU throttle, fast 3G, median of 5 runs",[685,1775,1776],{},"Axis pinning is the step teams skip. It removed a further 16 KB here for no visual change at all, because the design only ever used the weight axis.",[14,1778,1779,1780,1783,1784,1787,1788,25],{},"Subsetting does not change the font's metrics, so any ",[42,1781,1782],{},"size-adjust"," and ",[42,1785,1786],{},"ascent-override"," values calculated for layout stability remain valid — the two techniques compose cleanly, as covered in ",[21,1789,1791],{"href":1790},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Feliminating-layout-shift-from-web-fonts\u002F","Eliminating Layout Shift From Web Fonts",[27,1793,1795],{"id":1794},"cache-the-subset-not-the-step","Cache the Subset, Not the Step",[14,1797,1798],{},"Subsetting adds two to five seconds to a build, which is fine occasionally and annoying on every push. Key a cache on the font file and the character set so the step only runs when one of them changes:",[61,1800,1804],{"className":1801,"code":1802,"language":1803,"meta":66,"style":66},"language-yaml shiki shiki-themes github-light github-dark","- name: Cache font subsets\n  uses: actions\u002Fcache@v4\n  with:\n    path: dist\u002Ffonts\n    key: fonts-${{ hashFiles('fonts\u002Fsrc\u002F*.ttf', 'build\u002Fcharset.txt') }}\n","yaml",[42,1805,1806,1820,1830,1838,1848],{"__ignoreMap":66},[70,1807,1808,1811,1815,1817],{"class":72,"line":73},[70,1809,1810],{"class":87},"- ",[70,1812,1814],{"class":1813},"s9eBZ","name",[70,1816,874],{"class":87},[70,1818,1819],{"class":94},"Cache font subsets\n",[70,1821,1822,1825,1827],{"class":72,"line":80},[70,1823,1824],{"class":1813},"  uses",[70,1826,874],{"class":87},[70,1828,1829],{"class":94},"actions\u002Fcache@v4\n",[70,1831,1832,1835],{"class":72,"line":101},[70,1833,1834],{"class":1813},"  with",[70,1836,1837],{"class":87},":\n",[70,1839,1840,1843,1845],{"class":72,"line":115},[70,1841,1842],{"class":1813},"    path",[70,1844,874],{"class":87},[70,1846,1847],{"class":94},"dist\u002Ffonts\n",[70,1849,1850,1853,1855],{"class":72,"line":122},[70,1851,1852],{"class":1813},"    key",[70,1854,874],{"class":87},[70,1856,1857],{"class":94},"fonts-${{ hashFiles('fonts\u002Fsrc\u002F*.ttf', 'build\u002Fcharset.txt') }}\n",[14,1859,1860],{},"Serve the result with a content hash in the filename and an immutable cache header, so readers download it once and never revalidate it. Because the subset changes whenever the content introduces a new character, the hash is what keeps that correct without any manual versioning.",[27,1862,1864],{"id":1863},"pitfalls-rollback","Pitfalls & Rollback",[32,1866,1867,1874,1880,1892,1898,1904],{},[35,1868,1869,1873],{},[1870,1871,1872],"strong",{},"A hand-written character set."," It will be missing something within a month. Derive it from the built HTML.",[35,1875,1876,1879],{},[1870,1877,1878],{},"No coverage check."," Missing glyphs render in a fallback face and are easy to miss in review. Fail the build instead.",[35,1881,1882,1885,1886,1888,1889,1891],{},[1870,1883,1884],{},"Dropping needed OpenType features."," Losing ",[42,1887,838],{}," or ",[42,1890,842],{}," produces subtle rendering defects; keep the standard four unless you have verified otherwise.",[35,1893,1894,1897],{},[1870,1895,1896],{},"Subsetting a font whose licence forbids modification."," Check the licence — most webfont licences permit subsetting, but not all.",[35,1899,1900,1903],{},[1870,1901,1902],{},"Committing the subset."," It drifts from the content. Generate it in the build and cache the artifact.",[35,1905,1906,1909,1910,1912,1913,1916],{},[1870,1907,1908],{},"Rollback:"," point the ",[42,1911,1403],{}," ",[42,1914,1915],{},"src"," back at the full font file and remove the build step. Nothing else in the stylesheet changes, and metrics are identical either way.",[27,1918,1920],{"id":1919},"conclusion","Conclusion",[14,1922,1923,1924,25],{},"Subsetting turns a variable font from one of the largest assets on the page into one of the smallest, and axis pinning removes a further slice most sites never notice they are paying for. Derive the character set from the built HTML, generate the subset during the build, keep the OpenType features the face relies on, and gate on a coverage check so a new character fails the build rather than the page. Then preload the result, and combine it with the metric overrides in ",[21,1925,24],{"href":23},[27,1927,1929],{"id":1928},"faq","FAQ",[1931,1932,1934],"h3",{"id":1933},"how-much-smaller-does-subsetting-make-a-variable-font","How much smaller does subsetting make a variable font?",[14,1936,1937],{},"Typically 60 to 80 percent for a Latin site. A 128 kilobyte variable font with the full Latin Extended range drops to about 31 kilobytes when restricted to the characters a site actually publishes, and further if you pin unused axes.",[1931,1939,1941],{"id":1940},"what-is-axis-pinning-and-when-should-i-use-it","What is axis pinning and when should I use it?",[14,1943,1944],{},"Pinning fixes a variable axis at one value and removes its interpolation data. If your design uses weights 400 and 700 but no optical size or width variation, pinning those axes removes bytes you were never going to use, without changing a single rendered glyph.",[1931,1946,1948],{"id":1947},"will-subsetting-break-characters-i-did-not-anticipate","Will subsetting break characters I did not anticipate?",[14,1950,1951],{},"It will, unless the character set is derived from your actual content plus a safety margin. Derive it from the built HTML rather than guessing, and always include punctuation, currency symbols, arrows and the box-drawing characters code blocks tend to use.",[1931,1953,1955],{"id":1954},"should-the-subset-be-generated-in-the-build-or-committed","Should the subset be generated in the build or committed?",[14,1957,1958],{},"Generate it in the build from the original font, and cache the result. That way the subset always matches the current content, and a new character in a new article never renders as a fallback glyph.",[1931,1960,1962],{"id":1961},"does-subsetting-affect-the-metric-override-values-i-set-for-layout-stability","Does subsetting affect the metric-override values I set for layout stability?",[14,1964,1965,1966,1888,1968,1970],{},"No. Subsetting removes glyphs, not metrics — the ascent, descent and units-per-em are unchanged, so any ",[42,1967,1782],{},[42,1969,1786],{}," values you calculated remain correct.",[27,1972,1974],{"id":1973},"related","Related",[32,1976,1977,1985,1992,1997,2004],{},[35,1978,1979,1912,1982,1984],{},[1870,1980,1981],{},"Parent:",[21,1983,24],{"href":23}," — the loading policy this feeds.",[35,1986,1987,1991],{},[21,1988,1990],{"href":1989},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fself-hosting-google-fonts-to-eliminate-layout-shift\u002F","Self-Hosting Google Fonts to Eliminate Layout Shift"," — the prerequisite for subsetting at all.",[35,1993,1994,1996],{},[21,1995,1791],{"href":1790}," — metric overrides that compose with this.",[35,1998,1999,2003],{},[21,2000,2002],{"href":2001},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Foptimizing-lcp-on-astro-with-priority-hints\u002F","Optimizing LCP on Astro With Priority Hints"," — deciding what to preload alongside the font.",[35,2005,2006,2010],{},[21,2007,2009],{"href":2008},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002F","CDN Caching Rules for SSGs"," — caching the subset immutably.",[2012,2013,2014],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":66,"searchDepth":80,"depth":80,"links":2016},[2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2033],{"id":29,"depth":80,"text":30},{"id":55,"depth":80,"text":56},{"id":690,"depth":80,"text":691},{"id":922,"depth":80,"text":923},{"id":1383,"depth":80,"text":1384},{"id":1602,"depth":80,"text":1603},{"id":1794,"depth":80,"text":1795},{"id":1863,"depth":80,"text":1864},{"id":1919,"depth":80,"text":1920},{"id":1928,"depth":80,"text":1929,"children":2027},[2028,2029,2030,2031,2032],{"id":1933,"depth":101,"text":1934},{"id":1940,"depth":101,"text":1941},{"id":1947,"depth":101,"text":1948},{"id":1954,"depth":101,"text":1955},{"id":1961,"depth":101,"text":1962},{"id":1973,"depth":80,"text":1974},[2035,2037,2040,2041],{"name":2036,"item":207},"Home",{"name":2038,"item":2039},"Performance Optimization & Core Web Vitals for SSGs","\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002F",{"name":24,"item":23},{"name":5,"item":2042},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fsubsetting-variable-fonts-for-faster-first-render\u002F","2026-08-01","Cut a 128 KB variable font to 31 KB with glyph subsetting and axis pinning, generate the subset in the build, and keep punctuation and code glyphs from disappearing.","md",[2047,2048,2049,2050,2051],{"q":1934,"a":1937},{"q":1941,"a":1944},{"q":1948,"a":1951},{"q":1955,"a":1958},{"q":1962,"a":2052},"No. Subsetting removes glyphs, not metrics — the ascent, descent and units-per-em are unchanged, so any size-adjust or ascent-override values you calculated remain correct.",{},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fsubsetting-variable-fonts-for-faster-first-render",{"title":5,"description":2044},"performance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fsubsetting-variable-fonts-for-faster-first-render\u002Findex","article","ZQ3WCZfY34URu2YQFnvQRkrQrVHkk3mGQwFa3hs56g0",[2060,2063,2066,2069,2072,2075,2078,2081,2084,2087,2090,2093,2096,2099,2102,2105,2108,2111,2114,2117,2120,2123,2126,2129,2132,2135,2138,2140,2143,2146,2149,2151,2154,2157,2160,2163,2165,2168,2169,2172,2175,2178,2181,2184,2187,2190,2193,2196,2199,2202,2205,2208,2211,2214,2217,2220,2223,2226,2229,2232,2235,2238,2241,2244,2247,2250,2253,2256,2259,2262,2265,2268,2271,2274,2277,2280,2283,2286,2289,2292,2295,2298,2301],{"path":2061,"title":2062},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fchoosing-between-astro-and-eleventy-for-large-docs","Astro vs Eleventy for Large Docs (1000+ Pages)",{"path":2064,"title":2065},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fcontent-collections-vs-eleventy-data-cascade","Content Collections vs the Eleventy Data Cascade",{"path":2067,"title":2068},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":2070,"title":2071},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fhow-to-benchmark-hugo-vs-astro-build-speeds","How to Benchmark Hugo vs Astro Build Speeds",{"path":2073,"title":2074},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":2076,"title":2077},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fprofiling-hugo-templates-with-template-metrics","Profiling Hugo Templates With Template Metrics",{"path":2079,"title":2080},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fspeeding-up-hugo-builds-with-render-hooks-and-caching","Speeding Up Hugo Builds with Render Hooks & Caching",{"path":2082,"title":2083},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":2085,"title":2086},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Feleventy-vs-jekyll-for-markdown-heavy-blogs","Eleventy vs Jekyll for Markdown-Heavy Blogs",{"path":2088,"title":2089},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":2091,"title":2092},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Freplacing-jekyll-plugins-when-migrating-to-eleventy","Replacing Jekyll Plugins When Migrating to Eleventy",{"path":2094,"title":2095},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Frunning-jekyll-on-github-pages-without-plugins","Running Jekyll on GitHub Pages Without Plugins",{"path":2097,"title":2098},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":2100,"title":2101},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fkeeping-redirects-working-after-an-ssg-migration","Keeping Redirects Working After an SSG Migration",{"path":2103,"title":2104},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-a-docs-site-from-jekyll-to-hugo","Migrating a Docs Site From Jekyll to Hugo",{"path":2106,"title":2107},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls","Migrating From Hugo to Astro Without Breaking URLs",{"path":2109,"title":2110},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators","Porting Shortcodes and Includes Between Generators",{"path":2112,"title":2113},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fhandling-dynamic-routes-in-nextjs-static-export","Handling Dynamic Routes in Next.js Static Export",{"path":2115,"title":2116},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":2118,"title":2119},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fmigrating-from-gatsby-to-nextjs-static-export","Migrating from Gatsby to Next.js Static Export",{"path":2121,"title":2122},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fnextjs-static-export-vs-astro-for-marketing-sites","Next.js Static Export vs Astro for Marketing",{"path":2124,"title":2125},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fbest-ssg-for-technical-writers-without-coding-experience","Best SSG for Non-Developer Technical Writers",{"path":2127,"title":2128},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fchoosing-an-ssg-for-api-reference-documentation","Choosing an SSG for API Reference Documentation",{"path":2130,"title":2131},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":2133,"title":2134},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fpicking-an-ssg-for-a-multi-language-documentation-site","Picking an SSG for a Multi-Language Docs Site",{"path":2136,"title":2137},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fssg-selection-checklist-for-engineering-teams","SSG Selection Checklist for Engineering Teams",{"path":2139,"title":2009},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs",{"path":2141,"title":2142},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fpurging-the-cdn-cache-after-a-static-deploy","Purging the CDN Cache After a Static Deploy",{"path":2144,"title":2145},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fsetting-cache-control-headers-on-cloudflare-pages","Cache-Control Headers on Cloudflare Pages",{"path":2147,"title":2148},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fsetting-up-proper-cache-headers-on-netlify","Proper Cache Headers on Netlify for SSGs",{"path":2150,"title":1791},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Feliminating-layout-shift-from-web-fonts",{"path":2152,"title":2153},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-late-loading-embeds","Fixing CLS From Late-Loading Embeds",{"path":2155,"title":2156},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":2158,"title":2159},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js","Measuring CLS in the Field With web-vitals.js",{"path":2161,"title":2162},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Freserving-space-for-images-and-embeds-to-stop-layout-shift","Reserving Space for Images and Embeds",{"path":2164,"title":24},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites",{"path":2166,"title":2167},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fself-hosting-google-fonts-to-eliminate-layout-shift","Self-Host Google Fonts to Eliminate Layout Shift",{"path":2054,"title":5},{"path":2170,"title":2171},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fbuilding-an-image-cdn-pipeline-for-static-sites","Building an Image CDN Pipeline for Static Sites",{"path":2173,"title":2174},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":2176,"title":2177},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Foptimizing-webp-images-in-hugo-without-plugins","Optimizing WebP Images in Hugo Without Plugins",{"path":2179,"title":2180},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fserving-avif-with-fallbacks-on-static-sites","Serving AVIF With Fallbacks on Static Sites",{"path":2182,"title":2183},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":2185,"title":2186},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":2188,"title":2189},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fhow-to-reduce-bundle-size-in-eleventy-builds","How to Reduce Bundle Size in Eleventy Builds",{"path":2191,"title":2192},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":2194,"title":2195},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fmeasuring-inp-on-static-sites-with-real-user-monitoring","Measuring INP on Static Sites with RUM",{"path":2197,"title":2198},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Feliminating-render-blocking-css-on-static-sites","Eliminating Render-Blocking CSS on Static Sites",{"path":2200,"title":2201},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":2203,"title":2204},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Foptimizing-lcp-on-astro-with-priority-hints","Optimizing LCP on Astro with Priority Hints",{"path":2206,"title":2207},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Freducing-lcp-from-hero-images-on-static-sites","Reducing LCP from Hero Images on Static Sites",{"path":2209,"title":2210},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fauditing-third-party-scripts-with-lighthouse","Auditing Third-Party Scripts With Lighthouse",{"path":2212,"title":2213},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":2215,"title":2216},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Flazy-loading-youtube-embeds-on-static-sites","Lazy-Loading YouTube Embeds on Static Sites",{"path":2218,"title":2219},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests","Self-Hosting Analytics to Cut Third-Party Requests",{"path":2221,"title":2222},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2224,"title":2225},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fdeploying-hugo-to-cloudflare-pages-and-workers","Deploying Hugo to Cloudflare Pages and Workers",{"path":2227,"title":2228},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2230,"title":2231},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fdocs-as-code-review-workflow-for-writers","Docs-as-Code Review Workflow for Writers",{"path":2233,"title":2234},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":2236,"title":2237},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fscheduling-content-publication-with-cron-triggered-builds","Scheduling Content Publication With Cron-Triggered Builds",{"path":2239,"title":2240},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fwiring-a-headless-cms-to-a-static-build","Wiring a Headless CMS to a Static Build",{"path":2242,"title":2243},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fcaching-node-modules-in-github-actions-for-faster-ssg-builds","Caching node_modules in GitHub Actions",{"path":2245,"title":2246},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-multiple-environments-from-one-workflow","Deploying to Multiple Environments From One Workflow",{"path":2248,"title":2249},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fhow-to-set-up-github-actions-for-hugo-deployments","GitHub Actions for Hugo Deployments",{"path":2251,"title":2252},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":2254,"title":2255},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fcaching-hugo-builds-in-github-actions","Caching Hugo Builds in GitHub Actions",{"path":2257,"title":2258},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2260,"title":2261},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":2263,"title":2264},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fmeasuring-build-time-regressions-in-ci","Measuring Build-Time Regressions in CI",{"path":2266,"title":2267},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fsharing-build-cache-across-ci-runners","Sharing Build Cache Across CI Runners",{"path":2269,"title":2270},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2272,"title":2273},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2275,"title":2276},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2278,"title":2279},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fsetting-up-deploy-previews-on-netlify-for-every-pull-request","Netlify Deploy Previews for Every Pull Request",{"path":2281,"title":2282},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fvercel-isr-vs-static-generation-for-ssgs","Vercel ISR vs Static Generation for SSGs",{"path":2284,"title":2285},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fautomating-preview-deploy-pipelines-with-github-actions","Automating Preview Deploy Pipelines with GitHub Actions",{"path":2287,"title":2288},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2290,"title":2291},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":2293,"title":2294},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2296,"title":2297},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":2299,"title":2300},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frolling-back-a-bad-static-deploy-in-under-a-minute","Rolling Back a Bad Static Deploy in Under a Minute",{"path":2302,"title":2303},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url","Running Smoke Tests Against a Preview URL",1785611671176]