{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreigymzsrmga2zp3j3o5qmgeqoaodvqnbssdnozue63xcwgeafzeasi",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3moh6h4i2a7q2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreicx7i6al3nzag3bvlyaetib66tzzdhrh6bsztitgtsyvkslsfreha"
},
"mimeType": "image/webp",
"size": 114400
},
"path": "/_06a3df6b50aec966668fb/your-docs-have-dead-links-i-built-a-zero-dependency-cli-that-catches-the-local-ones-no-network-5a39",
"publishedAt": "2026-06-17T01:10:48.000Z",
"site": "https://dev.to",
"tags": [
"showdev",
"markdown",
"cli",
"opensource",
"https://www.npmjs.com/package/linkbust",
"https://pypi.org/project/linkbust",
"https://github.com/jjdoor/linkbust"
],
"textContent": "You rename a file, restructure a folder, tweak a heading — and quietly leave a trail of dead links across your README and docs. Nobody notices until a reader clicks `./old-guide.md` and gets a 404.\n\nThere are tools for this, but each asks for something:\n\n * **markdown-link-check** works, but it pulls in ~9 dependencies and its headline feature is hitting **external URLs over HTTP** — slow, rate-limited, and flaky in CI (your build shouldn't go red because someone's blog was down).\n * **lychee** is fast and great, but it's a **Rust binary** to install.\n * The old Python option, `mlc`, has been **unmaintained since 2021**.\n\n\n\nI wanted the deterministic part — **local links** — to be instant and dependency-free. So I built **linkbust** :\n\n\n\n npx linkbust\n\n\n\n README.md:8 ✗ ./setup.md (file not found)\n docs/api.md:14 ✗ #usage (no anchor in this file)\n guide.md:3 ✗ ./api.md#missing (no anchor in ./api.md)\n\n ✗ 3 broken · 142 local links checked · 38 external skipped (never fetched)\n\n\n## What it checks (and skips)\n\n * **Relative file links and image sources** resolve on disk.\n * **Anchors** — both `[x](#section)` in the same file and `[x](other.md#section)` across files — match a real heading (GitHub-style slug) or an explicit `<a id=\"…\">`.\n * **Reference links** `[text][ref]` have a matching `[ref]: …` definition.\n * Links inside fenced/inline **code blocks are ignored** , so examples don't cause false alarms.\n\n\n\nIt deliberately **never makes a network request** — `http(s)`, `mailto:`, and `/absolute` paths are classified and skipped. That's the whole design: because it's offline, it's instant and can't flake. Which makes it ideal as a **pre-commit hook** :\n\n\n\n - repo: local\n hooks:\n - id: linkbust\n name: linkbust\n entry: npx linkbust\n language: system\n pass_filenames: false\n\n\nExit code is `1` if anything's broken, so CI fails cleanly.\n\n## Zero dependencies, both ecosystems\n\nPure standard library — a small Markdown parser (code masking, reference links, GitHub anchor slugs) plus filesystem checks. The Node and Python ports are behavior-identical, byte-for-byte (same output, same exit codes).\n\n\n\n npx linkbust # Node >= 18\n pip install linkbust # Python >= 3.8\n\n\n * npm: https://www.npmjs.com/package/linkbust\n * PyPI: https://pypi.org/project/linkbust\n * GitHub: https://github.com/jjdoor/linkbust\n\n\n\nDo you check links in CI, or only find out when someone reports a 404? And for _external_ URLs — worth the flakiness to catch them, or do you treat those as out of scope too?",
"title": "Your docs have dead links. I built a zero-dependency CLI that catches the local ones — no network"
}