Claude Code Plugins

Community-maintained marketplace

Feedback

Check for broken links in documentation, markdown files, and website pages. Use when maintaining documentation, before releases, or investigating 404 errors.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name broken-links
description Check for broken links in documentation, markdown files, and website pages. Use when maintaining documentation, before releases, or investigating 404 errors.
allowed-tools Read, Edit, Write, Bash, Grep

Broken Links Skill

This skill helps you find and fix broken links in documentation and web pages.

When to Use This Skill

  • Before documentation releases
  • After refactoring documentation
  • Investigating 404 errors
  • Migrating documentation
  • Updating external references
  • Checking API documentation
  • Verifying cross-references

Link Types to Check

  • Internal links: /docs/api, ./components/button.md
  • External links: https://nextjs.org, https://github.com/...
  • Anchor links: #installation, /docs/api#methods
  • Image links: ![Logo](./logo.png)
  • API references: Links in OpenAPI specs

Mintlify Link Checking

Built-in Command

# Check broken links in Mintlify docs
cd apps/docs
pnpm mintlify broken-links

# Output:
# Checking for broken links...
# ✓ docs/architecture/system.md
# ✓ docs/architecture/workflows.md
# ✗ docs/api/cars.md
#   - Line 15: https://example.com/broken-link (404)
#   - Line 23: ./missing-file.md (File not found)

# Check specific directory
pnpm mintlify broken-links docs/architecture

# Check with verbose output
pnpm mintlify broken-links --verbose

Configuration

// apps/docs/mint.json
{
  "openapi": ["/api/openapi.json"],
  "navigation": [
    {
      "group": "Documentation",
      "pages": ["docs/introduction", "docs/getting-started"]
    }
  ],
  // Broken link checking configuration
  "validation": {
    "checkLinks": true,
    "ignorePatterns": [
      "https://localhost:*",
      "https://127.0.0.1:*"
    ]
  }
}

Markdown Link Check

Installation

# Install markdown-link-check
pnpm add -D markdown-link-check

# Or use globally
npm install -g markdown-link-check

Basic Usage

# Check single file
npx markdown-link-check README.md

# Check all markdown files
find . -name "*.md" -not -path "*/node_modules/*" | xargs -n1 markdown-link-check

# Save results
find . -name "*.md" | xargs -n1 markdown-link-check > link-check-results.txt

# Check only specific directory
find apps/docs -name "*.md" | xargs -n1 markdown-link-check

Configuration

// .markdown-link-check.json
{
  "ignorePatterns": [
    {
      "pattern": "^http://localhost"
    },
    {
      "pattern": "^https://127.0.0.1"
    }
  ],
  "replacementPatterns": [
    {
      "pattern": "^/docs",
      "replacement": "https://docs.sgcarstrends.com/docs"
    }
  ],
  "httpHeaders": [
    {
      "urls": ["https://api.sgcarstrends.com"],
      "headers": {
        "Authorization": "Bearer ${DOCS_TOKEN}"
      }
    }
  ],
  "timeout": "10s",
  "retryOn429": true,
  "retryCount": 3,
  "aliveStatusCodes": [200, 206, 301, 302]
}

Usage with Config

# Use configuration file
npx markdown-link-check README.md -c .markdown-link-check.json

# Check all files with config
find . -name "*.md" -not -path "*/node_modules/*" | \
  xargs -n1 markdown-link-check -c .markdown-link-check.json

Broken Link Checker (Web)

Installation

# Install broken-link-checker
pnpm add -D broken-link-checker

# Or use globally
npm install -g broken-link-checker

Check Website

# Check production website
npx blc https://sgcarstrends.com -ro

# Flags:
# -r: Recursive (follow links)
# -o: Check external links
# -v: Verbose output

# Check staging
npx blc https://staging.sgcarstrends.com -ro

# Check local dev server
npx blc http://localhost:3000 -ro

# Exclude specific URLs
npx blc https://sgcarstrends.com -ro \
  --exclude "https://twitter.com/*" \
  --exclude "https://linkedin.com/*"

# Save results
npx blc https://sgcarstrends.com -ro > broken-links.txt

Configuration File

// .blcrc
{
  "filterLevel": 3,
  "recursive": true,
  "excludeExternalLinks": false,
  "excludeInternalLinks": false,
  "excludeLinksToSamePage": true,
  "honorRobotExclusions": true,
  "maxSocketsPerHost": 1,
  "rateLimit": 500,
  "excludedKeywords": [
    "localhost",
    "127.0.0.1",
    "twitter.com",
    "linkedin.com"
  ]
}

CI Integration

GitHub Actions

# .github/workflows/check-links.yml
name: Check Links

