Blog writing process

Methodology, tools and procedures

Okay I have 9 blog posts in progress and 220 pages total on my site. It’s starting to look like an organized and productive system. Here is how I organize my writing workflow using Spacemacs and Firefox.

Site metadocumentation

This post is more about the workflow for writing the site content.

You will find documentation about the site’s tech stack and hosting in metadocumentation. In summary, the site is built with Hugo (one of the most popular open-source static site generators) and the Wowchemy theme and templates, self-hosted on a TrueNAS box with a pfSense reverse proxy.

Editing software

I do all the editing in Spacemacs, with heavy use of the plugins Projectile and Magit (Spacemacs layers git and project).

Here is a screenshot of my écriture browser session.

Screenshot of my Firefox session _écriture_ (writing).
Screenshot of my Firefox session écriture (writing).

Under blogue, I have my blog site with a distinct favicon for dev, staging and production. Research tabs for each post drafts are organized under authoring. Under docs, I have reference documentation for Hugo and Wowchemy.

See also how I manage my Firefox tabs and sessions with Tree Style Tabs and Tab Session Manager.

Reference documentation

For getting started guides, go here:

When picking a starter template, “Start with […]” means SaaS hosting and CMS with Fastly, and “Star on GitHub” is where you can clone the Git repo for self-managed sites.

Here are the reference documentation pages I have open in my “écriture” Firefox session. You always need reference documentation at these four levels: your CMS engine, your site template, your site, your content authoring.

Markup style

I write in Markdown (Hugo uses Goldmark by default) with semantic line breaks. I document custom styles and frequently used markup in the Styles metadocumentation page.

Icons for staging and dev

To help differentiate dev and staging tabs in Firefox, I use a variant favicon that I serve as static files. A custom template in Wowchemy replaces the icon using Javascript depending on the HUGO_ENV parameter set with the --environment flag in the publish script.

{{ if (in (slice (getenv "HUGO_ENV") hugo.Environment) "staging") }}
  <script type="text/javascript">
    var link = document.querySelector("link[rel~='icon']");
    link.href = '/images/icon-staging.png';
{{ end }}
{{ if (in (slice (getenv "HUGO_ENV") hugo.Environment) "development") }}
  <script type="text/javascript">
    var link = document.querySelector("link[rel~='icon']");
    link.href = '/images/icon-dev.png';
{{ end }}

Development site icon:
Staging site icon:
Production site icon:


