لو موقعك فيه 300 مقال وبتربط لمصادر خارجية في كل واحد منهم، غالبًا عندك دلوقتي ما بين 40 و 70 رابط بيرجّع HTTP 404 أو DNS error — وانت مش عارف. المقال ده بيوريك إزاي تأتمت الفحص كل أسبوع، تاخد تقرير مرتّب، وتفضّل ماسك موقعك قبل ما Google يخصم منه ترتيب.
الموقف باختصار: ليه الروابط الخارجية بتموت
لمّا بتكتب مقال تقني وبتحط رابط لـ توثيق أو مقال مرجعي، إنت بتعمل وعد للقارئ: "الرابط ده شغّال". المشكلة إن الوعد ده بيتكسر بشكل طبيعي بمرور الوقت. الشركات بتعيد هيكلة مواقعها، الدومينات بتنتهي، الصفحات بتتنقل من /blog/x إلى /docs/x. دراسة Ahrefs من 2023 على 2 مليون صفحة لقيت إن 66.5% من الروابط القديمة بتموت خلال 9 سنين. المعدل السنوي تقريبًا 7-8% لو ما فحصتش يدويًا.
خلينا نقرّب المفهوم بمثال بسيط قبل ما نتعمّق. تخيّل إنك عندك مكتبة فيها 500 كتاب، وكل كتاب بيحيل للقارئ على فهرس موجود في كتاب تاني. كل سنة، حوالي 35 كتاب بيتلغي. لو مفيش حد بيتفقّد الفهارس، بعد 3 سنين القارئ بيفتح كتاب، يطلب مرجع، ويلاقي الصفحة فاضية. ده بالظبط اللي بيحصل في موقعك لما ما تفحصش الروابط.
ليه Lychee وليس غيره
الأداة اللي هنستخدمها اسمها Lychee. مكتوبة بـ Rust، مفتوحة المصدر، وبتفحص الروابط بشكل متوازي. فيه بدايل زي linkchecker (Python) أو html-proofer (Ruby)، لكن الفرق رقمي وواضح:
- Lychee: بيفحص 10,000 رابط في حوالي 55 ثانية على GitHub Actions runner.
- linkchecker: نفس العدد بياخد 12-15 دقيقة.
- html-proofer: نفس العدد بياخد 8-10 دقيقة.
الافتراض هنا إن عندك اتصال إنترنت طبيعي من GitHub، ومفيش rate limiting صارم على الدومينات اللي بتربطها.
تعريف دقيق للمفهوم
Lychee بيشتغل كـ HTTP client متوازي (بـ tokio async runtime)، بيرسل طلبات HEAD بدل GET عشان يوفّر bandwidth. لو الرابط رجّع 405 أو 403 على HEAD، بيعمل fallback تلقائي لـ GET. بيدعم استخراج الروابط من Markdown, HTML, reStructuredText، وحتى Org-mode. فيه نظام cache مدمج بيخلّيه ما يفحصش نفس الرابط مرتين في نفس الـ run.
الإعداد الكامل — خطوة بخطوة
الـ workflow ده بيشتغل كل أحد الساعة 2 صباحًا UTC (5 صباحًا بتوقيت القاهرة)، يفحص كل ملفات الموقع، ويفتح Issue تلقائي لو لقى روابط مكسورة. حط الملف ده في .github/workflows/link-check.yml:
name: Link Checker
on:
schedule:
- cron: '0 2 * * 0'
workflow_dispatch:
jobs:
linkChecker:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/checkout@v4
- name: Restore lychee cache
uses: actions/cache@v4
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-
- name: Run Lychee
id: lychee
uses: lycheeverse/lychee-action@v2
with:
args: >-
--cache
--max-cache-age 7d
--no-progress
--exclude-mail
--exclude-path './node_modules'
--exclude-path './.git'
--timeout 20
--max-retries 2
'./**/*.md'
'./**/*.mdx'
'./**/*.html'
fail: false
output: ./lychee-report.md
- name: Create Issue from report
if: steps.lychee.outputs.exit_code != 0
uses: peter-evans/create-issue-from-file@v5
with:
title: 'تقرير الروابط المكسورة — ${{ github.run_id }}'
content-filepath: ./lychee-report.md
labels: broken-links, maintenance