Complete step-by-step workflow from making code changes to live deployment.
⚠️ IMPORTANT: All testing happens locally. GitHub Actions only builds and deploys.
# 1. Make changes
vim _portfolio/31-new-work.md
# 2. Preview locally (optional)
bundle exec jekyll serve # View at http://localhost:4000
# 3. Commit and push (tests run automatically via Lefthook)
git add .
git commit -m "Add new portfolio work"
git push origin main
# ✅ Done! Hooks test automatically, GitHub deploys if tests pass
# No need to run ./test-before-push.sh manually - hooks do it for you!
bundle exec jekyll serve # Start local dev server
git add . # Stage changes
git commit -m "message" # Commit (triggers YAML validation on staged files)
git push origin main # Push (triggers ./test-before-push.sh automatically)
# Optional - only if you want to test BEFORE committing:
./test-before-push.sh # Manual test run (catches errors early)
⭐ RECOMMENDED FOR MOST USERS - Lefthook handles testing automatically
Important: You do NOT need to run ./test-before-push.sh manually. The pre-push hook runs it automatically when you git push. Only run the script manually if you want to catch errors early during development.
Edit any files in the project:
# Edit portfolio work
vim _portfolio/31-new-work.md
# Edit bio page
vim bio.markdown
# Edit styles
vim _sass/main.scss
# Edit config
vim _config.yml
Start the Jekyll development server:
bundle exec jekyll serve
What happens:
_site/ directoryOutput:
Configuration file: /home/synse/DEV/wwwj3zz/website/_config.yml
Source: /home/synse/DEV/wwwj3zz/website
Destination: /home/synse/DEV/wwwj3zz/website/_site
Generating...
done in 1.649 seconds.
Server address: http://127.0.0.1:4000/
Server running... press ctrl-c to stop.
Keep this running in one terminal while you work. Open browser to http://localhost:4000
Note: Changes to _config.yml require server restart (Ctrl+C, then run command again).
Add files to git staging area:
# Stage all changes
git add .
# Or stage specific files
git add _portfolio/31-new-work.md
git add bio.markdown
Check what will be committed:
git status
Output:
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: _portfolio/31-new-work.md
new file: assets/images/work31/image.jpg
Commit with a descriptive message:
git commit -m "Add new portfolio work: Interactive Installation"
What happens automatically:
Success output:
LEFTHOOK (pre-commit):
EXECUTE > yaml-lint
✓ YAML linting passed!
[main abc1234] Add new portfolio work: Interactive Installation
2 files changed, 150 insertions(+)
create mode 100644 _portfolio/31-new-work.md
Failure output:
LEFTHOOK (pre-commit):
EXECUTE > yaml-lint
_portfolio/31-new-work.md
42:81 warning line too long (151 > 150 characters) (line-length)
❌ YAML validation failed. Fix the errors above before committing.
SUMMARY: (SKIP BY LEFTHOOK ENV=0)
🥊 yaml-lint
ERROR: (pre-commit) - "yaml-lint" failed with exit code 1
If commit is blocked:
git add .git commit -m "message"Push your committed changes:
git push origin main
What happens automatically:
./test-before-push.sh script automaticallyThis is the same as running ./test-before-push.sh manually - you don’t need to run it yourself unless you want to test during development before committing.
Success output:
LEFTHOOK (pre-push):
EXECUTE > full-tests
═══════════════════════════════════════════════════════════
Pre-Push Testing - Local Validation
═══════════════════════════════════════════════════════════
⚠️ IMPORTANT: This is your ONLY test gate!
GitHub Actions does NOT run tests - it only builds and deploys.
→ Step 1/5: YAML Linting
✓ YAML linting passed!
→ Step 2/5: Building Jekyll Site
✓ Jekyll build completed successfully!
→ Step 3/5: HTML Validation & Link Checking
✓ HTML validation passed!
→ Step 4/5: Print Testing
✓ Print tests passed!
→ Step 5/5: Lighthouse CI (Skipped)
═══════════════════════════════════════════════════════════
Test Results Summary
═══════════════════════════════════════════════════════════
✓ All tests passed! ✨
Your changes are ready to push.
SUMMARY: (SKIP BY LEFTHOOK ENV=0)
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 1.23 KiB | 1.23 MiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
To github.com:yourusername/website.git
abc1234..def5678 main -> main
Failure output:
LEFTHOOK (pre-push):
EXECUTE > full-tests
═══════════════════════════════════════════════════════════
Step 3/5: HTML Validation & Link Checking
═══════════════════════════════════════════════════════════
✗ HTML validation failed!
- _site/works/new-work/index.html
* internally linking to /assets/missing-image.jpg, which does not exist
(line 42)
═══════════════════════════════════════════════════════════
Test Results Summary
═══════════════════════════════════════════════════════════
✗ Some tests failed!
Please fix the errors listed above, then run this script again.
❌ Tests failed! Push blocked.
Fix the errors above, then run again:
./test-before-push.sh
Once all tests pass, try pushing again.
SUMMARY: (SKIP BY LEFTHOOK ENV=0)
🥊 full-tests
ERROR: (pre-push) - "full-tests" failed with exit code 1
If push is blocked:
git add .git commit -m "Fix missing image"git push origin mainOnce push succeeds, GitHub Actions automatically:
bundle exec jekyll buildNO TESTING happens in GitHub Actions - it only builds and deploys.
Watch GitHub Actions progress:
Timeline:
Verify site is live:
# Open your site
open https://www.j3zz.com
# Or check specific page
open https://www.j3zz.com/works/new-work/
Success indicators:
For when you want to test DURING DEVELOPMENT (before committing/pushing)
When to use this: You want to catch errors early while developing, instead of waiting until push time.
When NOT needed: The pre-push hook already runs ./test-before-push.sh automatically, so you don’t need to run it manually unless you want faster feedback during development.
Same as standard workflow:
vim _portfolio/31-new-work.md
Option A: Run all tests (recommended)
./test-before-push.sh
Option B: Run quick YAML check only
./test-before-push.sh --quick
# or
yamllint .
Option C: Run individual tests
# YAML validation
yamllint .
# Jekyll build
bundle exec jekyll build
# HTML validation
bundle exec rake test
# Print tests
npm run test:print
# Lighthouse (optional, slow)
npm run lighthouse
If tests fail:
Once tests pass manually:
git add .
git commit -m "Add new work"
git push origin main
Note: Lefthook pre-push hook will run ./test-before-push.sh AGAIN automatically. Since you already fixed all issues, the tests will pass quickly (they’re just validating what you already tested). This redundancy is intentional - it ensures nothing slips through.
⚠️ USE WITH EXTREME CAUTION - Only when absolutely necessary
ONLY skip hooks if:
./test-before-push.sh manually and all tests passedNEVER skip hooks if:
Skip for single commit:
git commit --no-verify -m "message"
# or
LEFTHOOK=0 git commit -m "message"
Skip for single push:
git push --no-verify origin main
# or
LEFTHOOK=0 git push origin main
Example workflow with skip:
# 1. Test manually FIRST
./test-before-push.sh
# ✓ All tests passed!
# 2. Commit normally (will re-run YAML check)
git add .
git commit -m "Update documentation"
# 3. Skip pre-push hook (you already tested)
LEFTHOOK=0 git push origin main
⚠️ WARNING: Skipping hooks means GitHub will deploy without any quality checks. If there are errors, your site may break!
Run these commands once when setting up the project:
git clone https://github.com/yourusername/website.git
cd website
# Ruby dependencies (Jekyll, html-proofer)
bundle install
# Node.js dependencies (Lighthouse, Lefthook)
npm install
# Python dependencies (yamllint)
pip install yamllint
# or
pip3 install yamllint
npx lefthook install
Verify installation:
ls -la .git/hooks/
# Should see: pre-commit, pre-push
# Run test script
./test-before-push.sh
# Should see all tests pass
# Start development server
bundle exec jekyll serve
# Make changes and follow standard workflow
After setting up the project, you can configure optional features like SEO, Google Analytics, and other integrations.
The site includes comprehensive SEO optimization powered by jekyll-seo-tag and jekyll-sitemap plugins.
What’s already configured:
/sitemap.xmlTo customize SEO settings:
Edit _config.yml:
# Author information for SEO
author:
name: Your Name
email: your@email.com
twitter: yourusername
# Social profiles for structured data
social:
name: Your Name
links:
- https://bandcamp.com/yourusername
- https://soundcloud.com/yourusername
- https://youtube.com/@yourusername
# Add all your social profile URLs
# Additional SEO settings
tagline: "Your site tagline for SEO"
default_image: /assets/img/your-default-image.png
lang: en_US # or your language code
After editing:
# Restart Jekyll server to apply config changes
bundle exec jekyll serve
The site includes privacy-compliant Google Analytics 4 integration with GDPR cookie consent.
To enable analytics:
G-XXXXXXXXXX)Update configuration:
Edit _config.yml:
# Replace the placeholder with your actual Measurement ID
google_analytics: G-XXXXXXXXXX # Your actual GA4 measurement ID
bundle exec jekyll serve
Privacy features:
To disable analytics:
google_analytics line in _config.ymlgoogle_analytics: ""The site includes a GDPR-compliant cookie consent system that’s automatically enabled.
What it does:
No configuration needed - it works automatically with Google Analytics integration.
Customization:
To customize cookie banner styling, edit assets/css/style.css and search for .cookie-consent-banner.
To enable the newsletter signup form on the contact page:
<form action="...">b_XXXXXXXXXX_XXXXXXXXXX)Update configuration:
Edit _config.yml:
mailchimp_action_url: "https://XXXX.usX.list-manage.com/subscribe/post?u=XXXXXX&id=XXXXXX"
mailchimp_bot_field: "b_XXXXXXXXXX_XXXXXXXXXX"
bundle exec jekyll serve
Note: If not configured, the form shows “Newsletter signup coming soon.”
Update your social media usernames in _config.yml:
# Social media usernames (without @ or full URLs)
bandcamp_username: yourusername
soundcloud_username: yourusername
youtube_username: @yourusername
vimeo_username: yourusername
facebook_username: yourusername
instagram_username: yourusername
twitter_username: yourusername
linkedin_username: yourusername
github_username: yourusername
Remember: After any _config.yml changes, restart the Jekyll server!
┌─────────────────────────────────────────────────────────────┐
│ YOUR LOCAL MACHINE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Make Changes │
│ ├─ Edit files: vim _portfolio/31-new-work.md │
│ ├─ Preview: bundle exec jekyll serve │
│ └─ View: http://localhost:4000 │
│ │
│ Step 2: Stage Changes │
│ └─ Command: git add . │
│ │
│ Step 3: Commit Changes │
│ ├─ Command: git commit -m "message" │
│ ├─ Hook runs: pre-commit (YAML validation) │
│ ├─ If pass: Commit succeeds ✓ │
│ └─ If fail: Commit blocked ✗ → Fix errors → Try again │
│ │
│ Step 4: Push to GitHub │
│ ├─ Command: git push origin main │
│ ├─ Hook runs: pre-push (ALL tests) │
│ │ ├─ YAML linting │
│ │ ├─ Jekyll build │
│ │ ├─ HTML validation │
│ │ └─ Print tests │
│ ├─ If all pass: Push succeeds ✓ │
│ └─ If any fail: Push blocked ✗ → Fix errors → Try again │
│ │
└─────────────────┬───────────────────────────────────────────┘
│ Push successful
▼
┌─────────────────────────────────────────────────────────────┐
│ GITHUB ACTIONS (Automatic, ~1-2 minutes) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 5: Build Site │
│ ├─ Checkout code from GitHub │
│ ├─ Setup Ruby environment │
│ ├─ Install dependencies: bundle install │
│ ├─ Build site: bundle exec jekyll build │
│ └─ NO TESTING - Only builds │
│ │
│ Step 6: Deploy │
│ ├─ Upload build artifact (_site/) │
│ └─ Deploy to GitHub Pages │
│ │
└─────────────────┬───────────────────────────────────────────┘
│ Deployment complete
▼
┌─────────────────────────────────────────────────────────────┐
│ LIVE SITE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 7: Site Updated │
│ ├─ URL: https://www.j3zz.com │
│ ├─ Time: ~2-3 minutes total from push │
│ └─ Status: Live and accessible ✓ │
│ │
└─────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ git commit -m "message" │
└────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ PRE-COMMIT HOOK (Lefthook) │
├──────────────────────────────────────────────────────────┤
│ ✓ Runs: yamllint {staged_files} │
│ ✓ Checks: YAML syntax in STAGED files only │
│ ✓ Fast: <1 second │
│ ✓ Purpose: Quick check to catch obvious errors │
│ ✓ If pass → Commit succeeds │
│ ✗ If fail → Commit blocked │
└────────┬─────────────────────────────────────────────────┘
│ Commit successful
▼
┌──────────────────────────────────────────────────────────┐
│ git push origin main │
└────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ PRE-PUSH HOOK (Lefthook) - COMPLETE TEST SUITE │
├──────────────────────────────────────────────────────────┤
│ ✓ Runs: ./test-before-push.sh (automatically!) │
│ ✓ Duration: ~30-60 seconds │
│ ✓ Scope: ALL files in project (not just staged) │
│ │
│ Tests executed (same as manual script): │
│ ├─ [1/5] YAML linting (ALL files) <1 sec │
│ ├─ [2/5] Jekyll build (entire site) ~10 sec │
│ ├─ [3/5] HTML validation (all links) ~20 sec │
│ ├─ [4/5] Print tests (all pages) ~10 sec │
│ └─ [5/5] Lighthouse (skipped) 0 sec │
│ │
│ ✓ All pass → Push succeeds │
│ ✗ Any fail → Push blocked │
└────────┬─────────────────────────────────────────────────┘
│ Push successful
▼
┌──────────────────────────────────────────────────────────┐
│ GitHub Actions → Build → Deploy │
└──────────────────────────────────────────────────────────┘
NOTE: Running ./test-before-push.sh manually is OPTIONAL.
The pre-push hook runs it automatically for you.
Only run manually if you want to catch errors early.
┌──────────────────────────────────────────────────────────┐
│ Push attempt: git push origin main │
└────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Pre-push hook runs tests │
└────────┬──────────────┬──────────────────────────────────┘
│ │
✓ PASS ✗ FAIL
│ │
▼ ▼
┌─────────────┐ ┌──────────────────────────────────────┐
│ Push │ │ Push blocked │
│ succeeds │ │ Error messages shown │
│ ↓ │ │ ↓ │
│ GitHub │ │ 1. Read error messages │
│ deploys │ │ 2. Fix the issues │
│ │ │ 3. git add . │
│ │ │ 4. git commit -m "Fix errors" │
│ │ │ 5. git push origin main │
│ │ │ └─→ Tests run again │
└─────────────┘ └──────────────────────────────────────┘
# Create new work file
vim _portfolio/32-new-installation.md
# Add images
mkdir -p assets/images/work32
cp ~/images/*.jpg assets/images/work32/
# Preview
bundle exec jekyll serve
# Check http://localhost:4000/works/
# Commit and push (tests run automatically via hooks)
git add .
git commit -m "Add new installation work"
git push origin main
# → Pre-push hook runs ./test-before-push.sh automatically
# → If tests pass: push succeeds, GitHub deploys
# → If tests fail: fix errors and push again
# ✅ Site updates in ~2-3 minutes
# Edit bio
vim bio.markdown
# Preview
bundle exec jekyll serve
# Check http://localhost:4000/bio/
# Test during development (optional - catch errors early)
./test-before-push.sh --quick
# ✓ All tests passed!
# Commit and push
git add bio.markdown
git commit -m "Update bio with recent exhibitions"
git push origin main
# → Hook runs ./test-before-push.sh again (passes quickly)
Why test manually here? To catch errors immediately while editing, instead of waiting until push time. The hook will still run the full test suite, but it will pass quickly since you already fixed everything.
# Edit file with broken link
vim _portfolio/28-modular-example.md
# Fix the link
# Save file
# Test manually before pushing
./test-before-push.sh
# ✓ HTML validation passed!
# Commit and push
git add .
git commit -m "Fix broken link in work 28"
git push origin main
# Make significant changes
vim _sass/main.scss
vim _layouts/work.html
# Preview extensively
bundle exec jekyll serve
# Run FULL test suite (including Lighthouse)
./test-before-push.sh --full
# Review Lighthouse reports
open .lighthouseci/*.html
# Review print PDFs
open print-test-results/*.pdf
# If all good, commit and push
git add .
git commit -m "Redesign work pages layout"
git push origin main
# Edit docs
vim docs/testing.md
# No need to preview or test content changes
# Hooks will still run, but will pass quickly
git add docs/testing.md
git commit -m "Fix typo in testing docs"
git push origin main
# Or skip hooks (docs only, low risk)
git add docs/testing.md
git commit -m "Fix typo in testing docs"
LEFTHOOK=0 git push origin main
# Check if hooks are installed
ls -la .git/hooks/
# Should see: pre-commit, pre-push
# Reinstall hooks
npx lefthook install
# Verify Lefthook config
cat lefthook.yml
The pre-push hook runs tests again. If it fails:
# Run manually to see detailed error
./test-before-push.sh
# Fix the errors shown
# Try pushing again
git push origin main
# Run tests anytime
./test-before-push.sh
# Quick YAML check
./test-before-push.sh --quick
# Full test with Lighthouse
./test-before-push.sh --full
# Check if build fails locally with production env
JEKYLL_ENV=production bundle exec jekyll build
# View GitHub Actions logs
# Go to: https://github.com/yourusername/website/actions
# Click failed workflow → Click "build" job → Read logs
# Development
bundle exec jekyll serve # Start dev server
bundle exec jekyll build # Build site
bundle exec jekyll clean # Clean build cache
# Testing (automatic via hooks)
git commit -m "message" # Triggers YAML validation
git push origin main # Triggers full test suite
# Testing (manual)
./test-before-push.sh # All tests (no Lighthouse)
./test-before-push.sh --full # All tests + Lighthouse
./test-before-push.sh --quick # YAML only
yamllint . # YAML validation
bundle exec rake test # HTML validation
npm run test:print # Print tests
npm run lighthouse # Lighthouse only
# Git operations
git status # Check status
git add . # Stage all changes
git add file.md # Stage specific file
git commit -m "message" # Commit with message
git push origin main # Push to GitHub
git log --oneline # View commit history
# Lefthook
npx lefthook install # Install hooks
npx lefthook run pre-commit # Test pre-commit hook
npx lefthook run pre-push # Test pre-push hook
LEFTHOOK=0 git push # Skip hooks (caution!)
# Deployment verification
open https://www.j3zz.com # View live site
# GitHub Actions: https://github.com/yourusername/website/actions
The complete workflow is:
bundle exec jekyll serve (optional)./test-before-push.sh (optional - for early error detection)git add .git commit -m "message" (YAML validation on staged files runs automatically)git push origin main (complete test suite runs automatically via hook)Remember:
./test-before-push.sh - you don’t need to run it manually./test-before-push.sh manually if you want to catch errors early during developmentFor more information: