Skip to content
inityslPublic

About

checker - website crawler, identifies broken links locally or in production.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

checker

A fast, zero-config CLI tool that crawls a website and identifies broken links — locally or in production.

  ✅ [200] https://mysite.com/                    35ms
  ✅ [200] https://mysite.com/about               11ms
  ❌ [404] https://mysite.com/old-post            12ms
  ❌ [301] https://mysite.com/moved-page           8ms → 404 https://mysite.com/gone
  💥 [ERR] https://mysite.com/api/health        8000ms — Request timed out

  ─────────────────────────────────────────────
  Summary
  ─────────────────────────────────────────────
  Total checked  : 47
  ✅ Live         : 41
  ❌ Broken       : 4
  ⚠️  Redirects    : 2
  💥 Errors       : 0

  Completed in 4.2s

Install

# Run without installing
npx @initysl/checker https://yoursite.com

# Install globally
npm install -g @initysl/checker
checker https://yoursite.com

Usage

checker <url> [options]

Options

Flag Default Description
-d, --depth <n> 2 Max crawl depth
-c, --concurrency <n> 5 Max requests in flight at once
-t, --timeout <ms> 8000 Per-request timeout
-o, --output <file> — Export results to JSON
--ignore <patterns...> — URL patterns to skip (* is a wildcard)
--only-broken false Only show broken links and errors
--exit-code false Exit 1 if any link is broken or errors
--no-robots — Skip robots.txt rules
--no-assets — Check only <a href>, not images/CSS/JS

Exit codes

Code Meaning
0 Crawl finished; nothing broken (or --exit-code not given)
1 Broken links or errors found, or the crawl could not start
130 Interrupted with Ctrl+C (a partial summary is still printed)

With --exit-code, both ❌ broken (4xx/5xx) and 💥 errors (timeout, DNS failure, connection refused) fail the build — a visitor cannot follow either.


Examples

# Basic crawl
checker https://mysite.com

# Localhost before deploying
checker http://localhost:3000 --depth 2

# Only show broken links
checker https://mysite.com --only-broken

# Ignore API routes and admin pages
checker https://mysite.com --ignore "/api/*" "/admin/*"

# Links only — skip images, stylesheets and scripts
checker https://mysite.com --no-assets

# Export full report to JSON
checker https://mysite.com --output report.json

# CI/CD — exit 1 if any broken links found
checker https://mysite.com --exit-code

Localhost

Works out of the box against any local dev server — no restrictions, no rate limits:

# Next.js
npm run dev
checker http://localhost:3000 --depth 2

# Astro / Hugo / Jekyll
npm run build && npm run preview
checker http://localhost:4321

# Any static server
npx serve ./dist
checker http://localhost:3000

The port is part of a site's identity, so a link to localhost:8080 found while crawling localhost:3000 is treated as external — checked, but not crawled.


CI/CD — GitHub Actions

Drop this into your workflow to automatically block deploys with broken links:

name: Check for broken links

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  broken-links:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Run checker
        run: npx @initysl/checker https://yoursite.com --depth 2 --only-broken --exit-code

The --exit-code flag causes the job to fail if any broken links are found, blocking the PR from merging.

Against a staging environment

- name: Deploy to staging
  run: # your deploy step

- name: Check broken links on staging
  run: npx @initysl/checker ${{ secrets.STAGING_URL }} --exit-code --ignore "/api/*"

Save report as artifact

- name: Check broken links
  run: npx @initysl/checker https://yoursite.com --output report.json --exit-code || true

- name: Upload report
  uses: actions/upload-artifact@v4
  with:
    name: broken-links-report
    path: report.json

JSON Report

Use --output to export a full structured report:

checker https://mysite.com --output report.json
{
  "meta": {
    "url": "https://mysite.com/",
    "generatedAt": "2026-05-30T10:00:00.000Z",
    "elapsedMs": 4231,
    "options": {
      "depth": 2,
      "concurrency": 5,
      "timeout": 8000,
      "ignore": [],
      "robots": true,
      "assets": true
    }
  },
  "stats": {
    "total": 47,
    "live": 41,
    "broken": 4,
    "redirects": 2,
    "errors": 0
  },
  "results": [
    {
      "url": "https://mysite.com/moved-page",
      "status": 301,
      "type": "broken",
      "linkType": "internal",
      "via": "a",
      "sourceUrl": "https://mysite.com/blog",
      "finalUrl": "https://mysite.com/gone",
      "finalStatus": 404,
      "redirects": 1,
      "responseTime": 12,
      "depth": 1,
      "error": null
    }
  ]
}
Field Meaning
status Status the link itself returned
type live · broken · redirect · error
linkType internal or external
via seed, a (an anchor) or asset (image, stylesheet, script)
sourceUrl Page the link was found on (null for the seed URL)
finalUrl Where the redirect chain ended, if it redirected
finalStatus Status at the end of the chain — this decides broken vs redirect
redirects Number of hops followed
depth Distance from the seed URL

How it works

Seed URL → checked itself (a 404 homepage is a broken link)
   ↓
Fetch page → parse <a href> + <img>/<link>/<script> sources
   ↓
For each link → HEAD request (GET fallback), follow redirects to the end
   ↓
Classify → live / broken / redirect / error
   ↓
Internal links within depth → enqueue → repeat
  • BFS crawl with configurable depth
  • Bounded concurrency — --concurrency caps every request in flight, not just pages
  • HEAD first for speed, with a GET fallback when a server rejects HEAD (400/403/404/405/501)
  • Redirects resolved to the end — a 301 into a 404 is reported as broken, not as a redirect
  • Deduplication — each URL checked exactly once across all pages
  • Assets checked — images, stylesheets, scripts and fonts are checked but never crawled
  • robots.txt respected by default, fetched once per host
  • One retry on transient network failures, so a blip doesn't redden CI
  • Readable errors — DNS lookup failed, Connection refused, Request timed out
  • Ctrl+C stops in-flight requests and still prints a summary

Known limitations

  • Bot-protected sites report as broken. Some hosts (npmjs.com, LinkedIn, Reddit, sites behind aggressive WAFs) return 403 to any client that is not a browser, no matter the method. Skip them with --ignore "npmjs.com/*" "linkedin.com/*".
  • Links are read from the served HTML, so anything a client-side framework renders after hydration is not seen. Point checker at a built/SSR'd site.
  • Fragments are not verified — /page#section is checked as /page; the presence of the anchor on that page is not confirmed.

Development

git clone https://github.com/initysl/checker
cd checker
npm install

# Run
node bin/checker.js https://example.com

# Test — spins up a local server and exercises the full crawl
npm test

License

MIT © initysl

About

checker - website crawler, identifies broken links locally or in production.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages