Drop-in Node script that runs 15 regression checks before every Netlify build. Critical fails (exit 1) block the deploy; warnings (exit 2) are logged. Zero dependencies β uses only Node built-ins.
Every bug we diagnosed across 20+ sites this quarter β automated so the next regression never ships.
| # | Check | Severity | Catches |
|---|---|---|---|
| 1 | force=true redirect loops | CRITICAL | Netlify redirects where from and to differ only by trailing slash + force = true β causes infinite redirect loops. Caught on 5 sites. |
| 2 | sitemap.xml present | CRITICAL | No sitemap.njk or sitemap.xml in source/build. Search engines can't discover your URLs. |
| 3 | robots.txt present | CRITICAL | No robots.txt or robots.njk. Bots fall back to "no rules" which is acceptable but signals an incomplete site. |
| 4 | Stale /tools/* refs | WARN | Markdown blog posts with /tools/slug/ relative links where the slug doesn't exist locally β these should be absolute to the tools hub (jwatte.com). Caught 80+ of these on the ai-terminal-kickstart blog. |
| 5 | Sitemap nav orphans | WARN | URLs in sitemap.xml that the homepage doesn't link to. Orphans lose authority flow; search engines deprioritize. |
| 6 | /tools/ hub missing | WARN | Nav has a "Tools" dropdown but no /tools/index.html landing page β that button 404s. |
| 7 | AEO files (llms.txt / humans.txt / .well-known/llms.txt) | WARN | Missing AEO signal files. AI citation engines prefer sites that publish these. |
| 8 | Template leakage | CRITICAL | Built HTML contains literal {{ }} or {% %} placeholders β Eleventy/Nunjucks failed to render, ships broken pages. |
| 9 | Homepage img alt + width/height | WARN | Images missing alt (WCAG fail) or width/height (CLS risk). |
| 10 | canonical + viewport + lang | WARN | Homepage missing canonical link, viewport meta, or html lang attribute. |
Download the script, drop it in your site root, wire it to Netlify's build command.
Step 1 β download:
Download predeploy-check.mjs View sourceStep 2 β wire it up in netlify.toml:
[build] command = "node predeploy-check.mjs || exit 1 ; npx @11ty/eleventy" publish = "_site"
The || exit 1 makes Netlify abort the build only on critical failures. Warnings print but don't block. Swap npx @11ty/eleventy for whatever your build command is (hugo, jekyll, gatsby, astro, etc.) β the check is framework-agnostic.
Step 3 β test locally:
cd /path/to/your/site node predeploy-check.mjs # Output: # ββ jwatte.com pre-deploy check βββββββββββββββββββββββββββββββββ # β Checks passed: 12 / 15 # β Critical fails: 0 # β Warnings: 3 # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Scenario: you rename a blog post from /blog/old-slug/ to /blog/new-slug/. You update the Eleventy frontmatter and redeploy. The check runs:
CRITICAL (blocks deploy): β [netlify_force_true_loop] no force=true loop risk Warnings (review, not blocking): β [stale_tools_refs] no stale tool refs β [sitemap_orphans] 1 sitemap URL with no homepage inbound link: /blog/new-slug/ β [aeo_.well-known_llms.txt] .well-known/llms.txt missing
The critical check passes β you're not shipping a loop. But the sitemap orphan warning flags that the new slug isn't linked from anywhere on the homepage. You add a footer link before deploying. Regression avoided.
The script is ~180 lines of plain Node with no external dependencies. Every check is a standalone function β keep the ones that matter, delete the rest, add your own. Common additions:
grep -c < 800)tags: post for collection inclusion.html has more than N short <p> elements (chunk density)The full script is ~180 lines. Paste or download:
Loadingβ¦