I have a few scripts to assist me with authoring tasks. I launch them from a terminal windows in Spacemacs (SPC p ').

dev script

Starts a hugo server with a few command line parameter. I typically launch this as a compile command (SPC p c). It runs in the background, I hide/show the compile window with SPC c d.

hugo --i18n-warnings server --buildDrafts --buildFuture

new script

I use this script to create a new post. It automatically creates a new branch on master and a new post in content.

git checkout master
git checkout -b "post/$1"
hugo new --kind post "post/$1"

publish script

This script has three functions, depending on how I call it.

  1. ./publish
    • Check that the master branch is checked out.
    • Publish to prod with rsync.
  2. ./publish staging
    • Check that the staging branch is checked out.
    • Publish to staging site with rsync.
  3. ./publish static
    • rsync my blog files to the static files site.
if [ "$1" = "static" ]; then
  rsync --verbose --recursive --chmod=ug=rwX,o=rX --delete "/home/alex/Documents/Blog files/" <web server>:static
elif [ "$1" = "staging" ]; then
  hugo \
    --environment staging \
    --buildDrafts \
    --buildFuture \
    --baseURL "" \
  && rsync --verbose --recursive --chmod=ug=rwX,o=rX --delete public/ <web server>:staging
  if [ "$(git branch --show-current = \"master\")" ]; then
    hugo \
    && rsync --verbose --recursive --chmod=ug=rwX,o=rX --delete public/ <web server>:public
    echo "Not on the master branch. Aborting!"
    exit 1

retitle script

When I use the new script to create a new post, I have to think about the title before writing. Sometimes I want to rename a post while editing. The retitle script renames the branch and the post slug.

old=$(git branch --show-current)  # Branch name should start with "/post/"
new="$1"  # No "/post/" prefix required in the command parameter.

if [ ! -d "content/$old" ]; then
  echo "You are on branch \"$old\" but the post \"$old\" does not exist."
  echo "Please retitle manually."
  exit 1

echo "Rename \"$old\" to \"post/$new\"? [y/n]"
read r
if [ "$r" = "y" ]; then
  mv "content/$old" "content/post/$new"
  git branch --move "$old" "post/$new"

Git branching model

master branch

Always in sync with production. The publish script checks if the current branch is master before executing rsync.

post/* branches

I have several posts in progress at any given time. Right now I have 9. Each post draft has its own branch, which I start from master. I rebase post/* branches on master whenever they get out of sync.

The initial commit message is “Create post/something-something”. Every subsequent post is “Edit”. I don’t really specify what is edited in each commit in a granular way. Sometimes, a commit fixes a typo. Sometimes it rearranges paragraphs. After a while, I get something like this.

Rebase actionCommit message
pickCreate post/my-post
squashAdd feature image
squashChange feature image
squashEdit and retitle

When I’m ready to publish, I’ll rebase and squash my edit commits, keeping only the create commit.

staging branch

I have a staging site at It’s password protected. This is to allow friends to review my drafts.

I merge posts on staging to share them, and git reset --hard master to reset, discarding the merge commits.

Media files

Large static files are hosted on a separate static HTTP server. It’s just another FreeBSD jail with apache24 serving a filesystem directory. This is to avoid commiting large binary files to Git, like videos or large archives I host for sharing.

I put those files in ~/Documents/Blog files. They get synchronized to Nextcloud for backups (see this post about my Nextcloud setup).

I commit blog post images optimized for web in my site’s repository, because they’re optimized and I can take advantage of Hugo Page Bundles.

Writing project media and supporting material

If I need to organize files related to a blog post, I can just create a directory under ~/Documents/Projets. See How I organize my ~/Documents directoy. Such project directories may contain pictures, drawings, scans, etc. The web optimized version is exported to my blog content and I keep the originals on my personal computer.

Editing helper style

I created a custom CSS style and a Hugo shortcode to help annotate my writings. See my edit shortcode documentation.

I can use it by appending the {.edit} custom attribute after any paragraph in my Markdown content like this:

A paragraph I want to mark as draft,
or an edit annotation I want visually standing out.

And it will be rendered like this:

A paragraph I want to mark as draft, or an edit annotation I want visually standing out.

Before publishing an article, I make sure to remove all edit annotations.

Improvements ideas

Metrics and logs for the site build and publish jobs

Hugo spouts out metrics:

                   | EN   
  Pages            | 220  
  Paginator pages  |   1  
  Non-page files   | 436  
  Static files     |   5  
  Processed images | 304  
  Aliases          |  34  
  Sitemaps         |   1  
  Cleaned          |   0  

I could parse them (can hugo output JSON?) and send them to Pushgateway, and log a line and get it picked up by Promtail’s systemd collector.

In the publish script, I would use curl and logger.


  • Site metadocumentation in /docs/meta/
  • Software I use for authoring and development:
  • Git branches:
    • master is what is shown on my public/production site.
    • Each post draft has a post/* branch.
    • staging is for sharing drafts with friends.
  • Git workflow:
    • I frequently rebase my post/* branches on master.
    • Squash commits before merging branches.
    • Don’t worry about committing unoptimized images temporarily. They will be garbage collected after commit squashing, keeping only the web optimized version.

Writing process inspiration

Alexandre de Verteuil
Alexandre de Verteuil
Senior Solutions Architect

I teach people how to see the matrix metrics.
Monkeys and sunsets make me happy.