I recently decided to give my blog a visual refresh. While I loved the content, the thumbnails were a mix of generic gradients and random screenshots. I wanted something that felt cohesive, technical, and aligned with the "terminal/cyberpunk" aesthetic of my portfolio.

Instead of manually designing a cover image for every new post in Figma or Canva, I did what any engineer would do: I wrote a script to do it for me.

The Goal

I wanted thumbnails that looked like a terminal window executing a command related to the post. For example:

  • A backend post might show a curl request.
  • A personal post might show a cat journal.md command.
  • A database post might show a psql query.

The result needed to be a high-quality PNG that could be used as an Open Graph image (for Twitter/LinkedIn cards) and as the header image on the blog itself.

The Tech Stack

  • Node.js: The runtime for the script.
  • Puppeteer: A headless Chrome Node.js API. This is the secret sauce. It allows me to render HTML/CSS exactly as a browser would and take a screenshot.
  • HTML/CSS: To design the "terminal window" layout.

The Implementation

1. The Template

First, I created an HTML template (template.html) that defines the look of the terminal. It uses Flexbox to center a "window" div, and CSS to style the window controls (the red, yellow, and green dots) and the dark background.

I used Google Fonts to load JetBrains Mono for the terminal text and Outfit for the window title, ensuring typography matches the rest of my site.

<div class="terminal-window">     <div class="window-header">         <div class="window-controls">             <!-- Mac-style dots -->             <div class="control-dot red"></div>             <div class="control-dot yellow"></div>             <div class="control-dot green"></div>         </div>         <div class="window-title">~/blog/posts/{{windowTitle}}</div>     </div>     <div class="terminal-content">         {{terminalContent}}     </div> </div>

2. The Generator Script

The script (generate.js) reads my blog's configuration and post metadata. It then launches a headless browser instance using Puppeteer.

For each post, it:

  1. Determines the "primary tag" (e.g., backend, personal, frontend).
  2. Selects a corresponding "command pattern" based on that tag.
  3. Injects the post title and the generated command into the HTML template.
  4. Takes a screenshot of the rendered page.
  5. Saves the image to public/assets/thumbnails/.

Here's a snippet of the logic that maps tags to terminal commands:

switch (primaryCategory) {     case 'backend':         commandHtml = `curl -X POST /api/v1/deploy`;         outputHtml = `{"status": "success", "message": "Deployment initiated..."}`;         break;     case 'personal':         commandHtml = `cat ~/journal/${slug}.md`;         outputHtml = `Reflecting on: "${description}"`;         break;     // ... other categories }

3. Build Integration

The best part is that this is now part of my build pipeline. Whenever I run npm run build:blog, the script checks if a thumbnail exists for each post. If it's missing, it automatically generates a fresh one in milliseconds.

This means I never have to worry about assets again. I just write the markdown, tag it appropriately, and the system generates a beautiful, custom thumbnail that perfectly matches the content.

The Result

You're looking at it right now! The thumbnail for this post was generated by the very system it describes. It's a meta-example of automation serving creativity.

By treating design assets as code, I've ensured 100% consistency across the blog while removing manual toil from my writing process.