How to write a new post
The standard loop for adding a post to a Jekyll + Minima blog.
The 4-step loop
# 1. Create the file. Name = date + URL slug.
$EDITOR _posts/2026-05-04-thoughts-on-rust.markdown
# 2. Top of the file — YAML "frontmatter" between two --- lines:
---
layout: post
title: "Thoughts on Rust"
date: 2026-05-04 14:30:00 +0800
categories: programming
tags: [rust, language-design]
---
The post body in **Markdown** starts here.
# 3. Save. With `jekyll serve --livereload` running on :4000,
# the site rebuilds in ~200 ms and the browser auto-refreshes.
# 4. Ship it.
git add _posts/2026-05-04-thoughts-on-rust.markdown
git commit -m "Add post: Thoughts on Rust"
git push
GitHub Pages picks up the push and rebuilds in 30–90 s.
Things worth knowing
- The filename is load-bearing.
YYYY-MM-DD-slug.markdownisn’t a convention — Jekyll parses it. The date becomespage.date(overridable via the frontmatterdate:field), the slug becomespage.slugand the URL fragment. A file namednotes.mdin_posts/is silently skipped, no error.- Frontmatter is YAML, with all of YAML’s quirks. Quote titles containing
:or starting with[,&,*,?. Thedate:value needs the timezone offset, set globally via_config.yml’stimezone:. If parsing fails, Jekyll prints a quiet warning and skips the post.layout: postwraps the body in Minima’s_layouts/post.html(title header, date, prev/next nav). Without it, you get raw content with no styling.
Drafts (work in progress)
Posts not yet ready to publish go in _drafts/ instead of _posts/, and don’t need a date in the filename:
mkdir -p _drafts
$EDITOR _drafts/half-baked-idea.markdown
Preview drafts locally — restart the server with --drafts:
bundle exec jekyll serve --livereload --drafts
When ready, move and rename:
mv _drafts/half-baked-idea.markdown _posts/2026-05-10-half-baked-idea.markdown
Drafts get
page.dateset to the file’s mtime, so they appear at the top of the local index until renamed. Without--drafts, Jekyll ignores_drafts/entirely — both locally and on GitHub Pages — so it’s safe to commit drafts as backups, though most people gitignore_drafts/.
Images and other assets
Drop them in any non-underscored directory. The convention is assets/img/:
mkdir -p assets/img
cp ~/Pictures/diagram.png assets/img/
Reference in a post:

The
relative_urlLiquid filter prependssite.baseurl. For a user site the baseurl is empty, so/assets/img/diagram.pngresolves directly. But using the filter anyway means the same code works if the post is copied to a project site (wherebaseurlis/repo-name). Cheap insurance.Underscored directories are reserved by Jekyll (
_posts,_drafts,_layouts,_includes,_data,_site). Don’t create your own — they’ll be either consumed or excluded.
Categories vs. tags — the practical difference
| Affects URL? | Generates archive page? | Typical use | |
|---|---|---|---|
categories |
Yes — prepended to permalink | No (in Minima) | High-level groupings: programming, journal, meta |
tags |
No | No (in Minima) | Cross-cutting topics: rust, kubernetes, book-review |
Because categories show in the URL, changing them later breaks links. Tags are cheap to revise.
To get clean URLs without category prefixes, override the permalink globally in _config.yml:
defaults:
- scope: { type: posts }
values:
permalink: /:year/:month/:day/:title/
Or per-post via frontmatter permalink: /thoughts-on-rust/.
Common pitfalls
| Symptom | Cause |
|---|---|
| Post doesn’t appear | Filename missing YYYY-MM-DD- prefix, or date is in the future |
| “Future” post hidden | Add --future to jekyll serve, or move the date back |
| Page renders without styling | Missing layout: post |
Build error: did not find expected key |
YAML frontmatter has an unquoted : or stray indentation |
| Site live but local doesn’t update | LiveReload connection dropped — hard-refresh once |
| Push succeeds but site stale | Bad Liquid syntax silently fails the build; check the latest Pages build status with gh api repos/<owner>/<repo>/pages/builds/latest |
A helper, if posts come often
Drop this in ~/.zshrc (or ~/.bashrc):
newpost() {
local slug="${1:?usage: newpost slug-words [title]}"
local title="${2:-${slug//-/ }}"
local date="$(date +%Y-%m-%d)"
local file="_posts/${date}-${slug}.markdown"
cat > "$file" <<EOF
---
layout: post
title: "${title}"
date: $(date '+%Y-%m-%d %H:%M:%S %z')
categories: meta
---
EOF
${EDITOR:-vi} "$file"
}
Then newpost thoughts-on-rust "Thoughts on Rust" scaffolds the file with the right date and timezone, and opens it in your editor.