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.js
will convert it to HTML. Ensure your post starts with an H1 (# Title
) or H2 (## Title
) for automatic title extraction bypost.py
andgenerate_blog.js
. -
HTML (
.html
): For more complex layouts or if you prefer writing raw HTML.
-
-
Filename Convention: While
post.py
enforces2025-07-17-HHMMSS-slug.md
, you can manually create files adhering to2025-07-17-optional-timestamp-your-post-slug.md
or.html
. Thegenerate_blog.js
script’sparsePostFilename
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. Thegenerate_blog.js
script automatically appends the<div class="profile-image-placeholder">
HTML after renderingabout.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 topull
,add
,commit
, andpush
your 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.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
, andgit push
.
-
-
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):
-
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.md
andsite_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 bymarkdown-it
if 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.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 byfullTimestamp
(newest first).
-
-
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 theprocessedContent
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
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-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.
about.md
andsite_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.
withaboutContentHtml
. -
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-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.
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###
: (Inindex.template.html
only) For the list of recent blog post summaries. -
###BLOG_ARCHIVE_PLACEHOLDER###
: (Inindex.template.html
only) For the chronological archive. -
###POST_CONTENT###
: (Inpost.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 fromabout.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-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.
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:
curl
commands use yourNEOCITIES_API_TOKEN
(via$NEO_TOKEN
env var) for authorization. -
File Uploads:
-
index.html
andstyles.css
are directly uploaded from the root. -
All
.html
files found within theposts/
directory (created bygenerate_blog.js
) are iterated through usingfind
andwhile 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.