Skip to content

feat(docs): SEO — sitemap, robots.txt, per-page og: tags, canonical URLs #68

feat(docs): SEO — sitemap, robots.txt, per-page og: tags, canonical URLs

feat(docs): SEO — sitemap, robots.txt, per-page og: tags, canonical URLs #68

Workflow file for this run

name: Documentation
on:
push:
branches:
- v5
paths:
- 'docs/**'
- 'Formulas/**'
- '.github/workflows/docs.yml'
pull_request:
paths:
- 'docs/**'
- 'Formulas/**'
- '.github/workflows/docs.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
# Used by VitePress config for og:url, canonical URLs, and sitemap hostname.
# VitePress reads process.env.SITE_URL; defaults to localhost for local dev.
env:
SITE_URL: https://www.fontist.org
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'npm'
cache-dependency-path: docs/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: docs
- name: Validate page generation
run: node generate.js
working-directory: docs
prepare:
needs: validate
if: github.ref_name == 'v5'
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.split.outputs.matrix }}
total: ${{ steps.split.outputs.total }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'npm'
cache-dependency-path: docs/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: docs
- name: Generate pages
run: node generate.js
working-directory: docs
- name: Compute batch matrix
id: split
working-directory: docs
run: |
total=$(find browse -name "*.md" ! -name "index.md" | wc -l | tr -d ' ')
batch_size=500
num_batches=$(( (total + batch_size - 1) / batch_size ))
echo "total=$total" >> $GITHUB_OUTPUT
echo "Splitting $total pages into $num_batches batches"
matrix=$(node -e "
const total = $total;
const batchSize = $batch_size;
const batches = [];
for (let i = 0; i < total; i += batchSize) {
batches.push({
index: batches.length,
start: i,
end: Math.min(i + batchSize, total)
});
}
console.log(JSON.stringify({ include: batches }));
")
echo "matrix=$matrix" >> $GITHUB_OUTPUT
build-main:
needs: prepare
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'npm'
cache-dependency-path: docs/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: docs
- name: Add swap space
run: |
if swapon --show | grep -q '/swapfile'; then
echo "Swap already active:"
else
sudo swapoff /swapfile 2>/dev/null || true
sudo rm -f /swapfile
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "Swap configured:"
fi
free -h
- name: Generate pages
run: node generate.js
working-directory: docs
- name: Build main site (no formula pages)
working-directory: docs
run: |
mkdir -p .browse-staging
find browse -name "*.md" ! -name "index.md" | while read -r f; do
relpath="${f#browse/}"
mkdir -p ".browse-staging/$(dirname "$relpath")"
mv "$f" ".browse-staging/$relpath"
done
NODE_OPTIONS="--max-old-space-size=6144" npx vitepress build
mv .browse-staging .browse-staging-saved
- name: Upload main site artifact
uses: actions/upload-artifact@v4
with:
name: dist-main
path: docs/.vitepress/dist
retention-days: 1
- name: Upload staged pages
uses: actions/upload-artifact@v4
with:
name: staged-pages
path: docs/.browse-staging-saved
include-hidden-files: true
retention-days: 1
build-batch:
needs: [prepare, build-main]
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'npm'
cache-dependency-path: docs/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: docs
- name: Add swap space
run: |
if swapon --show | grep -q '/swapfile'; then
echo "Swap already active:"
else
sudo swapoff /swapfile 2>/dev/null || true
sudo rm -f /swapfile
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "Swap configured:"
fi
free -h
- name: Download staged pages
uses: actions/download-artifact@v4
with:
name: staged-pages
path: docs/.browse-staging
- name: Build batch ${{ matrix.index }}
working-directory: docs
env:
BATCH_START: ${{ matrix.start }}
BATCH_END: ${{ matrix.end }}
run: |
# Generate pages
node generate.js
# Clear browse pages
find browse -name "*.md" ! -name "index.md" -delete
find browse -type d -empty -delete 2>/dev/null || true
# Copy only this batch's pages
echo "Batch ${{ matrix.index }}: pages ${{ matrix.start }}-${{ matrix.end }}"
all_files=($(find .browse-staging -name "*.md" | sort))
for ((i=${BATCH_START}; i<${BATCH_END}; i++)); do
f="${all_files[$i]}"
relpath="${f#.browse-staging/}"
mkdir -p "browse/$(dirname "$relpath")"
cp "$f" "browse/$relpath"
done
batch_count=$(find browse -name "*.md" ! -name "index.md" | wc -l | tr -d ' ')
echo "Building $batch_count pages for batch ${{ matrix.index }}"
NODE_OPTIONS="--max-old-space-size=6144" npx vitepress build
- name: Upload batch artifact
uses: actions/upload-artifact@v4
with:
name: dist-batch-${{ matrix.index }}
path: docs/.vitepress/dist
retention-days: 1
combine:
needs: [build-main, build-batch]
runs-on: ubuntu-latest
steps:
- name: Download main site
uses: actions/download-artifact@v4
with:
name: dist-main
path: dist
- name: Download batch artifacts
uses: actions/download-artifact@v4
with:
pattern: dist-batch-*
path: batches
- name: Merge batch browse pages and assets
# Each batch's VitePress build produces its own content-hashed JS chunks
# (app.HASH.js, browse_index.md.HASH.lean.js, etc.) because the page set
# differs per batch. Browse HTML from batch N references batch-N's hashes,
# so we must merge ALL batches' assets/ into dist/assets/ — otherwise
# client-side navigation 404s on the missing chunks (the SSR HTML still
# works on full page refresh, which masked this for a long time).
run: |
for d in batches/dist-batch-*/; do
[ -d "$d" ] || continue
echo "Merging $d"
if [ -d "$d/browse" ]; then
mkdir -p dist/browse
cp -r "$d/browse/"* dist/browse/ 2>/dev/null || true
fi
if [ -d "$d/assets" ]; then
mkdir -p dist/assets
cp -r "$d/assets/"* dist/assets/ 2>/dev/null || true
fi
done
shell: bash
- name: Rewrite clean URLs to directory style
# VitePress cleanUrls:true emits foo.html and expects the host to
# rewrite /foo/ -> /foo.html. GitHub Pages doesn't, so every browse
# page 404s on its directory-style URL. Convert each non-index .html
# to <name>/index.html so /foo/ resolves natively.
run: |
find dist -type f -name "*.html" ! -name "index.html" | while read -r f; do
dir="$(dirname "$f")/$(basename "$f" .html)"
mkdir -p "$dir"
mv "$f" "$dir/index.html"
done
echo "Rewrote $(find dist -type d -name index.html -exec dirname {} \; | wc -l) pages"
shell: bash
- name: Upload combined artifact
uses: actions/upload-pages-artifact@v4
with:
path: dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
if: github.ref_name == 'v5'
runs-on: ubuntu-latest
needs: combine
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4