on:
  pull_request:
    paths:
      - "apps/docs/**"
      - "**/*.md"
  schedule:
    - cron: "0 0 * * 0"  # Weekly on Sunday

jobs:
  check-markdown-links:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check markdown links
        uses: gaurav-nelson/github-action-markdown-link-check@v1
        with:
          config-file: ".markdown-link-check.json"
          folder-path: "apps/docs"
          file-extension: ".md"

  check-mintlify-links:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install

      - name: Check Mintlify broken links
        run: |
          cd apps/docs
          pnpm mintlify broken-links

  check-website-links:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install

      - name: Build website
        run: pnpm -F @sgcarstrends/web build

      - name: Start website
        run: |
          cd apps/web
          pnpm start &
          sleep 10

      - name: Check website links
        run: npx blc http://localhost:3000 -ro --exclude "*/twitter.com/*"

      - name: Stop website
        run: pkill -f "next start"

Common Link Issues

1. Relative Path Errors

<!-- ❌ Wrong relative path -->
[API Docs](../api/cars.md)  <!-- File doesn't exist -->

<!-- ✅ Correct relative path -->
[API Docs](./api/cars.md)

<!-- ✅ Absolute path from root -->
[API Docs](/docs/api/cars.md)

2. Case Sensitivity

<!-- ❌ Case mismatch (on Linux/macOS) -->
[README](./readme.md)  <!-- File is README.md -->

<!-- ✅ Correct case -->
[README](./README.md)

3. Missing Anchors

