Skip to main content
The MilesONerd website is deployed on Netlify with an automated build process that generates the blog post index. This page covers the complete deployment workflow.

Hosting Overview

Deployment Platform

The website is hosted on Netlify, with automatic deployments from the GitHub repository.

Why Netlify?

  • Zero-config static hosting
  • Automatic HTTPS/SSL certificates
  • Global CDN distribution
  • Continuous deployment from Git
  • Serverless functions support (if needed)
  • Built-in form handling

Netlify Configuration

The site is configured via netlify.toml in the root directory:
[build]
  publish = "."
  command = "chmod +x blog/update-posts.sh && ./blog/update-posts.sh"

[build.environment]
  NODE_VERSION = "18"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200
publish = ”.”: Deploy the entire root directorycommand: Run the blog indexing script during build
  • chmod +x blog/update-posts.sh - Make the script executable
  • ./blog/update-posts.sh - Generate the blog post index
NODE_VERSION = “18”: Ensures consistent Node.js version for builds
Even though this is a static site, Node.js is specified for potential future use of build tools.
SPA-style routing: All routes serve index.html with 200 statusThis allows clean URLs like /about instead of /about.html

Build Process

The build process is minimal but essential:
1

Trigger Build

Build is triggered by:
  • Git push to main branch
  • Manual deployment from Netlify dashboard
  • GitHub Actions (weekly scheduled builds)
2

Execute Build Command

Netlify runs the build command:
chmod +x blog/update-posts.sh && ./blog/update-posts.sh
3

Generate Blog Index

The script scans blog/posts/ and creates blog/posts.json
4

Deploy Static Files

All files from the root directory are published to Netlify’s CDN

Blog Update Script

The blog/update-posts.sh script is the core of the build process:
POSTS_DIR="blog/posts"
JSON_FILE="blog/posts.json"

# Initialize JSON array
echo "[" > $JSON_FILE

