Cookbook

A Blogin site is customized in six places. This page is the index: what each one reaches, and where the detail lives.

Where What it changes
blogin.json Site-wide behavior. Feeds, pagination, taxonomies, assets, languages.
Front matter One post. Its URL, its ordering, its summary, its redirects.
layouts/ The markup of every page, per section if you want.
assets/ and static/ Stylesheets, scripts, images, and files served verbatim.
data/ and shortcodes/ Values a layout reads, and markup a post can call.
themes/ A whole layout and asset set, reused across sites.

Change what a page looks like#

Edit layouts/. base.haml is the shell, show.haml a post, and index.haml a listing. show.haml and index.haml are required once there are posts and listings to render. base.haml is optional.

Give one section its own markup by putting a layout in a matching directory. A post in content/essays/ renders through layouts/essays/show.haml when that file exists, falling back to layouts/show.haml:

layouts/
  show.haml            every post
  essays/show.haml     posts under content/essays/

Or name a layout in the config instead of using a directory:

"sections": {
  "essays": { "layout": "essay" }
}

See Layouts for the full set of files and how they resolve, and Template Data for every value a layout can read.

Change what a post does#

Front matter. No key is required, and a post with no title takes one from its filename:

---
title: My Post
slug: a-different-url
order: 2
toc: true
draft: true
summary: What listings and feeds show instead of the opening paragraph.
aliases: [/the-old-url]
---

slug picks the URL, order places a post ahead of date ordering, toc turns on the table of contents, draft holds it back from a normal build, and aliases writes a redirecting page at each old URL. Any other key is read only when taxonomies in blogin.json names it, and then it groups posts the way tags does.

See Writing Posts.

Change the site's behavior#

blogin.json. Every key is optional, an unknown key is a warning that suggests a real key within three edits of it, and a key with the wrong type stops the build.

Common ones:

Want Key
Extensionless URLs clean-urls
Posts per listing page page-size, or per section
More than tags taxonomies
RSS or JSON alongside Atom feed-formats
Syntax highlighting highlight
Smaller CSS and JS minify
Cache-safe asset names fingerprint
Responsive images image-widths
More than one language languages

See Configuration for all of them with types and defaults.

Change the navigation#

The nav is built from the content tree, so a new directory under content/ becomes a new nav entry. Adjust it per section:

"sections": {
  "notes": { "label": "Field Notes", "order": -1 },
  "drafts": { "nav": false }
}

label renames it, order sets its position, and nav: false keeps it out. Sections sort by order ascending, then by name, and a section without one counts as 0, so a negative order moves a section to the front. A layout renders the tree itself from nav-nodes, with nav-current(node) marking the current section, so the markup is yours. See Template Data.

Nothing links /tags automatically. Add it to a layout when you want it.

Change the styling#

Three separate levers, and they combine.

A CSS framework. Set css-framework to none, bootstrap5, pico, or bulma and Blogin classes the HTML it writes to match. See CSS Frameworks.

Your own CSS. Anything under assets/css is copied to public/assets/css, minified and fingerprinted if you asked for it. Link it from base.haml.

The generated stylesheet. public/assets/css/blogin.css carries the syntax highlighting colors, code block styling, and the light and dark toggle. Override any rule in your own stylesheet rather than editing it, since a build rewrites it.

For a light and dark toggle, put theme-script in the <head> and theme-toggle in the navbar. See Layouts.

Change the pagination#

page-size sets how many posts a listing page holds, and a section can override it. pagination-html renders a ready-made bar, and pagination-links gives the pages as data for a bar you write yourself. See Pagination.

Add values a layout can read#

data/. Every .json, .yaml, and .yml file under it loads into data, keyed by filename:

data/
  authors.json    -> data<authors>
  site/links.yaml -> data<site><links>

A _data.json inside a content directory applies to the pages beneath it and overrides the site-wide tree key by key. See Data files and Data File YAML for what a YAML file may hold.

Add markup a post can call#

shortcodes/. Put an HTML template there named for the shortcode, and a post calls it with key="value" arguments:

<!-- shortcodes/note.html -->
<aside class="note">{{ text }}</aside>
{{< note text="Heads up" >}}

youtube and figure are built in, and a file of the same name overrides one. See Writing Posts.

Reuse a whole design#

themes/. A theme is a directory of layouts/, assets/, and static/ that fills in behind your own file by file, so you can override one layout and keep the rest:

"theme": "acme"

See Themes.

Speed up a big site#

Wrap chrome that renders the same on every page in cache-fragment:

!= cache-fragment('sidebar', { render(:partial<sidebar>) })

Reuse is decided by the values the fragment read, so it cannot serve one page's markup to another. See Layouts.

blogin build --counters prints the work a build did, which is what says whether a change made it do more. See Previewing and Building.

What cannot be customized#

Know these before you go looking for a setting.

  • No plugin system. Everything a site needs is in the binary.
  • No template logic beyond the expression language. It reads, compares, iterates, and calls what the view offers. No assignment, no user-defined functions, no calling into a host language. See Template Expressions.
  • No per-taxonomy page size. Term pages use the site's page-size.
  • The page/2 URL shape is fixed. See Pagination.
  • Feeds carry every post in scope, with no length key.