← Tools

Pre-Deploy Check Hook

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.

What it catches

Every bug we diagnosed across 20+ sites this quarter β€” automated so the next regression never ships.

#CheckSeverityCatches
1force=true redirect loopsCRITICALNetlify redirects where from and to differ only by trailing slash + force = true β€” causes infinite redirect loops. Caught on 5 sites.
2sitemap.xml presentCRITICALNo sitemap.njk or sitemap.xml in source/build. Search engines can't discover your URLs.
3robots.txt presentCRITICALNo robots.txt or robots.njk. Bots fall back to "no rules" which is acceptable but signals an incomplete site.
4Stale /tools/* refsWARNMarkdown 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.
5Sitemap nav orphansWARNURLs in sitemap.xml that the homepage doesn't link to. Orphans lose authority flow; search engines deprioritize.
6/tools/ hub missingWARNNav has a "Tools" dropdown but no /tools/index.html landing page β€” that button 404s.
7AEO files (llms.txt / humans.txt / .well-known/llms.txt)WARNMissing AEO signal files. AI citation engines prefer sites that publish these.
8Template leakageCRITICALBuilt HTML contains literal {{ }} or {% %} placeholders β€” Eleventy/Nunjucks failed to render, ships broken pages.
9Homepage img alt + width/heightWARNImages missing alt (WCAG fail) or width/height (CLS risk).
10canonical + viewport + langWARNHomepage missing canonical link, viewport meta, or html lang attribute.

Install (60 seconds)

Download the script, drop it in your site root, wire it to Netlify's build command.

Step 1 β€” download:

Download predeploy-check.mjs View source

Step 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
# └─────────────────────────────────────────────────────────

Example: catching a regression

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.

Customization

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:

Source

The full script is ~180 lines. Paste or download:

Loading…