Skip to main content

Enjoyment Work
— a Digital Garden 🌱

A Digital Garden: Capturing my daily thoughts and progress, as well as curated ideas with unique synthesis—a personal zettelkasten.

Shipping jekyll-audit — Dogfooding a Jekyll QA CLI on a production site

For over a decade, I’ve shipped sites with Jekyll. It’s simple, battle‑tested, and it fits my writing workflow. But every time I wanted a single CLI to check accessibility, links, HTML validity, and Lighthouse in one go, nothing quite fit. So I built one.

Meet jekyll-audit — an npm CLI that builds/serves your Jekyll site, crawls it, and runs a stack of audits (Lighthouse, Pa11y, Linkinator, HTML validator), then writes a consolidated summary.

I didn’t want another “sample demo.” I ate my own dogfood on a real site: my Jekyll project enjoyment-work (100+ GitHub ⭐), and used the findings to make concrete HTML and accessibility fixes.

Links:

  • jekyll-audit repo: https://github.com/brennanbrown/jekyll-audit
  • enjoyment-work repo: https://github.com/brennanbrown/enjoyment-work

What jekyll-audit does

  • Builds your site with Jekyll (JEKYLL_ENV=production).
  • Serves locally and auto-detects baseurl from _config.yml.
  • Discovers pages via sitemap.xml (or falls back to specified paths).
  • Runs:
    • Lighthouse (performance, SEO, best-practices)
    • Pa11y (accessibility)
    • Linkinator (broken links)
    • HTML validation (WHATWG)
  • Writes JSON (and optionally MD/HTML) reports to a dir (default reports/).
  • Emits a consolidated summary.json and exits non-zero if thresholds fail.

Quick start

Requirements: Node 18+, Ruby/Jekyll.

  • Local (from a clone of jekyll-audit):
    npm install
    npm run build
    node bin/jekyll-audit --help
    node bin/jekyll-audit audit
    
  • npx (recommended):
    npx @brennanbrown/jekyll-audit audit
    
  • Target a running server:
    npx @brennanbrown/jekyll-audit --baseUrl http://127.0.0.1:4000 audit
    
  • Useful flags: ```bash

    Faster link checks (internal only)

    npx @brennanbrown/jekyll-audit audit –linksInternalOnly –linksConcurrency 200 –linksTimeout 15000

Limit to fewer pages and disable sitemap

npx @brennanbrown/jekyll-audit audit –maxPages 20 –noSitemap

Heavier Lighthouse output

npx @brennanbrown/jekyll-audit audit –output full –includeDetails –includeScreenshots


Optional config file `jekyll-audit.config.js`:
```js
/** @type {import('./dist/config/schema.js').AuditConfig} */
export default {
  jekyll: { buildDir: '_site', buildCommand: 'bundle exec jekyll build' },
  crawl:  { useSitemap: true, maxPages: 50, paths: ['/'] },
  thresholds: {
    lighthouse: { performance: 0.8, seo: 0.9, 'best-practices': 0.9 },
    accessibility: { maxIssues: 0 },
    links: { maxBroken: 0 },
    html: { maxErrors: 0 },
  },
  reports: { formats: ['json'], outDir: 'reports' },
};

CI usage (GitHub Actions)

name: Jekyll Audit
on: [push, pull_request]
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Ruby
        if: hashFiles('**/Gemfile') != ''
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true
      - name: Install Jekyll deps
        if: hashFiles('**/Gemfile') != ''
        run: |
          bundle config set --local path 'vendor/bundle'
          bundle install
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - name: Run jekyll-audit
        run: node bin/jekyll-audit audit
      - name: Upload reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: jekyll-audit-reports
          path: reports/

Dogfooding on enjoyment-work

I ran the CLI against my real site and triaged the results. Representative run (summary):

  • Lighthouse: performance 0.90, best-practices 1.00, SEO 1.00
  • Accessibility (Pa11y): 39 issues across 39 pages
  • Links (Linkinator): 33 broken links
  • HTML validator: 464 total errors across 39 pages

Performance/SEO were great. The biggest wins were in markup correctness and link hygiene.

1) Duplicate IDs in templates

  • Files: _pages/tags.md, _pages/categories.md, _pages/dates.md
  • Problem: repeated IDs on list items and containers (invalid HTML, accessibility confusion).
  • Fix: convert repeated id usage to class and keep single, meaningful IDs for headings.

Before (from _pages/tags.md):

<ul id="tag-grid">
  
    <li id="tag-content"><a href="#">markdown</a></li>
  
    <li id="tag-content"><a href="#">jekyll</a></li>
  
    <li id="tag-content"><a href="#">theme</a></li>
  
    <li id="tag-content"><a href="#">test</a></li>
  
    <li id="tag-content"><a href="#">Practical</a></li>
  
    <li id="tag-content"><a href="#">Purpose</a></li>
  
    <li id="tag-content"><a href="#">Inspiration</a></li>
  
    <li id="tag-content"><a href="#">Productivity</a></li>
  
    <li id="tag-content"><a href="#">Habits</a></li>
  
    <li id="tag-content"><a href="#">Planning</a></li>
  
    <li id="tag-content"><a href="#">Writing</a></li>
  
    <li id="tag-content"><a href="#">Goals</a></li>
  
    <li id="tag-content"><a href="#">Creativity</a></li>
  
    <li id="tag-content"><a href="#">Jekyll</a></li>
  
    <li id="tag-content"><a href="#">Markdown</a></li>
  