first=true
for file in $POSTS_DIR/*.md; do
  if [ -f "$file" ]; then
    filename=$(basename "$file")
    title=${filename%.md}
    
    # Convert filename to title (capitalize words)
    title=$(echo $title | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1')
    
    # Extract date from filename (YYYY-MM-DD format)
    date=$(echo $filename | grep -o "^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}")
    if [ -z "$date" ]; then
      date=$(date +"%Y-%m-%d")
    fi
    
    # Add comma separator (except for first entry)
    if [ "$first" = true ]; then
      first=false
    else
      echo "," >> $JSON_FILE
    fi
    
    # Write JSON object
    echo "  {" >> $JSON_FILE
    echo "    \"title\": \"$title\"," >> $JSON_FILE
    echo "    \"path\": \"$file\"," >> $JSON_FILE
    echo "    \"date\": \"$date\"" >> $JSON_FILE
    echo -n "  }" >> $JSON_FILE
  fi
done

# Close JSON array
echo "" >> $JSON_FILE
echo "]" >> $JSON_FILE

echo "posts.json has been updated with $(grep -c "title" $JSON_FILE) posts."
echo "Posts JSON file is located at: $JSON_FILE"

How the Script Works

1

Initialize Variables

Set paths for posts directory and output JSON file
2

Start JSON Array

Write opening bracket to posts.json
3

Loop Through Markdown Files

For each .md file in blog/posts/:
  • Extract filename without extension
  • Convert hyphens to spaces and capitalize words
  • Extract date from filename (if formatted as YYYY-MM-DD)
  • Generate JSON object with title, path, and date
4

Handle JSON Formatting

  • Add commas between entries (but not before first entry)
  • Properly format JSON with indentation
5

Close JSON Array

Write closing bracket and log summary

Example Output

Given a post file blog/posts/05-10-2025.md, the script generates:
[
  {
    "title": "05 10 2025",
    "path": "blog/posts/05-10-2025.md",
    "date": "2025-05-10"
  }
]
The title extraction could be improved to handle more descriptive filenames like 2025-05-10-my-blog-post.md.

GitHub Actions Integration

The repository includes GitHub Actions workflows for automation:

Deploy Workflow

# .github/workflows/deploy.yml
# Triggers on push to main branch
# Notifies Netlify to rebuild

Weekly Deploy

# .github/workflows/deploy-weekly.yml
# Scheduled weekly builds
# Ensures site stays fresh with updated dependencies
# .github/workflows/update-copyright.yml
# Automatically updates copyright year in footer
# Runs annually
These workflows may trigger Netlify builds. Ensure you have sufficient build minutes on your Netlify plan.

Environment Setup

Local Development

No build step required for local development:
1

Clone Repository

git clone https://github.com/MilesONerd/website.git
cd website
2

Generate Blog Index (Optional)

chmod +x blog/update-posts.sh
./blog/update-posts.sh
3

Start Local Server

Use any static server:
# Python 3
python -m http.server 8000

# Node.js (http-server)
npx http-server

# PHP
php -S localhost:8000
4

Open Browser

Navigate to http://localhost:8000
All dependencies (Tailwind, Font Awesome) load from CDN, so no npm install required.

Prerequisites

Minimal requirements:
  • Git: For version control
  • Bash: To run the blog update script
  • Text editor: For editing HTML/CSS/JS
  • Web browser: For testing
Optional:
  • Node.js: If you want to use npm-based servers
  • Python: For simple HTTP server

Deployment Workflow

Manual Deployment

1

Make Changes

Edit HTML, CSS, or add blog posts
2

Test Locally

Run local server and verify changes
3

Generate Blog Index

./blog/update-posts.sh
(Only needed if you added/removed blog posts)
4

Commit and Push

git add .
git commit -m "Your commit message"
git push origin main
5

Automatic Deploy

Netlify detects the push and builds automatically

Continuous Deployment

Every push to main branch triggers:
  1. GitHub Actions: Runs any configured workflows
  2. Netlify Build:
    • Pulls latest code
    • Runs build command
    • Deploys to CDN
  3. Live Update: Site is live within 1-2 minutes

Build Time

Typical build takes 10-20 seconds (just running the bash script)

Redirects and Routing

The redirect configuration enables clean URLs:
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

How Routing Works

URL: /about.htmlResult: Serves about.html directly
URL: /aboutResult: Netlify serves about.html (automatic .html appending)
URL: /blogResult: Serves blog/index.html
URL: /nonexistent-pageResult: Serves index.html with 200 status (SPA-style)
This means 404 pages return 200 status. Consider adding a custom 404.html page.

Adding New Blog Posts

Streamlined workflow for blog content:
1

Create Markdown File

# Use date-based naming
touch blog/posts/$(date +%d-%m-%Y).md
2

Write Content

# My Blog Post Title

Content goes here with **markdown** formatting.

## Subheadings work too

- Lists
- Are
- Supported
3

Test Locally

./blog/update-posts.sh
# Start local server and verify
4

Deploy

git add blog/posts/*.md
git commit -m "Add new blog post"
git push
The build script runs automatically on Netlify, so you don’t need to commit posts.json.

Performance & Optimization

Build Optimization

  • Fast builds: No compilation or transpilation needed
  • Incremental updates: Only blog index regenerates
  • CDN caching: Netlify’s CDN caches static assets globally

Asset Optimization

Current setup:
Asset TypeCurrent ApproachOptimization Opportunity
CSSCustom CSS + Tailwind CDNBuild custom Tailwind CSS with only used classes
JavaScriptVanilla JS + CDN librariesMinify custom JS files
ImagesHosted on GitHub PagesUse Netlify Image CDN or optimize images
FontsSystem fonts + Font Awesome CDNSelf-host Font Awesome subset

Caching Strategy

# dist/_headers
/assets/*
  Cache-Control: public, max-age=31536000, immutable

/*.html
  Cache-Control: public, max-age=0, must-revalidate

/blog/posts.json
  Cache-Control: public, max-age=3600
Currently, the dist/_headers file exists but content is not shown. Consider adding cache headers.

Monitoring & Debugging

Netlify Deploy Logs

Access build logs in Netlify dashboard:
  1. Go to Netlify site dashboard
  2. Click “Deploys” tab
  3. Select a deploy
  4. View full build log

Common Build Issues

Error: Permission denied: blog/update-posts.shSolution: Build command includes chmod +x to fix permissions
Error: Blog page shows “Error loading posts”Solution: Ensure update-posts.sh ran successfully during build
Error: Clean URLs not workingSolution: Verify netlify.toml is in root directory and properly formatted

Testing Builds Locally

Simulate the Netlify build:
# Run the exact build command
chmod +x blog/update-posts.sh && ./blog/update-posts.sh

# Verify output
cat blog/posts.json

# Check for errors
echo $?

Production Checklist

Before deploying to production:
1

Content Review

  • All links work
  • Images load correctly
  • Meta tags are accurate
  • No typos or errors
2

Technical Checks

  • Blog index generates correctly
  • Responsive design works on all devices
  • Forms submit properly (Formspree integration)
  • External scripts load (Tailwind, Font Awesome)
3

Performance

  • Images optimized
  • No console errors
  • Fast page load times
4

SEO & Metadata

  • Page titles unique and descriptive
  • Meta descriptions present
  • Open Graph tags configured
  • Favicon loads correctly

Rollback Procedure

If a deployment breaks the site:
1

Netlify Dashboard Rollback

  1. Go to Netlify site dashboard
  2. Click “Deploys” tab
  3. Find last working deploy
  4. Click “Publish deploy” button
2

Git Revert (Alternative)

# Revert to previous commit
git revert HEAD
git push

# Or reset to specific commit
git reset --hard <commit-hash>
git push --force
Force pushing to main branch can cause issues. Use Netlify’s built-in rollback when possible.

Advanced Configuration

Custom Domain Setup

1

Add Domain in Netlify

Go to Site Settings → Domain Management → Add custom domain
2

Configure DNS

Point your domain’s DNS to Netlify:
A record: @ → 75.2.60.5
CNAME: www → your-site.netlify.app
3

Enable HTTPS

Netlify automatically provisions SSL certificate via Let’s Encrypt

Environment Variables

If you need environment variables:
[build.environment]
  NODE_VERSION = "18"
  # Add custom variables
  API_KEY = "your-api-key"
Currently, no environment variables are needed beyond Node version.

Build Hooks

Create a webhook to trigger builds:
  1. Netlify Dashboard → Site Settings → Build & Deploy → Build Hooks
  2. Create new hook with a name
  3. Copy the webhook URL
  4. Use in external services or scripts:
curl -X POST -d '{}' https://api.netlify.com/build_hooks/YOUR_HOOK_ID

Next Steps

Project Structure

Understand the file organization

Styling Guide

Learn about CSS and design system

Resources

Build docs developers (and LLMs) love