spilk@home:~$
~/posts/2025-07-17-my-static-site-workflow.html

My Static Site Workflow: A Comprehensive Usage Guide

This document serves as a detailed technical guide for understanding, setting up, and operating my automated static site generation and deployment workflow. It references all components within my project.

1. Project Structure & Core Files

Ensure your project directory is structured as follows, with the main files in the root and raw_posts/ and posts/ as subdirectories:

.
├── .gitlab-ci.yml        # GitLab CI/CD pipeline definition
├── about.md              # Markdown content for the "About" section
├── generate_blog.js      # Node.js script for site generation
├── index.template.html   # HTML template for the main blog page
├── package.json          # Node.js project dependencies
├── post.py               # Python utility for new post creation
├── post.template.html    # HTML template for individual blog posts
├── styles.css            # Site-wide CSS styling
├── raw_posts/            # Directory for raw Markdown/HTML blog post sources
└── posts/                # (Automatically generated) Directory for final HTML blog post files

2. Initial Setup

Before first use, or on a new machine, you need to set up the environment:

2.1. Node.js Dependencies

Your generate_blog.js script relies on markdown-it. Install it:

Bash

cd /path/to/your/blog/root
npm install

This command reads package.json and installs markdown-it into node_modules/.

2.2. GitLab CI/CD Variables

For the deploy stage to work, you must configure two CI/CD Variables in your GitLab project settings (Settings > CI/CD > Variables):

  • NEOCITIES_API_TOKEN: Your personal Neocities API key. Make sure this is “Protected” and “Masked.”

  • NEOCITIES_SITE_NAME: Your Neocities site name (e.g., spilk). Make sure this is “Protected” and “Masked.”

3. Content Management

3.1. Blog Posts (raw_posts/)

