[{"data":1,"prerenderedAt":1911},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys":3,"all-docs-nav":1369},{"id":4,"title":5,"body":6,"breadcrumb":1346,"dateModified":1356,"datePublished":1356,"description":1357,"extension":1358,"faq":1359,"meta":1364,"navigation":282,"path":1365,"seo":1366,"slug":12,"stem":1367,"type":136,"__hash__":1368},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys\u002Findex.md","Visual Regression Testing on Preview Deploys",{"type":7,"value":8,"toc":1328},"minimark",[9,13,17,31,36,61,65,68,221,227,231,238,521,687,690,694,697,748,841,845,848,1014,1017,1024,1028,1127,1130,1189,1192,1201,1205,1208,1212,1244,1248,1251,1255,1260,1263,1267,1270,1274,1277,1281,1284,1288,1324],[10,11,5],"h1",{"id":12},"visual-regression-testing-on-preview-deploys",[14,15,16],"p",{},"Static sites fail visually more often than they fail functionally. A CSS refactor shifts the sidebar on article pages, a dependency update changes the code block font, a new component pushes the footer over the content on narrow screens. Links still work, builds pass, and the problem is only noticed when a reader reports it. Reviewers looking at a preview deployment catch some of this, but nobody clicks through twelve templates at three screen sizes on every pull request.",[14,18,19,20,24,25,30],{},"Visual regression tests do that clicking. They take screenshots of key pages on the preview deployment, compare them with the same pages on ",[21,22,23],"code",{},"main",", and show the differences in the pull request. This guide sets them up with Playwright, chooses pages and viewports, removes the noise that makes teams abandon visual tests, and wires the results into review. It is part of ",[26,27,29],"a",{"href":28},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002F","Preview Environments for Pull Requests",".",[32,33,35],"h2",{"id":34},"prerequisites","Prerequisites",[37,38,39,47,54],"ul",{},[40,41,42,43,30],"li",{},"Preview deployments per pull request, with a URL available to CI — see ",[26,44,46],{"href":45},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fautomating-preview-deploy-pipelines-with-github-actions\u002F","Automating Preview Deploy Pipelines with GitHub Actions",[40,48,49,50,53],{},"Node.js and ",[21,51,52],{},"@playwright\u002Ftest"," in the repository.",[40,55,56,57,30],{},"A bypass token if previews are protected — see ",[26,58,60],{"href":59},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments\u002F","Password-Protecting Preview Deployments",[32,62,64],{"id":63},"what-to-screenshot","What to Screenshot",[14,66,67],{},"A static site's pages come from a small number of templates, and a layout bug in a template shows up on every page that uses it. Cover templates, not pages:",[69,70,71,217],"figure",{},[72,73,80,81,80,85,80,89,80,96],"svg",{"viewBox":74,"role":75,"ariaLabelledBy":76,"xmlns":79},"0 0 760 270","img",[77,78],"vrt-cov-title","vrt-cov-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[82,83,84],"title",{"id":77},"Coverage grid of templates and viewports",[86,87,88],"desc",{"id":78},"Seven templates - home, section index, article, long article with code and tables, search, 404 and a landing page - by three viewports - 375, 768 and 1280 pixels wide - give 21 screenshots, which run in under two minutes and cover every layout on the site.",[90,91],"rect",{"x":92,"y":92,"width":93,"height":94,"fill":95},"0","760","270","#ffffff",[97,98,100,101,100,109,100,115,100,119,100,123,100,129,100,133,100,137,100,141,100,145,100,149,100,153,100,211,80],"g",{"style":99},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[102,103,108],"text",{"x":104,"y":105,"fill":106,"style":107},"380","26","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","7 templates × 3 widths = 21 screenshots",[102,110,114],{"x":111,"y":112,"fill":106,"style":113},"400","56","font-weight:700;text-anchor:middle","375 px",[102,116,118],{"x":117,"y":112,"fill":106,"style":113},"520","768 px",[102,120,122],{"x":121,"y":112,"fill":106,"style":113},"640","1280 px",[102,124,128],{"x":125,"y":126,"fill":106,"style":127},"300","84","text-anchor:end","home",[102,130,132],{"x":125,"y":131,"fill":106,"style":127},"110","section index",[102,134,136],{"x":125,"y":135,"fill":106,"style":127},"136","article",[102,138,140],{"x":125,"y":139,"fill":106,"style":127},"162","long article (code, tables)",[102,142,144],{"x":125,"y":143,"fill":106,"style":127},"188","search",[102,146,148],{"x":125,"y":147,"fill":106,"style":127},"214","404",[102,150,152],{"x":125,"y":151,"fill":106,"style":127},"240","landing page",[97,154,156,157,163,166,156,169,172,174,156,176,179,181,156,183,186,188,156,190,193,195,156,197,200,202,156,204,207,209,100],{"style":155},"fill-opacity:0.35","\n      ",[90,158],{"x":104,"y":159,"width":160,"height":161,"fill":162},"72","40","16","#3f6410",[90,164],{"x":165,"y":159,"width":160,"height":161,"fill":162},"500",[90,167],{"x":168,"y":159,"width":160,"height":161,"fill":162},"620",[90,170],{"x":104,"y":171,"width":160,"height":161,"fill":162},"98",[90,173],{"x":165,"y":171,"width":160,"height":161,"fill":162},[90,175],{"x":168,"y":171,"width":160,"height":161,"fill":162},[90,177],{"x":104,"y":178,"width":160,"height":161,"fill":162},"124",[90,180],{"x":165,"y":178,"width":160,"height":161,"fill":162},[90,182],{"x":168,"y":178,"width":160,"height":161,"fill":162},[90,184],{"x":104,"y":185,"width":160,"height":161,"fill":162},"150",[90,187],{"x":165,"y":185,"width":160,"height":161,"fill":162},[90,189],{"x":168,"y":185,"width":160,"height":161,"fill":162},[90,191],{"x":104,"y":192,"width":160,"height":161,"fill":162},"176",[90,194],{"x":165,"y":192,"width":160,"height":161,"fill":162},[90,196],{"x":168,"y":192,"width":160,"height":161,"fill":162},[90,198],{"x":104,"y":199,"width":160,"height":161,"fill":162},"202",[90,201],{"x":165,"y":199,"width":160,"height":161,"fill":162},[90,203],{"x":168,"y":199,"width":160,"height":161,"fill":162},[90,205],{"x":104,"y":206,"width":160,"height":161,"fill":162},"228",[90,208],{"x":165,"y":206,"width":160,"height":161,"fill":162},[90,210],{"x":168,"y":206,"width":160,"height":161,"fill":162},[102,212,216],{"x":104,"y":213,"fill":214,"style":215},"264","#556071","font-size:11px;text-anchor:middle","add a dark-mode column if the site has a dark theme",[218,219,220],"figcaption",{},"Twenty-odd screenshots cover every layout; a thousand would cover the same layouts slower.",[14,222,223,224,30],{},"Pick URLs whose content is stable — a reference page that rarely changes is better than the newest blog post. Include at least one page with every component that has broken before: long code blocks, wide tables, callouts, image galleries, embedded video placeholders. Three widths — a phone, a tablet and a laptop — catch almost all responsive bugs. If the site has a dark theme, add it as a separate project with ",[21,225,226],{},"colorScheme: 'dark'",[32,228,230],{"id":229},"setting-up-playwright","Setting Up Playwright",[14,232,233,234,237],{},"Playwright's ",[21,235,236],{},"toHaveScreenshot"," compares a screenshot with a stored baseline and fails if they differ beyond a threshold:",[239,240,245],"pre",{"className":241,"code":242,"language":243,"meta":244,"style":244},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F tests\u002Fvisual.spec.ts\nimport { test, expect } from '@playwright\u002Ftest';\n\nconst pages = ['\u002F', '\u002Fdocs\u002F', '\u002Fdocs\u002Finstall\u002F', '\u002Fdocs\u002Freference\u002Fconfig\u002F', '\u002Fsearch\u002F', '\u002Fmissing-page\u002F'];\n\nfor (const path of pages) {\n  test(`visual ${path}`, async ({ page }) => {\n    await page.goto(path, { waitUntil: 'networkidle' });\n    await page.evaluate(() => document.fonts.ready);\n    await expect(page).toHaveScreenshot({\n      fullPage: true,\n      animations: 'disabled',\n      mask: [page.locator('[data-visual-mask]')],\n      maxDiffPixelRatio: 0.001,\n    });\n  });\n}\n","ts","",[21,246,247,256,277,284,332,337,357,397,418,436,452,464,475,492,503,509,515],{"__ignoreMap":244},[248,249,252],"span",{"class":250,"line":251},"line",1,[248,253,255],{"class":254},"sJ8bj","\u002F\u002F tests\u002Fvisual.spec.ts\n",[248,257,259,263,267,270,274],{"class":250,"line":258},2,[248,260,262],{"class":261},"szBVR","import",[248,264,266],{"class":265},"sVt8B"," { test, expect } ",[248,268,269],{"class":261},"from",[248,271,273],{"class":272},"sZZnC"," '@playwright\u002Ftest'",[248,275,276],{"class":265},";\n",[248,278,280],{"class":250,"line":279},3,[248,281,283],{"emptyLinePlaceholder":282},true,"\n",[248,285,287,290,294,297,300,303,306,309,311,314,316,319,321,324,326,329],{"class":250,"line":286},4,[248,288,289],{"class":261},"const",[248,291,293],{"class":292},"sj4cs"," pages",[248,295,296],{"class":261}," =",[248,298,299],{"class":265}," [",[248,301,302],{"class":272},"'\u002F'",[248,304,305],{"class":265},", ",[248,307,308],{"class":272},"'\u002Fdocs\u002F'",[248,310,305],{"class":265},[248,312,313],{"class":272},"'\u002Fdocs\u002Finstall\u002F'",[248,315,305],{"class":265},[248,317,318],{"class":272},"'\u002Fdocs\u002Freference\u002Fconfig\u002F'",[248,320,305],{"class":265},[248,322,323],{"class":272},"'\u002Fsearch\u002F'",[248,325,305],{"class":265},[248,327,328],{"class":272},"'\u002Fmissing-page\u002F'",[248,330,331],{"class":265},"];\n",[248,333,335],{"class":250,"line":334},5,[248,336,283],{"emptyLinePlaceholder":282},[248,338,340,343,346,348,351,354],{"class":250,"line":339},6,[248,341,342],{"class":261},"for",[248,344,345],{"class":265}," (",[248,347,289],{"class":261},[248,349,350],{"class":292}," path",[248,352,353],{"class":261}," of",[248,355,356],{"class":265}," pages) {\n",[248,358,360,364,367,370,373,376,378,381,384,388,391,394],{"class":250,"line":359},7,[248,361,363],{"class":362},"sScJk","  test",[248,365,366],{"class":265},"(",[248,368,369],{"class":272},"`visual ${",[248,371,372],{"class":265},"path",[248,374,375],{"class":272},"}`",[248,377,305],{"class":265},[248,379,380],{"class":261},"async",[248,382,383],{"class":265}," ({ ",[248,385,387],{"class":386},"s4XuR","page",[248,389,390],{"class":265}," }) ",[248,392,393],{"class":261},"=>",[248,395,396],{"class":265}," {\n",[248,398,400,403,406,409,412,415],{"class":250,"line":399},8,[248,401,402],{"class":261},"    await",[248,404,405],{"class":265}," page.",[248,407,408],{"class":362},"goto",[248,410,411],{"class":265},"(path, { waitUntil: ",[248,413,414],{"class":272},"'networkidle'",[248,416,417],{"class":265}," });\n",[248,419,421,423,425,428,431,433],{"class":250,"line":420},9,[248,422,402],{"class":261},[248,424,405],{"class":265},[248,426,427],{"class":362},"evaluate",[248,429,430],{"class":265},"(() ",[248,432,393],{"class":261},[248,434,435],{"class":265}," document.fonts.ready);\n",[248,437,439,441,444,447,449],{"class":250,"line":438},10,[248,440,402],{"class":261},[248,442,443],{"class":362}," expect",[248,445,446],{"class":265},"(page).",[248,448,236],{"class":362},[248,450,451],{"class":265},"({\n",[248,453,455,458,461],{"class":250,"line":454},11,[248,456,457],{"class":265},"      fullPage: ",[248,459,460],{"class":292},"true",[248,462,463],{"class":265},",\n",[248,465,467,470,473],{"class":250,"line":466},12,[248,468,469],{"class":265},"      animations: ",[248,471,472],{"class":272},"'disabled'",[248,474,463],{"class":265},[248,476,478,481,484,486,489],{"class":250,"line":477},13,[248,479,480],{"class":265},"      mask: [page.",[248,482,483],{"class":362},"locator",[248,485,366],{"class":265},[248,487,488],{"class":272},"'[data-visual-mask]'",[248,490,491],{"class":265},")],\n",[248,493,495,498,501],{"class":250,"line":494},14,[248,496,497],{"class":265},"      maxDiffPixelRatio: ",[248,499,500],{"class":292},"0.001",[248,502,463],{"class":265},[248,504,506],{"class":250,"line":505},15,[248,507,508],{"class":265},"    });\n",[248,510,512],{"class":250,"line":511},16,[248,513,514],{"class":265},"  });\n",[248,516,518],{"class":250,"line":517},17,[248,519,520],{"class":265},"}\n",[239,522,524],{"className":241,"code":523,"language":243,"meta":244,"style":244},"\u002F\u002F playwright.config.ts\nimport { defineConfig, devices } from '@playwright\u002Ftest';\nexport default defineConfig({\n  use: {\n    baseURL: process.env.PREVIEW_URL,\n    extraHTTPHeaders: process.env.BYPASS_TOKEN ? { 'x-preview-bypass': process.env.BYPASS_TOKEN } : {},\n  },\n  projects: [\n    { name: 'mobile', use: { ...devices['Pixel 7'] } },\n    { name: 'tablet', use: { viewport: { width: 768, height: 1024 } } },\n    { name: 'desktop', use: { viewport: { width: 1280, height: 800 } } },\n  ],\n});\n",[21,525,526,531,544,557,562,572,603,608,613,636,658,677,682],{"__ignoreMap":244},[248,527,528],{"class":250,"line":251},[248,529,530],{"class":254},"\u002F\u002F playwright.config.ts\n",[248,532,533,535,538,540,542],{"class":250,"line":258},[248,534,262],{"class":261},[248,536,537],{"class":265}," { defineConfig, devices } ",[248,539,269],{"class":261},[248,541,273],{"class":272},[248,543,276],{"class":265},[248,545,546,549,552,555],{"class":250,"line":279},[248,547,548],{"class":261},"export",[248,550,551],{"class":261}," default",[248,553,554],{"class":362}," defineConfig",[248,556,451],{"class":265},[248,558,559],{"class":250,"line":286},[248,560,561],{"class":265},"  use: {\n",[248,563,564,567,570],{"class":250,"line":334},[248,565,566],{"class":265},"    baseURL: process.env.",[248,568,569],{"class":292},"PREVIEW_URL",[248,571,463],{"class":265},[248,573,574,577,580,583,586,589,592,594,597,600],{"class":250,"line":339},[248,575,576],{"class":265},"    extraHTTPHeaders: process.env.",[248,578,579],{"class":292},"BYPASS_TOKEN",[248,581,582],{"class":261}," ?",[248,584,585],{"class":265}," { ",[248,587,588],{"class":272},"'x-preview-bypass'",[248,590,591],{"class":265},": process.env.",[248,593,579],{"class":292},[248,595,596],{"class":265}," } ",[248,598,599],{"class":261},":",[248,601,602],{"class":265}," {},\n",[248,604,605],{"class":250,"line":359},[248,606,607],{"class":265},"  },\n",[248,609,610],{"class":250,"line":399},[248,611,612],{"class":265},"  projects: [\n",[248,614,615,618,621,624,627,630,633],{"class":250,"line":420},[248,616,617],{"class":265},"    { name: ",[248,619,620],{"class":272},"'mobile'",[248,622,623],{"class":265},", use: { ",[248,625,626],{"class":261},"...",[248,628,629],{"class":265},"devices[",[248,631,632],{"class":272},"'Pixel 7'",[248,634,635],{"class":265},"] } },\n",[248,637,638,640,643,646,649,652,655],{"class":250,"line":438},[248,639,617],{"class":265},[248,641,642],{"class":272},"'tablet'",[248,644,645],{"class":265},", use: { viewport: { width: ",[248,647,648],{"class":292},"768",[248,650,651],{"class":265},", height: ",[248,653,654],{"class":292},"1024",[248,656,657],{"class":265}," } } },\n",[248,659,660,662,665,667,670,672,675],{"class":250,"line":454},[248,661,617],{"class":265},[248,663,664],{"class":272},"'desktop'",[248,666,645],{"class":265},[248,668,669],{"class":292},"1280",[248,671,651],{"class":265},[248,673,674],{"class":292},"800",[248,676,657],{"class":265},[248,678,679],{"class":250,"line":466},[248,680,681],{"class":265},"  ],\n",[248,683,684],{"class":250,"line":477},[248,685,686],{"class":265},"});\n",[14,688,689],{},"Baselines are stored next to the test file, one per page and project. Commit them to the repository; they are the reference for what the site should look like.",[32,691,693],{"id":692},"removing-noise","Removing Noise",[14,695,696],{},"Visual tests are abandoned when they fail for reasons that are not regressions. Deal with each source of noise up front:",[37,698,699,710,724,738],{},[40,700,701,705,706,709],{},[702,703,704],"strong",{},"Font rendering."," Different operating systems and even different Linux images render fonts differently. Always run visual tests in the official Playwright Docker image (",[21,707,708],{},"mcr.microsoft.com\u002Fplaywright",") pinned to a version, both in CI and when updating baselines locally.",[40,711,712,715,716,719,720,723],{},[702,713,714],{},"Animations and transitions."," ",[21,717,718],{},"animations: 'disabled'"," stops CSS animations; also add a test-only stylesheet that sets ",[21,721,722],{},"scroll-behavior: auto"," and hides blinking cursors.",[40,725,726,729,730,733,734,737],{},[702,727,728],{},"Lazy images and fonts."," Wait for ",[21,731,732],{},"document.fonts.ready",", scroll the page to trigger lazy images or set ",[21,735,736],{},"loading=\"eager\""," in a test mode, and wait for network idle.",[40,739,740,743,744,747],{},[702,741,742],{},"Dynamic content."," Dates, \"last updated\" lines, randomised testimonials and third-party embeds change between runs. Mark them with a ",[21,745,746],{},"data-visual-mask"," attribute in the templates and mask them.",[69,749,750,838],{},[72,751,80,756,80,759,80,762,80,764],{"viewBox":752,"role":75,"ariaLabelledBy":753,"xmlns":79},"0 0 760 240",[754,755],"vrt-noise-title","vrt-noise-desc",[82,757,758],{"id":754},"False positive rate as noise sources are removed",[86,760,761],{"id":755},"Percentage of visual test runs failing without a real regression. Default setup on a hosted runner: 34 percent. Pinned Playwright container: 11 percent. Animations disabled and fonts awaited: 4 percent. Dynamic regions masked: under 1 percent.",[90,763],{"x":92,"y":92,"width":93,"height":151,"fill":95},[97,765,100,766,100,770,100,776,100,783,100,789,100,793,100,800,100,805,100,808,100,812,100,817,100,820,100,826,100,831,100,834,80],{"style":99},[102,767,769],{"x":104,"y":768,"fill":106,"style":107},"28","Runs failing with no real regression",[250,771],{"x1":772,"y1":773,"x2":774,"y2":773,"stroke":214,"style":775},"60","190","700","stroke-width:1.5px",[90,777],{"x":778,"y":779,"width":780,"height":135,"fill":781,"style":782},"100","54","90","#d83b41","fill-opacity:0.45",[102,784,788],{"x":785,"y":786,"fill":106,"style":787},"145","48","text-anchor:middle","34%",[102,790,792],{"x":785,"y":791,"fill":214,"style":787},"208","defaults",[90,794],{"x":795,"y":796,"width":780,"height":797,"fill":798,"style":799},"250","146","44","#8a6d0f","fill-opacity:0.4",[102,801,804],{"x":802,"y":803,"fill":106,"style":787},"295","140","11%",[102,806,807],{"x":802,"y":791,"fill":214,"style":787},"pinned container",[90,809],{"x":111,"y":810,"width":780,"height":161,"fill":811,"style":782},"174","#166a9e",[102,813,816],{"x":814,"y":815,"fill":106,"style":787},"445","168","4%",[102,818,819],{"x":814,"y":791,"fill":214,"style":787},"+ no animations, fonts",[90,821],{"x":822,"y":823,"width":780,"height":824,"fill":162,"style":825},"550","187","3","fill-opacity:0.7",[102,827,830],{"x":828,"y":829,"fill":106,"style":787},"595","180","\u003C1%",[102,832,833],{"x":828,"y":791,"fill":214,"style":787},"+ masks",[102,835,837],{"x":104,"y":836,"fill":214,"style":215},"230","measured over 200 pull requests with no intended visual change",[218,839,840],{},"Each fix is small; together they turn an ignored check into a trusted one.",[32,842,844],{"id":843},"running-it-on-previews","Running It on Previews",[14,846,847],{},"Add a job that waits for the preview deployment, then runs the tests against its URL:",[239,849,853],{"className":850,"code":851,"language":852,"meta":244,"style":244},"language-yaml shiki shiki-themes github-light github-dark","visual:\n  needs: preview\n  runs-on: ubuntu-latest\n  container: mcr.microsoft.com\u002Fplaywright:v1.47.0-jammy\n  steps:\n    - uses: actions\u002Fcheckout@v4\n    - run: npm ci\n    - run: npx playwright test tests\u002Fvisual.spec.ts\n      env:\n        PREVIEW_URL: ${{ needs.preview.outputs.url }}\n        BYPASS_TOKEN: ${{ secrets.PREVIEW_BYPASS_TOKEN }}\n    - if: failure()\n      uses: actions\u002Fupload-artifact@v4\n      with:\n        name: visual-diffs\n        path: test-results\u002F\n","yaml",[21,854,855,864,875,885,895,902,915,927,938,945,955,965,977,987,994,1004],{"__ignoreMap":244},[248,856,857,861],{"class":250,"line":251},[248,858,860],{"class":859},"s9eBZ","visual",[248,862,863],{"class":265},":\n",[248,865,866,869,872],{"class":250,"line":258},[248,867,868],{"class":859},"  needs",[248,870,871],{"class":265},": ",[248,873,874],{"class":272},"preview\n",[248,876,877,880,882],{"class":250,"line":279},[248,878,879],{"class":859},"  runs-on",[248,881,871],{"class":265},[248,883,884],{"class":272},"ubuntu-latest\n",[248,886,887,890,892],{"class":250,"line":286},[248,888,889],{"class":859},"  container",[248,891,871],{"class":265},[248,893,894],{"class":272},"mcr.microsoft.com\u002Fplaywright:v1.47.0-jammy\n",[248,896,897,900],{"class":250,"line":334},[248,898,899],{"class":859},"  steps",[248,901,863],{"class":265},[248,903,904,907,910,912],{"class":250,"line":339},[248,905,906],{"class":265},"    - ",[248,908,909],{"class":859},"uses",[248,911,871],{"class":265},[248,913,914],{"class":272},"actions\u002Fcheckout@v4\n",[248,916,917,919,922,924],{"class":250,"line":359},[248,918,906],{"class":265},[248,920,921],{"class":859},"run",[248,923,871],{"class":265},[248,925,926],{"class":272},"npm ci\n",[248,928,929,931,933,935],{"class":250,"line":399},[248,930,906],{"class":265},[248,932,921],{"class":859},[248,934,871],{"class":265},[248,936,937],{"class":272},"npx playwright test tests\u002Fvisual.spec.ts\n",[248,939,940,943],{"class":250,"line":420},[248,941,942],{"class":859},"      env",[248,944,863],{"class":265},[248,946,947,950,952],{"class":250,"line":438},[248,948,949],{"class":859},"        PREVIEW_URL",[248,951,871],{"class":265},[248,953,954],{"class":272},"${{ needs.preview.outputs.url }}\n",[248,956,957,960,962],{"class":250,"line":454},[248,958,959],{"class":859},"        BYPASS_TOKEN",[248,961,871],{"class":265},[248,963,964],{"class":272},"${{ secrets.PREVIEW_BYPASS_TOKEN }}\n",[248,966,967,969,972,974],{"class":250,"line":466},[248,968,906],{"class":265},[248,970,971],{"class":859},"if",[248,973,871],{"class":265},[248,975,976],{"class":272},"failure()\n",[248,978,979,982,984],{"class":250,"line":477},[248,980,981],{"class":859},"      uses",[248,983,871],{"class":265},[248,985,986],{"class":272},"actions\u002Fupload-artifact@v4\n",[248,988,989,992],{"class":250,"line":494},[248,990,991],{"class":859},"      with",[248,993,863],{"class":265},[248,995,996,999,1001],{"class":250,"line":505},[248,997,998],{"class":859},"        name",[248,1000,871],{"class":265},[248,1002,1003],{"class":272},"visual-diffs\n",[248,1005,1006,1009,1011],{"class":250,"line":511},[248,1007,1008],{"class":859},"        path",[248,1010,871],{"class":265},[248,1012,1013],{"class":272},"test-results\u002F\n",[14,1015,1016],{},"Run the visual job in parallel with other preview checks such as link checks and smoke tests, not after them, so the slowest check sets the pace rather than their sum. Make it a required check only once false positives are rare; until then, report without blocking so the team builds trust in its results.",[14,1018,1019,1020,1023],{},"When a test fails, Playwright writes the expected, actual and diff images to ",[21,1021,1022],{},"test-results\u002F",". Upload them as an artifact and, ideally, post a comment on the pull request linking the HTML report so reviewers see the diffs without downloading anything.",[32,1025,1027],{"id":1026},"reviewing-and-updating-baselines","Reviewing and Updating Baselines",[69,1029,1030,1124],{},[72,1031,80,1035,80,1038,80,1041,80,1043,80,1058],{"viewBox":752,"role":75,"ariaLabelledBy":1032,"xmlns":79},[1033,1034],"vrt-rev-title","vrt-rev-desc",[82,1036,1037],{"id":1033},"What happens when a visual test fails",[86,1039,1040],{"id":1034},"A failing visual test produces expected, actual and diff images. The author decides whether the change was intended. If not, they fix the CSS and push again. If it was, they update the baselines in the pinned container and commit them, and reviewers approve the new images in the pull request diff.",[90,1042],{"x":92,"y":92,"width":93,"height":151,"fill":95},[1044,1045,100,1046,80],"defs",{},[1047,1048,1055],"marker",{"id":1049,"viewBox":1050,"refX":1051,"refY":1052,"markerWidth":1053,"markerHeight":1053,"orient":1054},"vrt-rev-arrow","0 0 10 10","9","5","7","auto-start-reverse",[372,1056],{"d":1057,"fill":214},"M0,0 L10,5 L0,10 z",[97,1059,100,1060,100,1063,100,1068,100,1074,100,1078,100,1081,100,1085,100,1088,100,1093,100,1097,100,1099,100,1103,100,1107,100,1113,100,1117,100,1120,80],{"style":99},[102,1061,1062],{"x":104,"y":105,"fill":106,"style":107},"A diff is a question: was this intended?",[90,1064],{"x":1065,"y":780,"width":185,"height":112,"rx":1066,"fill":781,"style":1067,"stroke":781},"20","8","fill-opacity:0.08",[102,1069,1073],{"x":1070,"y":1071,"fill":1072,"style":113},"95","114","#b32b30","test fails",[102,1075,1077],{"x":1070,"y":1076,"fill":214,"style":215},"132","expected · actual · diff",[90,1079],{"x":1080,"y":780,"width":185,"height":112,"rx":1066,"fill":798,"style":1067,"stroke":798},"220",[102,1082,1084],{"x":802,"y":1083,"fill":106,"style":787},"122","intended change?",[90,1086],{"x":1087,"y":160,"width":125,"height":112,"rx":1066,"fill":811,"style":1067,"stroke":811},"440",[102,1089,1092],{"x":1090,"y":1091,"fill":811,"style":113},"590","64","no: fix the CSS",[102,1094,1096],{"x":1090,"y":1095,"fill":214,"style":215},"82","push; test re-runs on the new preview",[90,1098],{"x":1087,"y":803,"width":125,"height":112,"rx":1066,"fill":162,"style":1067,"stroke":162},[102,1100,1102],{"x":1090,"y":1101,"fill":162,"style":113},"164","yes: update baselines",[102,1104,1106],{"x":1090,"y":1105,"fill":214,"style":215},"182","in the pinned container; reviewers approve images",[250,1108],{"x1":1109,"y1":1110,"x2":1111,"y2":1110,"stroke":214,"style":1112},"170","118","216","stroke-width:1.5px;marker-end:url(#vrt-rev-arrow)",[250,1114],{"x1":1115,"y1":131,"x2":1116,"y2":159,"stroke":214,"style":1112},"370","436",[250,1118],{"x1":1115,"y1":1119,"x2":1116,"y2":1101,"stroke":214,"style":1112},"126",[102,1121,1123],{"x":104,"y":1122,"fill":214,"style":215},"226","either way, the pull request ends with a passing check and a reviewed visual state",[218,1125,1126],{},"Baseline updates are code changes and get the same review as the CSS that caused them.",[14,1128,1129],{},"A failing visual test is a question, not a verdict: did you mean to change this? If the change is unintended, fix the CSS. If it is intended — a redesigned header, a new component — update the baselines in the same pull request:",[239,1131,1135],{"className":1132,"code":1133,"language":1134,"meta":244,"style":244},"language-bash shiki shiki-themes github-light github-dark","docker run --rm -v \"$PWD\":\u002Fwork -w \u002Fwork mcr.microsoft.com\u002Fplaywright:v1.47.0-jammy \\\n  npx playwright test tests\u002Fvisual.spec.ts --update-snapshots\n","bash",[21,1136,1137,1172],{"__ignoreMap":244},[248,1138,1139,1142,1145,1148,1151,1154,1157,1160,1163,1166,1169],{"class":250,"line":251},[248,1140,1141],{"class":362},"docker",[248,1143,1144],{"class":272}," run",[248,1146,1147],{"class":292}," --rm",[248,1149,1150],{"class":292}," -v",[248,1152,1153],{"class":272}," \"",[248,1155,1156],{"class":265},"$PWD",[248,1158,1159],{"class":272},"\":\u002Fwork",[248,1161,1162],{"class":292}," -w",[248,1164,1165],{"class":272}," \u002Fwork",[248,1167,1168],{"class":272}," mcr.microsoft.com\u002Fplaywright:v1.47.0-jammy",[248,1170,1171],{"class":292}," \\\n",[248,1173,1174,1177,1180,1183,1186],{"class":250,"line":258},[248,1175,1176],{"class":272},"  npx",[248,1178,1179],{"class":272}," playwright",[248,1181,1182],{"class":272}," test",[248,1184,1185],{"class":272}," tests\u002Fvisual.spec.ts",[248,1187,1188],{"class":292}," --update-snapshots\n",[14,1190,1191],{},"Running the update in the same container as CI prevents font-rendering differences from creeping into the baselines. Reviewers then see the new baseline images in the pull request diff, which GitHub renders side by side, and approve the visual change explicitly.",[14,1193,1194,1195,1197,1198,1200],{},"Some teams prefer not to store baselines in Git at all, because design-heavy repositories accumulate many megabytes of images over time. The alternative is to screenshot the ",[21,1196,23],{}," branch preview (or production) in the same job and compare the pull request against those images on the fly. It avoids baseline maintenance and never goes stale, at the cost of twice the screenshots per run and a dependency on ",[21,1199,23],{}," being healthy. Hosted services such as Percy, Chromatic and Argos follow this model and add a review interface on top; they are worth considering once the number of screenshots grows beyond what a pull request diff shows comfortably.",[32,1202,1204],{"id":1203},"measured-impact","Measured Impact",[14,1206,1207],{},"A documentation team added 21 screenshots across seven templates and three viewports to their pull request pipeline. The job ran in about 90 seconds after the preview was ready. In the first quarter it caught 11 unintended layout changes before merge, including a table overflow on mobile introduced by a Tailwind upgrade and a missing dark-mode background on code blocks. False positives fell below 1% after masking two \"last updated\" dates and a GitHub star counter. Reader-reported layout bugs dropped from about four a month to one a quarter.",[32,1209,1211],{"id":1210},"pitfalls-rollback","Pitfalls & Rollback",[37,1213,1214,1220,1226,1232,1238],{},[40,1215,1216,1219],{},[702,1217,1218],{},"Baselines from a different environment."," Screenshots from a Mac never match Linux CI; always update in the container.",[40,1221,1222,1225],{},[702,1223,1224],{},"Too many pages."," Hundreds of screenshots make runs slow and reviews tedious; cover templates.",[40,1227,1228,1231],{},[702,1229,1230],{},"Low thresholds on huge pages."," A tiny ratio on a very long page can fail on one-pixel anti-aliasing; tune per test.",[40,1233,1234,1237],{},[702,1235,1236],{},"Protected previews."," Without the bypass token every screenshot is the login page, which all match each other perfectly.",[40,1239,1240,1243],{},[702,1241,1242],{},"Rollback:"," make the job non-required, or remove it; nothing else depends on it.",[32,1245,1247],{"id":1246},"conclusion","Conclusion",[14,1249,1250],{},"Visual regression tests catch the bugs that static sites are most prone to — layout, spacing, overflow, theme — and preview deployments give them a real environment to run against. Screenshot one or two pages per template at three widths with Playwright, run in a pinned container, disable animations, wait for fonts and images, mask dynamic content, and review diffs in the pull request. Updated baselines then become an explicit, reviewable record of every intended design change.",[32,1252,1254],{"id":1253},"faq","FAQ",[1256,1257,1259],"h3",{"id":1258},"which-pages-should-a-visual-regression-suite-cover-on-a-static-site","Which pages should a visual regression suite cover on a static site?",[14,1261,1262],{},"One or two representative URLs per template - home, section index, article, a long article with code and tables, search and 404 - at two or three viewport widths. Covering every page adds cost and noise without catching more layout bugs, because pages sharing a template break together.",[1256,1264,1266],{"id":1265},"why-do-visual-tests-produce-false-positives","Why do visual tests produce false positives?",[14,1268,1269],{},"Differences in font rendering between machines, animations, lazy-loaded images, dates, randomised content and third-party embeds all change pixels without a real regression. Run tests in a fixed container, disable animations, wait for fonts and images, and mask dynamic regions.",[1256,1271,1273],{"id":1272},"should-visual-tests-run-against-the-preview-url-or-a-local-build","Should visual tests run against the preview URL or a local build?",[14,1275,1276],{},"Against the preview when you want to test exactly what will ship, including headers, redirects and CDN behaviour. A local build served in CI is faster and avoids authentication, and is a good choice when previews are slow or protected.",[1256,1278,1280],{"id":1279},"where-do-the-baseline-screenshots-come-from","Where do the baseline screenshots come from?",[14,1282,1283],{},"From main. Either commit baselines to the repository and update them in the pull request that intentionally changes the design, or generate them on each run from the production site or the main branch preview and compare the pull request against that.",[32,1285,1287],{"id":1286},"related","Related",[37,1289,1290,1298,1305,1310,1317],{},[40,1291,1292,715,1295,1297],{},[702,1293,1294],{},"Parent:",[26,1296,29],{"href":28}," — the full preview workflow.",[40,1299,1300,1304],{},[26,1301,1303],{"href":1302},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url\u002F","Running Smoke Tests Against a Preview URL"," — functional checks alongside visual ones.",[40,1306,1307,1309],{},[26,1308,60],{"href":59}," — the bypass token tests need.",[40,1311,1312,1316],{},[26,1313,1315],{"href":1314},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Frunning-webpagetest-scripts-against-preview-deploys\u002F","Running WebPageTest Scripts Against Preview Deploys"," — performance checks on the same previews.",[40,1318,1319,1323],{},[26,1320,1322],{"href":1321},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fevaluating-ssg-accessibility-defaults\u002F","Evaluating SSG Accessibility Defaults"," — pairing visual tests with accessibility checks.",[1325,1326,1327],"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 .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":244,"searchDepth":258,"depth":258,"links":1329},[1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1345],{"id":34,"depth":258,"text":35},{"id":63,"depth":258,"text":64},{"id":229,"depth":258,"text":230},{"id":692,"depth":258,"text":693},{"id":843,"depth":258,"text":844},{"id":1026,"depth":258,"text":1027},{"id":1203,"depth":258,"text":1204},{"id":1210,"depth":258,"text":1211},{"id":1246,"depth":258,"text":1247},{"id":1253,"depth":258,"text":1254,"children":1340},[1341,1342,1343,1344],{"id":1258,"depth":279,"text":1259},{"id":1265,"depth":279,"text":1266},{"id":1272,"depth":279,"text":1273},{"id":1279,"depth":279,"text":1280},{"id":1286,"depth":258,"text":1287},[1347,1350,1353,1354],{"name":1348,"item":1349},"Home","\u002F",{"name":1351,"item":1352},"Production-Ready Deployment & CI\u002FCD Workflows","\u002Fproduction-ready-deployment-cicd-workflows\u002F",{"name":29,"item":28},{"name":5,"item":1355},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys\u002F","2026-09-18","Screenshot key templates on every preview deploy with Playwright, compare them with main, and review diffs in the pull request without drowning in noise.","md",[1360,1361,1362,1363],{"q":1259,"a":1262},{"q":1266,"a":1269},{"q":1273,"a":1276},{"q":1280,"a":1283},{},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys",{"title":5,"description":1357},"production-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys\u002Findex","3nOtrJy8BBwIbU_g62pSmbNhfM6yAhdVF-0lOIix_WU",[1370,1373,1376,1379,1382,1385,1388,1391,1394,1397,1400,1403,1406,1409,1412,1415,1418,1421,1424,1427,1430,1433,1436,1439,1442,1445,1448,1451,1454,1457,1460,1463,1466,1469,1472,1475,1478,1481,1484,1487,1490,1493,1496,1499,1502,1505,1508,1511,1514,1517,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1659,1662,1665,1668,1671,1674,1677,1680,1683,1686,1689,1692,1695,1698,1701,1704,1707,1710,1713,1716,1719,1722,1725,1728,1731,1734,1737,1740,1743,1746,1749,1752,1755,1758,1761,1764,1767,1770,1773,1776,1779,1782,1785,1788,1791,1794,1797,1800,1803,1806,1809,1812,1815,1818,1821,1823,1826,1828,1830,1833,1834,1837,1840,1843,1846,1849,1851,1854,1857,1860,1863,1866,1869,1872,1875,1878,1881,1884,1887,1890,1893,1896,1899,1902,1905,1908],{"path":1371,"title":1372},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fastro-vs-eleventy-build-times-at-10000-pages","Astro vs Eleventy Build Times at 10,000 Pages",{"path":1374,"title":1375},"\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":1377,"title":1378},"\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":1380,"title":1381},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1383,"title":1384},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fshortcodes-vs-components-for-docs-authors","Shortcodes vs Components for Docs Authors",{"path":1386,"title":1387},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fsidebar-navigation-in-astro-and-eleventy","Sidebar Navigation in Astro and Eleventy",{"path":1389,"title":1390},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fcustomizing-starlight-without-forking-the-theme","Customizing Starlight Without Forking the Theme",{"path":1392,"title":1393},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fdocusaurus-vs-starlight-for-product-documentation","Docusaurus vs Starlight for Product Documentation",{"path":1395,"title":1396},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress","Docs Frameworks: Docusaurus, Starlight and VitePress",{"path":1398,"title":1399},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmdx-vs-markdoc-for-docs-content","MDX vs Markdoc for Docs Content",{"path":1401,"title":1402},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmigrating-from-mkdocs-to-starlight","Migrating from MkDocs to Starlight",{"path":1404,"title":1405},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fversioned-documentation-with-docusaurus","Versioned Documentation with Docusaurus",{"path":1407,"title":1408},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fvitepress-for-library-documentation","VitePress for Library Documentation",{"path":1410,"title":1411},"\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":1413,"title":1414},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fhugo-partialcached-for-faster-builds","Hugo partialCached for Faster Builds",{"path":1416,"title":1417},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1419,"title":1420},"\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":1422,"title":1423},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Freducing-hugo-memory-usage-on-ci-runners","Reducing Hugo Memory Usage on CI Runners",{"path":1425,"title":1426},"\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":1428,"title":1429},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1431,"title":1432},"\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":1434,"title":1435},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1437,"title":1438},"\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":1440,"title":1441},"\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":1443,"title":1444},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fspeeding-up-slow-jekyll-builds","Speeding Up Slow Jekyll Builds",{"path":1446,"title":1447},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely","Upgrading Jekyll and Ruby Versions Safely",{"path":1449,"title":1450},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fconverting-front-matter-at-scale-during-migration","Converting Front Matter at Scale During Migration",{"path":1452,"title":1453},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1455,"title":1456},"\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":1458,"title":1459},"\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":1461,"title":1462},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-gatsby-to-astro","Migrating from Gatsby to Astro",{"path":1464,"title":1465},"\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":1467,"title":1468},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-wordpress-to-a-static-site-generator","Migrating WordPress to a Static Site Generator",{"path":1470,"title":1471},"\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":1473,"title":1474},"\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":1476,"title":1477},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1479,"title":1480},"\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":1482,"title":1483},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fnextjs-app-router-static-export-limitations","Next.js App Router Static Export Limitations",{"path":1485,"title":1486},"\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":1488,"title":1489},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Foptimizing-images-in-nextjs-static-export","Optimizing Images in Next.js Static Export",{"path":1491,"title":1492},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fadding-pagefind-to-an-astro-site","Adding Pagefind to an Astro Site",{"path":1494,"title":1495},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fbuilding-a-lunr-index-at-build-time-in-eleventy","Building a Lunr Index at Build Time in Eleventy",{"path":1497,"title":1498},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites","Search for Static Sites",{"path":1500,"title":1501},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Findexing-hugo-sites-with-pagefind","Indexing Hugo Sites with Pagefind",{"path":1503,"title":1504},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fmultilingual-search-on-static-sites","Multilingual Search on Static Sites",{"path":1506,"title":1507},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fpagefind-vs-algolia-docsearch","Pagefind vs Algolia DocSearch",{"path":1509,"title":1510},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fsearch-index-size-budgets-for-large-docs","Search Index Size Budgets for Large Docs",{"path":1512,"title":1513},"\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":1515,"title":1516},"\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":1518,"title":1322},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fevaluating-ssg-accessibility-defaults",{"path":1520,"title":1521},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1523,"title":1524},"\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":1526,"title":1527},"\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":1529,"title":1530},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Ftotal-cost-of-ownership-for-static-site-generators","Total Cost of Ownership for Static Site Generators",{"path":1532,"title":1533},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fcache-busting-with-content-hashed-filenames","Cache Busting with Content-Hashed Filenames",{"path":1535,"title":1536},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1538,"title":1539},"\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":1541,"title":1542},"\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":1544,"title":1545},"\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":1547,"title":1548},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fstale-while-revalidate-for-static-html","Stale-While-Revalidate for Static HTML",{"path":1550,"title":1551},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Feliminating-layout-shift-from-web-fonts","Eliminating Layout Shift From Web Fonts",{"path":1553,"title":1554},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-cookie-banners","Fixing CLS from Cookie Banners",{"path":1556,"title":1557},"\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":1559,"title":1560},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-sticky-headers-and-anchor-links","Fixing CLS from Sticky Headers and Anchor Links",{"path":1562,"title":1563},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1565,"title":1566},"\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":1568,"title":1569},"\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":1571,"title":1572},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Ffont-display-optional-vs-swap","font-display: optional vs swap",{"path":1574,"title":1575},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1577,"title":1578},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fmetric-matched-fallback-fonts-with-size-adjust","Metric-Matched Fallback Fonts with size-adjust",{"path":1580,"title":1581},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fpreloading-fonts-without-double-downloads","Preloading Fonts Without Double Downloads",{"path":1583,"title":1584},"\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":1586,"title":1587},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fsubsetting-variable-fonts-for-faster-first-render","Subsetting Variable Fonts for Faster First Render",{"path":1589,"title":1590},"\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":1592,"title":1593},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fgenerating-open-graph-images-at-build-time","Generating Open Graph Images at Build Time",{"path":1595,"title":1596},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1598,"title":1599},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Flazy-loading-images-without-hurting-lcp","Lazy-Loading Images Without Hurting LCP",{"path":1601,"title":1602},"\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":1604,"title":1605},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fresponsive-images-with-srcset-in-eleventy","Responsive Images with srcset in Eleventy",{"path":1607,"title":1608},"\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":1610,"title":1611},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1613,"title":1614},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1616,"title":1617},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fdeferring-hydration-with-client-visible-in-astro","Deferring Hydration with client:visible in Astro",{"path":1619,"title":1620},"\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":1622,"title":1623},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1625,"title":1626},"\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":1628,"title":1629},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Freplacing-react-islands-with-web-components","Replacing React Islands with Web Components",{"path":1631,"title":1632},"\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":1634,"title":1635},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Ffixing-lcp-on-text-heavy-documentation-pages","Fixing LCP on Text-Heavy Documentation Pages",{"path":1637,"title":1638},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1640,"title":1641},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Fmeasuring-lcp-subparts-with-devtools","Measuring LCP Subparts with DevTools",{"path":1643,"title":1644},"\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":1646,"title":1647},"\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":1649,"title":1650},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fcomparing-lab-and-field-data-with-crux","Comparing Lab and Field Data with CrUX",{"path":1652,"title":1653},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci","Performance Budgets and Lighthouse CI",{"path":1655,"title":1656},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Freducing-lighthouse-score-variance-in-ci","Reducing Lighthouse Score Variance in CI",{"path":1658,"title":1315},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Frunning-webpagetest-scripts-against-preview-deploys",{"path":1660,"title":1661},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fsetting-up-lighthouse-ci-for-a-static-site","Setting Up Lighthouse CI for a Static Site",{"path":1663,"title":1664},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Ftracking-bundle-size-per-pull-request","Tracking Bundle Size per Pull Request",{"path":1666,"title":1667},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fwriting-a-performance-budget-that-fails-builds","Writing a Performance Budget That Fails Builds",{"path":1669,"title":1670},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fauditing-unused-preloads","Auditing Unused Preloads",{"path":1672,"title":1673},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed","Resource Hints and Navigation Speed",{"path":1675,"title":1676},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Finstant-navigation-with-speculation-rules","Instant Navigation with Speculation Rules",{"path":1678,"title":1679},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fpreconnect-vs-dns-prefetch-on-static-sites","Preconnect vs DNS-Prefetch on Static Sites",{"path":1681,"title":1682},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fprefetching-links-in-astro","Prefetching Links in Astro",{"path":1684,"title":1685},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fview-transitions-on-multi-page-static-sites","View Transitions on Multi-Page Static Sites",{"path":1687,"title":1688},"\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":1690,"title":1691},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":1693,"title":1694},"\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":1696,"title":1697},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Floading-google-tag-manager-without-hurting-inp","Loading Google Tag Manager Without Hurting INP",{"path":1699,"title":1700},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Frunning-third-party-scripts-in-a-web-worker-with-partytown","Running Third-Party Scripts in a Web Worker with Partytown",{"path":1702,"title":1703},"\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":1705,"title":1706},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1708,"title":1709},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fconfiguring-redirects-on-cloudflare-pages","Configuring Redirects on Cloudflare Pages",{"path":1711,"title":1712},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fcustom-domains-and-tls-on-cloudflare-pages","Custom Domains and TLS on Cloudflare Pages",{"path":1714,"title":1715},"\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":1717,"title":1718},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":1720,"title":1721},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fmigrating-from-cloudflare-pages-to-workers-static-assets","Migrating from Cloudflare Pages to Workers Static Assets",{"path":1723,"title":1724},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fchecking-links-in-pull-requests","Checking Links in Pull Requests",{"path":1726,"title":1727},"\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":1729,"title":1730},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Feditorial-checks-with-vale-in-ci","Editorial Checks with Vale in CI",{"path":1732,"title":1733},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1735,"title":1736},"\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":1738,"title":1739},"\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":1741,"title":1742},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fbuilding-astro-sites-with-github-actions","Building Astro Sites with GitHub Actions",{"path":1744,"title":1745},"\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":1747,"title":1748},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-github-pages-with-actions","Deploying to GitHub Pages with Actions",{"path":1750,"title":1751},"\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":1753,"title":1754},"\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":1756,"title":1757},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1759,"title":1760},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fmatrix-builds-for-multi-site-monorepos","Matrix Builds for Multi-Site Monorepos",{"path":1762,"title":1763},"\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":1765,"title":1766},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1768,"title":1769},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fincremental-builds-in-astro-with-the-content-layer","Incremental Builds in Astro with the Content Layer",{"path":1771,"title":1772},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1774,"title":1775},"\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":1777,"title":1778},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fremote-caching-with-turborepo-for-ssg-monorepos","Remote Caching with Turborepo for SSG Monorepos",{"path":1780,"title":1781},"\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":1783,"title":1784},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":1786,"title":1787},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Falerting-on-cache-hit-ratio-drops","Alerting on Cache Hit Ratio Drops",{"path":1789,"title":1790},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fbuilding-a-core-web-vitals-dashboard-from-rum-data","Building a Core Web Vitals Dashboard from RUM Data",{"path":1792,"title":1793},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fcrawling-for-broken-links-on-a-schedule","Crawling for Broken Links on a Schedule",{"path":1795,"title":1796},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production","Monitoring Static Sites in Production",{"path":1798,"title":1799},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Flogging-404s-at-the-edge","Logging 404s at the Edge",{"path":1801,"title":1802},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fuptime-and-synthetic-checks-for-static-sites","Uptime and Synthetic Checks for Static Sites",{"path":1804,"title":1805},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fconfiguring-vercel-for-hugo-and-eleventy","Configuring Vercel for Hugo and Eleventy",{"path":1807,"title":1808},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":1810,"title":1811},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":1813,"title":1814},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-redirects-and-rewrites-for-static-sites","Netlify Redirects and Rewrites for Static Sites",{"path":1816,"title":1817},"\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":1819,"title":1820},"\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":1822,"title":46},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fautomating-preview-deploy-pipelines-with-github-actions",{"path":1824,"title":1825},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":1827,"title":29},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests",{"path":1829,"title":60},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments",{"path":1831,"title":1832},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpreviewing-headless-cms-drafts","Previewing Headless CMS Drafts",{"path":1365,"title":5},{"path":1835,"title":1836},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":1838,"title":1839},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fcanary-releases-for-static-sites","Canary Releases for Static Sites",{"path":1841,"title":1842},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Ffeature-flags-on-static-sites","Feature Flags on Static Sites",{"path":1844,"title":1845},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":1847,"title":1848},"\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":1850,"title":1303},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url",{"path":1852,"title":1853},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines","Auditing npm Dependencies in SSG Pipelines",{"path":1855,"title":1856},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely","Enabling HSTS and Preload Safely",{"path":1858,"title":1859},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro","Hash-Based CSP for Inline Scripts in Astro",{"path":1861,"title":1862},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites","Security Headers and Hardening for Static Sites",{"path":1864,"title":1865},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsecuring-deploy-credentials-with-github-oidc","Securing Deploy Credentials with GitHub OIDC",{"path":1867,"title":1868},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets","Subresource Integrity for Third-Party Assets",{"path":1870,"title":1871},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fwriting-a-content-security-policy-for-a-static-site","Writing a Content Security Policy for a Static Site",{"path":1873,"title":1874},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fclean-urls-and-trailing-slashes-on-s3","Clean URLs and Trailing Slashes on S3",{"path":1876,"title":1877},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fcloudfront-functions-for-redirects","CloudFront Functions for Redirects",{"path":1879,"title":1880},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fdeploying-a-static-site-to-s3-and-cloudfront","Deploying a Static Site to S3 and CloudFront",{"path":1882,"title":1883},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites","Self-Hosting Static Sites on S3, Nginx and Caddy",{"path":1885,"title":1886},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-caddy","Serving a Static Site with Caddy",{"path":1888,"title":1889},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx","Serving a Static Site with Nginx",{"path":1891,"title":1892},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fzero-downtime-deploys-with-symlink-swaps","Zero-Downtime Deploys with Symlink Swaps",{"path":1894,"title":1895},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fadding-a-contact-form-with-cloudflare-workers","Adding a Contact Form with Cloudflare Workers",{"path":1897,"title":1898},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fhandling-form-submissions-on-a-static-site","Handling Form Submissions on a Static Site",{"path":1900,"title":1901},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites","Serverless Functions for Static Sites",{"path":1903,"title":1904},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fnetlify-functions-vs-cloudflare-workers","Netlify Functions vs Cloudflare Workers",{"path":1906,"title":1907},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fprotecting-a-static-site-behind-authentication","Protecting a Static Site Behind Authentication",{"path":1909,"title":1910},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fproxying-third-party-apis-from-an-edge-function","Proxying Third-Party APIs from an Edge Function",1789722848237]