<!-- ❌ Anchor doesn't exist -->
[Installation](#instalation)  <!-- Typo: instalation -->

<!-- ✅ Correct anchor -->
[Installation](#installation)

<!-- ✅ Check anchor exists in target file -->
[API Reference](./api.md#get-cars)

4. External Link Issues

# Check if external links are accessible
curl -I https://example.com/api/docs

# If 404, update or remove link

# ❌ Broken external link
[Docs](https://old-site.com/docs)  # Site moved

# ✅ Updated link
[Docs](https://new-site.com/docs)

Fix Broken Links

Find and Replace

# Find all occurrences of broken link
grep -r "old-domain.com" apps/docs/

# Replace in all files
find apps/docs -name "*.md" -exec sed -i '' 's/old-domain.com/new-domain.com/g' {} +

# Or use modern tools
fd -e md -x sd 'old-domain.com' 'new-domain.com'

Automated Fix Script

#!/bin/bash
# fix-links.sh

# Check if file has broken links
check_links() {
  local file=$1
  echo "Checking $file..."

  # Run markdown-link-check
  if ! markdown-link-check "$file" -c .markdown-link-check.json; then
    echo "Broken links found in $file"
    return 1
  fi

  return 0
}

# Fix common issues
fix_common_issues() {
  local file=$1

  # Fix relative paths
  sed -i '' 's|\.\./\.\./docs|/docs|g' "$file"

  # Fix case sensitivity
  sed -i '' 's|readme\.md|README.md|g' "$file"

  # Update old domain
  sed -i '' 's|old-site\.com|new-site.com|g' "$file"
}

# Main
for file in $(find apps/docs -name "*.md"); do
  if ! check_links "$file"; then
    echo "Attempting to fix $file..."
    fix_common_issues "$file"
    check_links "$file"
  fi
done

Link Validation in Code

Validate Links in Tests

// apps/docs/__tests__/links.test.ts
import { describe, it, expect } from "vitest";
import fs from "node:fs";
import path from "node:path";

describe("Documentation Links", () => {
  const docsDir = path.join(process.cwd(), "apps/docs");

  it("should have no broken internal links", async () => {
    const files = getAllMarkdownFiles(docsDir);
    const brokenLinks: string[] = [];

    for (const file of files) {
      const content = fs.readFileSync(file, "utf-8");
      const links = extractInternalLinks(content);

      for (const link of links) {
        const targetPath = resolveLink(file, link);
        if (!fs.existsSync(targetPath)) {
          brokenLinks.push(`${file}: ${link} -> ${targetPath}`);
        }
      }
    }

    expect(brokenLinks).toEqual([]);
  });

  it("should have valid anchor links", async () => {
    const files = getAllMarkdownFiles(docsDir);
    const brokenAnchors: string[] = [];

    for (const file of files) {
      const content = fs.readFileSync(file, "utf-8");
      const anchorLinks = extractAnchorLinks(content);
      const headings = extractHeadings(content);

      for (const anchor of anchorLinks) {
        const anchorId = anchor.replace("#", "").toLowerCase();
        if (!headings.includes(anchorId)) {
          brokenAnchors.push(`${file}: ${anchor}`);
        }
      }
    }

    expect(brokenAnchors).toEqual([]);
  });
});

function getAllMarkdownFiles(dir: string): string[] {
  // Recursively get all .md files
  const files: string[] = [];
  const entries = fs.readdirSync(dir, { withFileTypes: true });

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory() && entry.name !== "node_modules") {
      files.push(...getAllMarkdownFiles(fullPath));
    } else if (entry.isFile() && entry.name.endsWith(".md")) {
      files.push(fullPath);
    }
  }

  return files;
}

function extractInternalLinks(content: string): string[] {
  const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
  const links: string[] = [];
  let match;

  while ((match = linkRegex.exec(content)) !== null) {
    const url = match[2];
    // Internal links start with ./ or / or are relative
    if (!url.startsWith("http://") && !url.startsWith("https://")) {
      links.push(url.split("#")[0]); // Remove anchor
    }
  }

  return links;
}

function extractAnchorLinks(content: string): string[] {
  const linkRegex = /\[([^\]]+)\]\((#[^)]+)\)/g;
  const anchors: string[] = [];
  let match;

  while ((match = linkRegex.exec(content)) !== null) {
    anchors.push(match[2]);
  }

  return anchors;
}

function extractHeadings(content: string): string[] {
  const headingRegex = /^#+\s+(.+)$/gm;
  const headings: string[] = [];
  let match;

  while ((match = headingRegex.exec(content)) !== null) {
    const heading = match[1]
      .toLowerCase()
      .replace(/[^\w\s-]/g, "")
      .replace(/\s+/g, "-");
    headings.push(heading);
  }

  return headings;
}

function resolveLink(fromFile: string, link: string): string {
  const dir = path.dirname(fromFile);
  return path.resolve(dir, link);
}

OpenAPI Link Validation

Check API Documentation Links

# Validate OpenAPI spec links
npx @redocly/cli lint apps/docs/api/openapi.json

# Check for broken API endpoints
curl -f https://api.sgcarstrends.com/v1/cars || echo "Endpoint broken"

# Validate all endpoints in spec
jq -r '.paths | keys[]' apps/docs/api/openapi.json | while read endpoint; do
  echo "Checking $endpoint..."
  curl -f "https://api.sgcarstrends.com$endpoint" || echo "Broken: $endpoint"
done

Best Practices

1. Regular Checks

# ✅ Check links weekly
# Set up CI schedule or local cron job

# Weekly check
0 0 * * 0 cd /path/to/repo && make check-links

2. Use Absolute Paths

<!-- ❌ Relative paths break when moving files -->
[API](../../api/cars.md)

<!-- ✅ Absolute paths from docs root -->
[API](/docs/api/cars.md)

<!-- ✅ Or use full URL -->
[API](https://docs.sgcarstrends.com/api/cars)

3. Test Before Committing

# ✅ Pre-commit hook
# .husky/pre-commit
#!/bin/sh

# Check links in staged markdown files
git diff --cached --name-only --diff-filter=ACMR | grep "\.md$" | \
  xargs -I {} markdown-link-check {} -c .markdown-link-check.json

4. Document External Dependencies

<!-- ✅ Note when external links are critical -->
<!-- IMPORTANT: This link is referenced in production -->
[API Reference](https://api.sgcarstrends.com/docs)

<!-- ✅ Or use link checker config to monitor -->

Troubleshooting

False Positives

// .markdown-link-check.json
{
  "ignorePatterns": [
    {
      "pattern": "^http://localhost"
    },
    {
      "pattern": "^https://example.com"  // Ignore example URLs
    }
  ],
  "aliveStatusCodes": [200, 206, 301, 302, 403]  // 403 might be valid
}

Rate Limiting

# Issue: Rate limited by external sites
# Solution: Slow down checks

# Use rate limit in config
{
  "timeout": "10s",
  "retryOn429": true,
  "retryCount": 3
}

# Or check with delay
for file in *.md; do
  markdown-link-check "$file"
  sleep 2
done

Private Links

# Issue: Private URLs return 401/403
# Solution: Add authentication

# Use httpHeaders in config
{
  "httpHeaders": [
    {
      "urls": ["https://private-api.com"],
      "headers": {
        "Authorization": "Bearer ${API_TOKEN}"
      }
    }
  ]
}

References

Best Practices Summary

  1. Check Regularly: Run link checks weekly or before releases
  2. Automate: Use CI to catch broken links early
  3. Absolute Paths: Prefer absolute paths for documentation links
  4. Configure Ignores: Exclude localhost and example URLs
  5. Monitor External: Track critical external dependencies
  6. Test Locally: Validate links before committing
  7. Fix Quickly: Address broken links as soon as detected
  8. Document: Note critical external links in config