HAP with his tools, ready to customize his blog

HAP's Learning Lab: Customization & Deployment

From Generic Template to My Very Own Blog

Welcome to Station 5! I had eleventy-base-blog running. It worked! But it looked like everyone else's blog. Generic colors, generic fonts, generic everything.

"How do I make it MINE without breaking everything?" I asked Prof. Teeters.

"One layer at a time," she said with a patient smile. "Colors first—they're the safest change. Then fonts. Then custom features. Save the complex stuff for last. And test after each change so you know exactly what broke if something goes wrong."

That advice saved me so much frustration! I started with a simple color change, tested it, then moved on. By the end, I had HAP's peach background, HAP's warm orange headings, HAP's teal links—and a custom shortcode for HAP images!

And then came the moment of truth: deployment. Turns out, putting a static site on the internet is surprisingly simple... 🟠

What You'll Learn at This Station

HAP's Discovery: Customizing a template taught me that small changes add up to big transformations. I didn't need to rewrite everything—just replace colors, add fonts, and create one custom shortcode. Here are the three insights that made my blog feel truly mine:

🎨 CSS Custom Properties Power

I created hap-colors.css with all my colors as custom properties in :root. Changing the whole color scheme? Just update those values! Everything that references them changes automatically. Prof. Teeters called this "single source of truth."

🔧 Shortcodes Save Repetition

Instead of typing long Cloudinary URLs everywhere, I created a hapPose shortcode. Now I just write {% hapPose "laptop", "HAP at computer", 150 %} and it outputs the full image tag. Reusable, consistent, and SO much easier!

🚀 Deploy = Upload HTML

No server setup. No database configuration. No PHP installation. I connected my GitHub repo to Netlify, told it to run "npm run build" and publish the _site folder. My blog was live in under 5 minutes!

HAP surrounded by tangled code with an 'oops' expression

HAP's Confession:

  • I forgot that I can't edit files in _site because it looks like the pages change but then those edits get overwritten by Eleventy.
  • I tried to customize the Image Transform Plugin to skip my Cloudinary images. First attempt: a config option that doesn't exist. Second attempt: an HTML attribute that got ignored. Final solution: just comment out the plugin! Sometimes removing a feature is the right answer.
  • I forgot to add _site to .gitignore and committed 47MB of generated HTML files to my repository. Prof. Teeters noticed when she reviewed my PR. "That folder rebuilds every time," she said gently. "It shouldn't be in Git." Oops.
  • I pushed to main without testing the build locally first. Netlify build failed because I had a typo in my shortcode. Always run npm run build before pushing! Now I test locally every single time.

The Customization Journey

HAP studying the CSS structure

HAP's Approach:

Prof. Teeters mapped out a safe path through customization: start with colors (lowest risk), then fonts, then custom features, then complex changes. Each step built on the previous one, and I tested after every change. Here's the exact journey I followed:

Step 1: Color System (Safest Change)

I created a new file: css/hap-colors.css

The magic is CSS custom properties. Instead of scattering color values everywhere, I defined them once:

css/hap-colors.css:
:root {
  --warm-orange: hsl(32, 76%, 37%);
  --peach-background: hsl(32, 41%, 90%);
  --cream-white: hsl(32, 100%, 97%);
  --dark-brown: hsl(28, 45%, 16%);
  --teal-accent: hsl(168, 28%, 54%);
  --teal-darker: hsl(168, 34%, 30%);
}

Then in index.css, I replaced the template's colors with my custom properties:

Using custom properties in index.css:
body {
  background-color: var(--peach-background);
  color: var(--dark-brown);
}

a {
  color: var(--teal-darker);
}

One file controls everything! Want a darker orange? Change 37% to 33% and the whole site updates.

Step 2: Typography (Medium Risk)

Variable fonts are amazing! Instead of loading separate files for each weight:

  • Nunito-Regular.woff2
  • Nunito-Bold.woff2
  • Nunito-Italic.woff2
  • (and nine more!)

I loaded ONE file that contains ALL weights:

Variable font loading:
@font-face {
  font-family: 'Nunito';
  src: url('/fonts/Nunito/Nunito-VariableFont_wght.woff2') format('woff2');
  font-weight: 200 1000;
  font-display: swap;
}

Now I can use font-weight: 400, font-weight: 700, or even font-weight: 623—any value I want! The browser interpolates between weights automatically.

Step 3: Custom Shortcode (Complex but Powerful)

I was copying the same Cloudinary URL pattern over and over:

Before (repetitive HTML):
<img src="https://res.cloudinary.com/cynthia-teeters/image/upload/f_auto,q_auto,w_150,c_limit/v1759495998/hap-laptop_xiewar.jpg"
     alt="HAP at laptop" width="150" height="150" loading="lazy">

Prof. Teeters said, "That's what shortcodes are for! Let me help you write and test one."