Individual blog entries are created here.

  • Format: Files can be either:

    • Markdown (.md): Preferred, as generate_blog.js will convert it to HTML. Ensure your post starts with an H1 (# Title) or H2 (## Title) for automatic title extraction by post.py and generate_blog.js.

    • HTML (.html): For more complex layouts or if you prefer writing raw HTML.

  • Filename Convention: While post.py enforces 2025-07-17-HHMMSS-slug.md, you can manually create files adhering to 2025-07-17-optional-timestamp-your-post-slug.md or .html. The generate_blog.js script’s parsePostFilename function specifically expects this pattern: ^(\d{4}-\d{2}-\d{2})(?:-(\d{6}))?-(.+?)\.(html|md)$.

3.2. Static Content (about.md, site_update_log.md)

These Markdown files drive the dynamic “About” and “Site Update Log” sections on your index.html and individual post.html pages.

  • about.md: Write your “About spilk” content. Markdown will be rendered to HTML. The generate_blog.js script automatically appends the <div class="profile-image-placeholder"> HTML after rendering about.md.

  • site_update_log.md: List your site updates here. generate_blog.js will render this Markdown. Note that if you start this file with an H1/H2, generate_blog.js will remove it during processing to avoid duplicate headings with the template.

4. Creating New Blog Posts (post.py)

The post.py script simplifies adding new entries to raw_posts/.

Syntax:

Bash

python post.py -f <path/to/your/markdown_draft.md> [--commit-and-push]
  • -f <path/to/your/markdown_draft.md>: Required. Specifies the path to your new Markdown draft file. This file must contain an H1 or H2 heading as its title.

  • --commit-and-push or -c: Optional. If present, the script will automatically execute Git commands to pull, add, commit, and push your changes to GitLab after successfully processing and copying the file.

Example Usage:

  1. Prepare a draft: You write a new post in ~/drafts/my-awesome-post.md.

    Markdown

    # My Awesome New Post Title
    
    This is the *content* of my awesome new post.
    
    - Item 1
    - Item 2
    
  2. Process and stage the post:

    Bash

    python post.py -f ~/drafts/my-awesome-post.md
    

    This will:

    • Read ~/drafts/my-awesome-post.md.

    • Generate a filename like 2025-07-17-133000-my-awesome-new-post.md.

    • Copy the content to raw_posts/2025-07-17-133000-my-awesome-new-post.md.

    • Output a confirmation message: Successfully processed and saved to 'raw_posts/2025-07-17-133000-my-awesome-new-post.md'

    • You would then manually git add raw_posts/2025-07-17-133000-my-awesome-new-post.md, git commit, and git push.

  3. Process and auto-deploy:

    Bash

    python post.py -f ~/drafts/another-cool-post.md -c
    

    This will perform all steps from the previous example, plus it will execute:

    Bash

    git pull
    git add .
    git commit -m "New Post 2025-07-17-134500-another-cool-post.md"
    git push
    

    This push will trigger your GitLab CI/CD pipeline.

5. Site Generation Logic (generate_blog.js)

This Node.js script is the engine that converts your raw content and templates into a fully functional static site. It runs automatically in GitLab CI, but you can run it locally for testing.

Execution:

Bash

cd /path/to/your/blog/root
node generate_blog.js

What it Does (Step-by-Step):

  1. Initializes markdown-it: Sets up the Markdown parser with HTML, linkify, and typographer options enabled.

  2. Ensures posts/ Directory: Creates the posts/ directory if it doesn’t exist, where individual post HTML files will be stored.

  3. Reads & Processes about.md and site_update_log.md:

    • Reads about.md and converts its content to HTML (aboutContentHtml). It then appends the <div class="profile-image-placeholder">...</div> HTML directly.

    • Reads site_update_log.md and converts its content to HTML (siteUpdateLogHtml). It specifically checks for and removes any <h1> or <h2> generated by markdown-it if it’s the first element.

    • If these files are missing, it logs a warning and uses placeholder content.

  4. Processes raw_posts/:

    • Reads all files in raw_posts/.

    • For each file, parsePostFilename() extracts:

      • date (2025-07-17)

      • time (HHMMSS)

      • fullTimestamp (Date object for precise sorting)

      • slug (from filename)

      • title (extracted from the first H1/H2 in the content)

      • filename (e.g., 2025-07-17-my-slug.html for output)

      • processedContent (HTML version of the post, markdown-it converted if original was .md).

    • All parsed post metadata is collected into postsMetadata, sorted by fullTimestamp (newest first).

  5. Generates Individual Post Pages (posts/*.html):

    • Reads post.template.html.

    • For each post in postsMetadata:

      • Replaces YOUR POST TITLE HERE with the actual post title.

      • Replaces YOUR-POST-SLUG.html with the generated output filename.

      • Replaces ###POST_CONTENT### with the processedContent of the post.

      • Crucially, it also populates Hello! I’m spilk. I enjoy tinkering with computers, experimenting in the kitchen, fixing what’s broken, and maintaining various online services.

        This blog is a place for me to document my projects and share what I’ve learned.

        and
        • 2025-07-17: Implemented dynamic “About” and “Site Update Log” sections using Markdown files and automated generation.
        • 2025-07-17: Streamlined post creation with post.py’s new -c flag for auto-commit/push.
        • 2025-07-17: Enhanced blog post aesthetics and code block syntax highlighting using Atom One Dark theme.
        • 2025-06-30: Setting up GitLab Runner – Again
        • 2025-02-04: Setting up GitLab Runner
        • 2025-01-28: Initial website setup on Neocities.
        using the pre-processed content from about.md and site_update_log.md
        , ensuring these sidebars are consistent across all post pages.

      • Writes the final HTML to posts/2025-07-17-slug.html.

  6. Generates Main Index Page (index.html):

    • Reads index.template.html.

    • Generates HTML for the “Recent Posts” section (blogPostsHtml), showing the latest maxPostsToShow (currently 5) with summaries and “Read More” links.

    • Generates HTML for the “Blog Archive” section (blogArchiveHtml), grouping posts by year using <details> and <summary> tags.

    • Performs replacements on index.template.html:

      • ###BLOG_POSTS_PLACEHOLDER### with blogPostsHtml.

      • ###BLOG_ARCHIVE_PLACEHOLDER### with blogArchiveHtml.

      • Hello! I’m spilk. I enjoy tinkering with computers, experimenting in the kitchen, fixing what’s broken, and maintaining various online services.

        This blog is a place for me to document my projects and share what I’ve learned.

        with aboutContentHtml.

        • 2025-07-17: Implemented dynamic “About” and “Site Update Log” sections using Markdown files and automated generation.
        • 2025-07-17: Streamlined post creation with post.py’s new -c flag for auto-commit/push.
        • 2025-07-17: Enhanced blog post aesthetics and code block syntax highlighting using Atom One Dark theme.
        • 2025-06-30: Setting up GitLab Runner – Again
        • 2025-02-04: Setting up GitLab Runner
        • 2025-01-28: Initial website setup on Neocities.
        with siteUpdateLogHtml.

    • Writes the complete index.html to the project root.

6. Templates (index.template.html, post.template.html)

These are the blueprints for your HTML output. They contain the overall structure, common headers/footers, sidebar elements, and placeholders that generate_blog.js fills in.

  • Placeholders:

    • ###BLOG_POSTS_PLACEHOLDER###: (In index.template.html only) For the list of recent blog post summaries.

    • ###BLOG_ARCHIVE_PLACEHOLDER###: (In index.template.html only) For the chronological archive.

    • ###POST_CONTENT###: (In post.template.html only) For the full content of an individual post.

    • Hello! I’m spilk. I enjoy tinkering with computers, experimenting in the kitchen, fixing what’s broken, and maintaining various online services.

      This blog is a place for me to document my projects and share what I’ve learned.

      :
      (In both templates) For the HTML content rendered from about.md.

      • 2025-07-17: Implemented dynamic “About” and “Site Update Log” sections using Markdown files and automated generation.
      • 2025-07-17: Streamlined post creation with post.py’s new -c flag for auto-commit/push.
      • 2025-07-17: Enhanced blog post aesthetics and code block syntax highlighting using Atom One Dark theme.
      • 2025-06-30: Setting up GitLab Runner – Again
      • 2025-02-04: Setting up GitLab Runner
      • 2025-01-28: Initial website setup on Neocities.
      : (In both templates) For the HTML content rendered from site_update_log.md.

    • YOUR POST TITLE HERE, YOUR-POST-SLUG.html, 2025-07-17: Dynamic text replaced by generate_blog.js.

7. Styling (styles.css)

This file defines the visual presentation of your entire blog, creating the distinct terminal-like aesthetic. It’s referenced by both index.html and individual post HTML files.

8. Deployment (.gitlab-ci.yml - deploy stage)

After generate_blog.js creates the static files, the deploy stage pushes them to Neocities.

Mechanism:

  1. Authentication: curl commands use your NEOCITIES_API_TOKEN (via $NEO_TOKEN env var) for authorization.

  2. File Uploads:

    • index.html and styles.css are directly uploaded from the root.

    • All .html files found within the posts/ directory (created by generate_blog.js) are iterated through using find and while read, and each is uploaded to Neocities, preserving its posts/ subdirectory path.

This entire system ensures that once you push changes (especially new posts using post.py -c), GitLab automatically builds your site and deploys the latest version to Neocities without manual intervention.

← Back to Blog Home