This is the source code for my personal website and blog, built with Hugo and deployed via GitHub Pages.
Live Site: joshuapsteele.com
Colophon: joshuapsteele.com/colophon - Detailed information about how this site is built
- Static Site Generator: Hugo 0.147.3 (Extended)
- Theme: PaperMod
- Deployment: GitHub Pages with GitHub Actions
- Search: Fuse.js
- Node: 18+ (local) / 20 (CI)
- 📝 Blog with 310+ posts organized by categories and tags
- 📧 "Steele Notes" email newsletter via Buttondown
- 🔍 Full-text search functionality
- 📱 Responsive design with dark mode support
- 📊 RSS and JSON feeds
- 🏷️ Taxonomy management system
- 💬 Comment system integration (Disqus)
- 📈 Analytics integration (Google Analytics, Tinylytics)
- ⚡ Optimized build with minification and caching
- 🌐 IndieWeb Integration:
- Microformats2 markup (h-card, h-entry, h-feed)
- Webmention support for receiving interactions
- Reply context display for reply posts
- IndieAuth for domain-based authentication
- WebFinger for Fediverse discovery
- rel="me" identity verification
- POSSE workflow (Publish Own Site, Syndicate Everywhere)
- Hugo Extended 0.147.3+
- Node.js 18+
- Git
# Clone the repository with submodules
git clone --recurse-submodules https://github.com/joshuapsteele/joshuapsteele.github.io.git
cd joshuapsteele.github.io
# If you forgot --recurse-submodules, initialize them:
git submodule update --init --recursive
# Install dependencies (if any)
npm install# Start local development server with drafts
npm run dev
# Site will be available at http://localhost:1313
# Build for production
npm run build
# Build without minification (faster)
npm run build:fast
# Build with template performance metrics
npm run build:stats
# Clean generated files
npm run clean.
├── content/
│ ├── blog/ # Blog posts (310+ markdown files)
│ ├── pages/ # Static pages (about, contact, cv, now, uses, etc.)
│ └── search.md # Search page
├── layouts/ # Custom Hugo layouts and overrides
│ ├── shortcodes/ # Custom shortcodes (audio, callout, figure, gallery)
│ ├── partials/ # Partial templates (webmentions, analytics, etc.)
│ └── _default/ # Default layouts (h-entry, h-feed markup)
├── static/ # Static assets (images, PDFs, favicons)
├── assets/ # Processed assets (CSS extensions)
├── scripts/ # Maintenance and automation scripts
│ ├── data/ # Configuration files and audit outputs
│ ├── *.py # Python scripts for auditing and content management
│ └── *.sh # Shell scripts for deployment and utilities
├── templates/ # Obsidian blog post templates
├── themes/ # PaperMod theme (git submodule)
├── docs/ # Additional documentation and audit reports
├── public/ # Generated site (git-ignored, never edit)
├── hugo.yaml # Main site configuration
├── CLAUDE.md # AI assistant guidance
└── .github/
└── workflows/
└── hugo.yml # GitHub Actions deployment workflow
# Create a new post file in content/blog/
# Use kebab-case naming: my-new-post.mdExample front matter:
---
title: "My New Post"
date: 2025-11-14
tags: ["hugo", "blogging"]
categories: ["Tech"]
description: "A brief description of the post"
draft: false
---
Your content here...- CSS: Edit
assets/css/extended/custom.css(do not edit theme files directly) - Layouts: Create overrides in
layouts/mirroring theme paths - Shortcodes: Add custom shortcodes in
layouts/shortcodes/
This site is part of the IndieWeb, a community effort to keep the web independent and user-controlled.
- h-card: Machine-readable identity information on the homepage
- rel="me": Verified links to other profiles (Micro.blog, Mastodon)
- WebFinger: Fediverse discovery at
/.well-known/webfinger - IndieAuth: Sign in with your domain at
https://joshuapsteele.com
- h-entry: Blog posts marked up with microformats2 for machine readability
- h-feed: Blog list pages formatted as feeds for IndieWeb readers
- Semantic classes:
p-name,e-content,dt-published,p-author,p-category
- Webmentions: Receive likes, replies, and mentions from across the web
- Powered by webmention.io
- JavaScript-based display of webmentions and Micro.blog conversations on each post
- Grouped by type: likes, reposts, replies, mentions
- Outgoing Webmentions: Recent reply posts are checked during deploy and notify the original post when it advertises a Webmention endpoint
- Syndication links: The deploy fetches Micro.blog's public JSON feed and renders known Mastodon, Threads, and Micro.blog discussion links on canonical posts
Create reply posts by adding in_reply_to to your front matter:
---
title: "My Reply"
date: 2025-12-09
in_reply_to: "https://example.com/original-post"
---The site will automatically:
- Display a reply context card showing the original post
- Mark up the post with
u-in-reply-tofor proper webmention threading - Attempt to fetch and show the original author, title, and excerpt
- Include the reply link in JSON Feed output and attempt an outgoing Webmention during deploy
This site is part of the IndieWeb Webring - a collection of IndieWeb sites linked together. Find the webring navigation in the footer.
Posts are published on this site first, then syndicated to:
scripts/fetch_syndication_links.py maps Micro.blog's _microblog.syndication URLs back to canonical joshuapsteele.com paths and writes data/syndication.json. GitHub Actions runs it before Hugo builds, and a scheduled deploy refreshes those links after Micro.blog finishes asynchronous cross-posting.
The site automatically deploys to GitHub Pages when changes are pushed to the main branch via GitHub Actions.
# Using the deploy script (commits all changes and pushes to main)
npm run deploy
# or with a custom commit message:
./deploy.sh "Your commit message"Important: Always test locally with npm run dev and npm run build before deploying!
The scripts/ directory contains various automation tools for maintaining and analyzing the site. All scripts should be run from the repository root directory.
- deploy.sh - Commits all changes with timestamp (or custom message) and pushes to main branch
./scripts/deploy.sh "Optional commit message" - review_changes.sh - Review staged git changes before committing
- apply-high-traffic-tags.sh - Apply tags to high-traffic posts
- rename_blog_files.sh - Rename dated blog posts to remove date prefixes
⚠️ Review before running - cleanup_images.sh - Remove legacy/external images
⚠️ DANGEROUS: Review carefully before running
Content Auditing:
-
audit-frontmatter.py - Analyze front matter for missing fields and inconsistencies
python3 scripts/audit-frontmatter.py
Outputs:
scripts/data/audit-frontmatter.json -
check-internal-links.py - Check for broken internal links
python3 scripts/check-internal-links.py
Outputs:
scripts/data/audit-internal-links.json -
check-external-links.py - Check for broken external links (may take time)
python3 scripts/check-external-links.py
Outputs:
scripts/data/audit-external-links.json
Content Management:
- cleanup_frontmatter.py - Clean up and standardize front matter fields
- cleanup_posts.py - General post cleanup utilities
- fix_malformed_yaml.py - Fix malformed YAML front matter
- generate_descriptions.py - Generate descriptions for posts missing them
- update_descriptions.py - Update existing descriptions
- show_posts_batch.py - Display posts in batches for review
Taxonomy & Categorization:
- apply-taxonomy.py - Apply taxonomy consolidation based on
scripts/data/taxonomy_map.yamlpython3 scripts/apply-taxonomy.py
- suggest-tags.py - Generate tag suggestions for posts
- categorize-uncategorized.py - Suggest categories for uncategorized posts
- convert-taxonomy-to-kebab-case.py - Convert taxonomy terms to kebab-case
- taxonomy_tools.py - Shared taxonomy utilities
Analytics:
- analyze_website_stats.py - Analyze traffic statistics
- fetch_popular_posts.py - Fetch and analyze popular posts
- categorize_page_changes.py - Categorize and analyze page changes
The site uses a taxonomy consolidation system with configuration files in scripts/data/:
- taxonomy_map.yaml - Master configuration defining category/tag consolidation rules
- taxonomy_map.suggested.yaml - AI-generated suggestions (review before using)
- taxonomy_map.generated.yaml - Generated mapping results from processing
- Always run audit scripts before bulk changes to understand current state
- Review git diff after running cleanup/modification scripts
- Test with
npm run devandnpm run buildafter script-based changes - Back up important configuration files before running destructive scripts
Main configuration is in hugo.yaml. Key settings include:
- Profile Mode: Custom homepage with profile buttons
- Navigation: Top menu and footer customization
- Social: Mastodon, Threads, GitHub, LinkedIn, RSS
- Search: Fuse.js configuration
- Feeds: RSS and JSON output formats
- Build: Image optimization, caching, minification
- CLAUDE.md - Comprehensive guide for AI assistants working with this codebase
- AGENTS.md - Repository guidelines and conventions
- Audit Reports - Various
AUDIT-*.mdfiles with site analysis
This is a personal website, but if you notice any issues or have suggestions:
- Open an issue describing the problem or suggestion
- If you'd like to contribute a fix, fork the repo and submit a pull request
This repository uses a dual-license structure:
The code, scripts, layouts, configuration files, and other technical implementations in this repository are licensed under the MIT License - see LICENSE-CODE file for details.
This includes but is not limited to:
- Hugo configuration (
hugo.yaml) - Layout templates and partials (
layouts/) - Custom shortcodes
- Build scripts and automation tools
- CSS and JavaScript files
- GitHub Actions workflows
All original written content, including blog posts, articles, and pages in the content/ directory, is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - see LICENSE-CONTENT file for details.
This means:
- ✅ You can share and adapt the content with attribution
- ❌ You cannot use it for commercial purposes
- 🔄 Derivative works must use the same license
- 📝 You must provide proper attribution and indicate changes
In summary: Feel free to learn from and use the site's code and configuration, but please don't republish my writing without permission.
- Website: joshuapsteele.com
- Blog: joshuapsteele.com/blog
- Newsletter: Steele Notes - Subscribe at joshuapsteele.com/follow
- Contact: joshuapsteele.com/contact
- Built with Hugo
- Theme: PaperMod by Aditya Telange
- Hosted on GitHub Pages