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, asgenerate_blog.jswill convert it to HTML. Ensure your post starts with an H1 (# Title) or H2 (## Title) for automatic title extraction bypost.pyandgenerate_blog.js. -
HTML (
.html): For more complex layouts or if you prefer writing raw HTML.
-
-
Filename Convention: While
post.pyenforces2025-07-17-HHMMSS-slug.md, you can manually create files adhering to2025-07-17-optional-timestamp-your-post-slug.mdor.html. Thegenerate_blog.jsscript’sparsePostFilenamefunction 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. Thegenerate_blog.jsscript automatically appends the<div class="profile-image-placeholder">HTML after renderingabout.md. -
site_update_log.md: List your site updates here.generate_blog.jswill render this Markdown. Note that if you start this file with an H1/H2,generate_blog.jswill 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-pushor-c: Optional. If present, the script will automatically execute Git commands topull,add,commit, andpushyour changes to GitLab after successfully processing and copying the file.
Example Usage:
-
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 -
Process and stage the post:
Bash
python post.py -f ~/drafts/my-awesome-post.mdThis 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, andgit push.
-
-
Process and auto-deploy:
Bash
python post.py -f ~/drafts/another-cool-post.md -cThis 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 pushThis 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):
-
Initializes
markdown-it: Sets up the Markdown parser with HTML, linkify, and typographer options enabled. -
Ensures
posts/Directory: Creates theposts/directory if it doesn’t exist, where individual post HTML files will be stored. -
Reads & Processes
about.mdandsite_update_log.md:-
Reads
about.mdand converts its content to HTML (aboutContentHtml). It then appends the<div class="profile-image-placeholder">...</div>HTML directly. -
Reads
site_update_log.mdand converts its content to HTML (siteUpdateLogHtml). It specifically checks for and removes any<h1>or<h2>generated bymarkdown-itif it’s the first element. -
If these files are missing, it logs a warning and uses placeholder content.
-
-
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.htmlfor output) -
processedContent(HTML version of the post, markdown-it converted if original was.md).
-
-
All parsed post metadata is collected into
postsMetadata, sorted byfullTimestamp(newest first).
-
-
Generates Individual Post Pages (
posts/*.html):-
Reads
post.template.html. -
For each post in
postsMetadata:-
Replaces
YOUR POST TITLE HEREwith the actual post title. -
Replaces
YOUR-POST-SLUG.htmlwith the generated output filename. -
Replaces
###POST_CONTENT###with theprocessedContentof 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
using the pre-processed content from- 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-cflag 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.
about.mdandsite_update_log.md, ensuring these sidebars are consistent across all post pages. -
Writes the final HTML to
posts/2025-07-17-slug.html.
-
-
-
Generates Main Index Page (
index.html):-
Reads
index.template.html. -
Generates HTML for the “Recent Posts” section (
blogPostsHtml), showing the latestmaxPostsToShow(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###withblogPostsHtml. -
###BLOG_ARCHIVE_PLACEHOLDER###withblogArchiveHtml. -
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. -
with- 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-cflag 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.
siteUpdateLogHtml.
-
-
Writes the complete
index.htmlto 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###: (Inindex.template.htmlonly) For the list of recent blog post summaries. -
###BLOG_ARCHIVE_PLACEHOLDER###: (Inindex.template.htmlonly) For the chronological archive. -
###POST_CONTENT###: (Inpost.template.htmlonly) 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. -
: (In both templates) For the HTML content rendered from- 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-cflag 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.
site_update_log.md. -
YOUR POST TITLE HERE,YOUR-POST-SLUG.html,2025-07-17: Dynamic text replaced bygenerate_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:
-
Authentication:
curlcommands use yourNEOCITIES_API_TOKEN(via$NEO_TOKENenv var) for authorization. -
File Uploads:
-
index.htmlandstyles.cssare directly uploaded from the root. -
All
.htmlfiles found within theposts/directory (created bygenerate_blog.js) are iterated through usingfindandwhile read, and each is uploaded to Neocities, preserving itsposts/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.