Internal links & assets
HTML, CSS url()/@import, web manifests, sitemap*.xml and robots.txt are all checked against the build.
npm package · MIT · Node ≥ 18
Fail CI on broken links before you deploy.
Crawls your built static site (dist/, build/, out/, public/, _site/) for broken internal links, missing assets, missing #anchors and paths whose letter case only works on macOS. With --external it checks outbound links for 404s and redirect chains, showing every hop. No server needed: it resolves URLs the way Netlify, Vercel, GitHub Pages and Cloudflare Pages do.
npm i -D static-link-sentinel
$ npx static-link-sentinel dist --site-url https://mysite.com --orphansstatic-link-sentinel: dist/ 5 page(s), 1 stylesheet(s), 13 file(s), 41 internal reference(s) index.html ✖ error Broken internal link [broken-link] line 21 <a href="/blog/post-l/"> resolves to no file in the build (would serve 404.html) fix: did you mean /blog/post-1/? ✖ error Path case mismatch [case-mismatch] line 22 fix: Use /blog/post-1/ or rename the file ⚠ warn Missing #fragment anchor [missing-fragment] line 24 fix: did you mean #team? ℹ info Absolute link to own domain [absolute-internal] line 31 fix: Use a relative link: /about
/img/Logo.png works on your Mac and 404s on the Linux CDN. A typo'd slug, a renamed heading anchor or a dead outbound link ships because nothing checks the built output.
static-link-sentinel reads the files you are about to deploy, resolves every reference exactly like your host will, and fails CI with a fix line such as "did you mean /blog/post-1/?".
HTML, CSS url()/@import, web manifests, sitemap*.xml and robots.txt are all checked against the build.
Case-sensitive matching like Linux hosts, with a dedicated case-mismatch error instead of a silent pass.
Missing fragments are reported with a "did you mean" suggestion.
--external HEADs each unique URL with GET fallback, per-host throttling, retries and a 24 h cache, recording every redirect hop.
Clean URLs, index.html, trailingSlash, basePath, Netlify _redirects and vercel.json.
--sources checks links and #heading anchors in .md / .mdx before you even build.
JSON, SARIF and Markdown output (great for job summaries and PR comments).
CLI, API, Vite and webpack plugins, and a GitHub Action. Node ≥ 18.
# Auto-detect the build dir
npx static-link-sentinel# Check external links too
npx static-link-sentinel dist --external --site-url https://example.com# GitHub Pages project site
npx static-link-sentinel dist --base-path /my-repo# Markdown before build
npx static-link-sentinel --sources docs content README.mdname: Links
on: [push, pull_request]
jobs:
links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci && npm run build
- uses: MrLouisWright/static-link-sentinel@v1
with:
dir: dist
site-url: https://example.com
external: ${{ github.event_name == 'push' }}
fail-on: error
sarif: links.sarif
- if: always()
uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: links.sarif }
| Flag | Description |
|---|---|
-b, --base-path <path> | Site is served under a sub-path (e.g. /docs) |
-s, --site-url <url> | Production URL: absolute links to it are checked as internal |
--trailing-slash <m> | always | never | ignore (default: ignore, or vercel.json) |
--clean-urls | Host serves /about for about.html |
--orphans | Report pages no other page links to |
-e, --external | Also check external links (HEAD, GET fallback) |
--concurrency <n> | Parallel external requests (default 8) |
--per-host <n> | Parallel requests per host (default 2) |
--host-delay <ms> | Minimum gap between requests to one host (default 150) |
--timeout <ms> | Per-request timeout (default 10000) |
--retries <n> | Retries on timeout/5xx/429 (default 2) |
--user-agent <ua> | User-Agent for external requests |
--skip <pattern> | Don't fetch matching external URLs (repeatable) |
--cache-file <file> | External result cache (default .link-cache.json) |
--cache-ttl <hours> | Cache lifetime (default 24) |
--no-cache | Don't read or write the cache |
--sources [pattern...] | Also scan markdown/mdx under the project |
-i, --ignore <pattern> | Ignore matching URLs (glob, substring or re:regex; repeatable) |
-c, --config <file> | JSON config |
--fail-on <level> | error | warn | none (default error) |
--json | JSON report on stdout |
--sarif [file] | SARIF 2.1.0 to stdout or file |
--markdown [file] | Markdown summary to stdout or file |
-q, --quiet | Hide info-level findings |
Full reference, config file format and API in the README.
Support this project
I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.
Buy me a coffee (opens Stripe in a new tab)Secure checkout by Stripe