</ul>

After:

<ul class="tag-grid">
  
    <li class="tag-content"><a href="#">markdown</a></li>
  
    <li class="tag-content"><a href="#">jekyll</a></li>
  
    <li class="tag-content"><a href="#">theme</a></li>
  
    <li class="tag-content"><a href="#">test</a></li>
  
    <li class="tag-content"><a href="#">Practical</a></li>
  
    <li class="tag-content"><a href="#">Purpose</a></li>
  
    <li class="tag-content"><a href="#">Inspiration</a></li>
  
    <li class="tag-content"><a href="#">Productivity</a></li>
  
    <li class="tag-content"><a href="#">Habits</a></li>
  
    <li class="tag-content"><a href="#">Planning</a></li>
  
    <li class="tag-content"><a href="#">Writing</a></li>
  
    <li class="tag-content"><a href="#">Goals</a></li>
  
    <li class="tag-content"><a href="#">Creativity</a></li>
  
    <li class="tag-content"><a href="#">Jekyll</a></li>
  
    <li class="tag-content"><a href="#">Markdown</a></li>
  
</ul>
<h3 id=""></h3>

Why this matters: duplicate IDs break document semantics and can confuse assistive tech. It also creates fragile CSS/JS selectors.

2) Self-closing <br/> → HTML5 <br>

  • Files: _includes/backlinks.html, _includes/related.html, _includes/footer.html, _notes/*.md, _pages/*
  • Problem: HTML5 treats <br/> as non-standard; validators flag it.
  • Fix: <br/><br> in all templates and markdown content.

Examples:

  • _notes/reading-material.md
  • _notes/serendipity-outlook.md
  • _notes/dark-theme-adoption.md
  • _notes/reading-fractailized.md
  • _pages/tags.md, _pages/categories.md, _pages/dates.md

Before:

> "... quoted text ..." <br/><cite>—Source</cite>

After:

> "... quoted text ..." <br><cite>—Source</cite>

3) Void element solidus on <img> and <input>

  • Files: _includes/content.html, _posts/2020-04-01-Exploring-the-features-of-enjoyment-work.md
  • Problem: trailing /> on void elements is unnecessary in HTML5 and often flagged alongside other markup issues.
  • Fix: remove the solidus.

Before:

<img src="/assets/img/feed.png" alt="Feed" width="80%" />

After:

<img src="/assets/img/feed.png" alt="Feed" width="80%">

Also corrected generated HTML strings in _includes/content.html where <img> and <input> were assembled with a /> and converted those to standard HTML5 tags.

4) Placement and structure tweaks around loops

  • File: _pages/tags.md
  • Problem: stray <br> inside loops created awkward structure.
  • Fix: move <br> outside loops to avoid repeated, stray breaks and improve flow for screen readers.

The link scan surfaced 33 broken links. Typical fixes include:

  • Updating moved posts/notes to current slugs.
  • Replacing external dead links with archived copies (e.g., web.archive.org).
  • For “nice to have” external references, gracefully removing or rewording.

I keep link checks in summary mode for CI, then dive into a full detail pass locally when I’m ready to fix.

Takeaways

  • Eat your own dogfood: A real site will surface validation and a11y issues that simple demos never hit.
  • HTML correctness matters: small, systematic fixes (<br/>, duplicate IDs, void-element solidus) significantly clean the validator output and reduce noisy errors that hide the real problems.
  • Keep audits lean for CI: default “summary” outputs are diff‑friendly and fast. When you’re ready to fix, flip on the heavier flags locally.
  • Jekyll is still a great publishing surface: with a modern QA pipeline, it holds up well on performance/SEO and is easy to make accessible.

Try it on your site

  • Minimal run:
    npx @brennanbrown/jekyll-audit audit
    
  • Heavier local analysis:
    npx @brennanbrown/jekyll-audit audit --output full --includeDetails --includeScreenshots --a11yIncludeDetails --linksIncludeDetails --htmlIncludeDetails
    
  • Faster internal-only link checks:
    npx @brennanbrown/jekyll-audit audit --linksInternalOnly --linksConcurrency 200 --linksTimeout 15000
    

If you ship with Jekyll and want a single CLI to answer “Is my site healthy?” across performance, accessibility, HTML, and link quality — give jekyll-audit a spin, and let me know what else you’d like it to check.

Brennan Kenneth Brown

About Brennan Kenneth Brown

Hey there! My name is Brennan, I'm a 29-year-old Métis web developer and content strategist from Winnipeg, Manitoba and currently reside in Calgary, Alberta. I've recently compeleted a Full Stack Developer Program at EvolveU, and I'm looking to help those that need web development work done, or searching for ideas and management for their next content project.