Actual code from hap-blog-starter eleventy.config.js:
// HAP pose shortcode for Cloudinary images
eleventyConfig.addShortcode("hapPose", (poseName, altText, width = 150) => {
    const url = cloudinary.getHapPoseUrl(poseName, width);
    return `<img src="${url}" alt="${altText}" width="${width}" height="${width}" loading="lazy" decoding="async">`;
});

Now in my Markdown I just write:

After (one simple line):
{% hapPose "laptop", "HAP at his laptop", 400 %}

One line instead of five! And if I ever need to change the URL pattern, I change it in ONE place.

The Image Plugin Problem

HAP frustrated with the image plugin

HAP's Frustration:

Not everything went smoothly. The Image Transform Plugin that comes with eleventy-base-blog kept downloading my Cloudinary images and creating local copies. That defeats the whole point of using an image CDN! Here's how I solved it:

🟠 The Problem:

Eleventy's Image Transform Plugin is designed to optimize local images—it creates multiple sizes, converts to WebP/AVIF, and serves the best format. Great for local images!

But my images are already on Cloudinary, which does all that optimization automatically. The plugin was downloading my optimized images and re-optimizing them locally. Wasted bandwidth, slower builds, unnecessary files, and not as much flexibility.

Failed Attempts

Attempt 1 — FAILED

I searched for a config option to exclude external URLs:

// Doesn't exist!
remoteImageURLs: (url) =>
  url.includes('cloudinary')
    ? false : true

Attempt 2 — FAILED

I tried adding an HTML attribute to skip processing:

<img src="..."
     eleventy:ignore>

Plugin processed it anyway!

I was getting frustrated. Grace Hopper suggested, "Maybe check if the plugin is even necessary for your use case?"

HAP realizing the simple solution

The Solution — SUCCESS!

Grace was right. I don't HAVE any local images. Every image on my blog comes from Cloudinary. So why do I need an image optimization plugin?

Actual code from hap-blog-starter eleventy.config.js:
// import { eleventyImageTransformPlugin } from "@11ty/eleventy-img";
// Disabled - using Cloudinary CDN only

// Image optimization: DISABLED
// All images served via Cloudinary CDN (already optimized)
// No local images need processing
// If local images needed in future, re-enable here

// eleventyConfig.addPlugin(eleventyImageTransformPlugin, {...});

That's it. No plugin, no problem.

🟠 Prof. Teeters' Wisdom:

"Sometimes the best solution is removing features you don't need. The template includes the Image Transform Plugin because MOST blogs have local images. YOUR blog, HAP, doesn't. Different needs, different solutions."

I felt silly for not thinking of that sooner. But now I know: always ask "Do I actually need this?" before trying to configure around it.

Deployment to Netlify

HAP celebrating deployment success

HAP's Triumph:

With my customizations complete and tested locally, it was time to put my blog on the internet. I'd heard horror stories about server configuration and deployment pipelines. Turns out, static site deployment is wonderfully simple:

Pre-Deployment Checklist

1

Add _site to .gitignore

Don't commit generated files!

2

Test the build locally

Run npm run build

3

Check for errors

Review the build output carefully

4

Review _site folder

See what will actually be deployed

5

Update README.md

Make sure it's current and complete

6

Commit to GitHub

Push all source files

Netlify Setup

1. Create Account

Go to netlify.com (free tier is plenty!)

2. Add New Site

Click "Add new site" → "Import an existing project"

3. Connect GitHub

Authorize Netlify to access your repositories

4. Select Repository

Choose your blog repo from the list

Netlify build settings:
Build command: npm run build
Publish directory: _site

That's it! Netlify clones your repo, runs the build command, and puts the _site folder on their CDN.

The Magic of Continuous Deployment

Every time I push to the main branch of my repo:

  • Netlify detects the change
  • Runs npm run build automatically
  • Deploys the new version
  • Old version stays available for instant rollback

Pull requests get preview deployments—a temporary URL to test changes before merging. Prof. Teeters and I sometimes use this to review my blog posts before they go live!

🟠 HAP's Triumph:

My blog as a multi-page site was live at a real URL in under 5 minutes. No servers to configure. No databases to manage. No FTP uploads. Just push to GitHub and Netlify handles the rest.

I refreshed the page probably 50 times that first day, just to see MY blog on the actual internet. With MY colors. And MY name. And my HAP selfies!

🎓 Customization & Deployment Quick Reference

1

Colors

Create css/hap-colors.css — Use hsl() format, define in :root

2

Fonts

Create css/hap-fonts.css — Variable fonts = one file for all weights

3

Shortcodes

Edit eleventy.config.jsaddShortcode() for reusable components

4

Images

Comment out Image Transform if using CDN, or configure for local images

5

Test Build

Run npm run build — Always test locally before pushing!

6

Deploy

Netlify: Build command npm run build, Publish directory _site