Building a theme
Content written as files
Turn a folder of Markdown into a collection, the way these pages work.
Most content belongs in the control panel, where somebody can write it without asking a developer. Some content does not: documentation, a changelog, reference pages that change in the same pull request as the code they describe.
For those, a collection can be written as Markdown files in your repository. Nibble reads the folder and makes the collection match it — the pages are ordinary entries afterwards, with URLs, search, caching and a navigation tree, but the files are the source and the control panel will not let anyone edit them.
The pages you are reading now are exactly this.
Setting it up
Three things: a folder, a blueprint, and a collection that points at them.
1. Put the files in content/. That directory is reserved for content kept as files, and it is the only
place a collection may read from.
content/
└── docs/
├── index.md
├── guides/
│ ├── index.md
│ └── testing.md
└── images/
└── diagram.png
Note
A folder’s index.md is the page its files sit under, and a folder without one is an error rather than a
guess. The root of the folder is the exception: name that page something other than index.md.
2. Write a blueprint with a field for the body and whatever the frontmatter carries:
# schema/blueprints/collections/docs/doc.yml
schema: 1
title: Doc
tabs:
main:
sections:
- fields:
- handle: title
field: { type: text, required: true }
- handle: description
field: { type: text }
- handle: body
field: { type: markdown }
3. Point a collection at the folder:
# schema/collections/docs.yml
schema: 1
title: Docs
route: "{parent_uri}/{slug}"
structure:
max_depth: 3
blueprints: [doc]
template: docs/show
source:
markdown: docs # the folder inside content/
field: body # the field the Markdown lands in
navigation: docs # optional: build this navigation from the folders
markdown: docs and markdown: content/docs mean the same thing. Anything outside content/ is refused, so a
collection cannot be pointed at the rest of the application.
4. Bring the files in:
bin/rails nibble:content:markdown
What a file becomes
---
title: Testing
description: How we test, and why it is worth it.
order: 2
---
# Testing
Start with [the guides](/docs/themes), and see the [diagram](../images/diagram.png).
| In the file | Becomes |
|---|---|
| its path | where the page sits — guides/testing.md is testing, under guides |
title, description |
fields on the entry, by the same name |
order |
where it sits among its siblings in the navigation |
| everything after the frontmatter | the Markdown field named by field |
A folder’s index.md is the page its other files sit under. So guides/index.md is the “Guides” page and
guides/testing.md is a page beneath it. A folder without an index.md is an error rather than a guess —
its files would have nowhere to live.
Frontmatter keys must be fields of the blueprint, or columns Nibble already understands such as published_at.
A key that is neither is reported by name, so a typo is caught rather than silently dropped.
Links and images
A link to another .md file becomes a link to that page. It is stored as a reference, not as a URL, and
resolved when the page renders — so if either page moves later, the link still lands. A link to a page that does
not exist renders as a dead link rather than something strange.
An image beside the pages becomes an asset. It is filed in a folder mirroring where it was written, and uploaded again only when the file itself changes, so running the command repeatedly does not fill your bucket with copies.
External links, anchors and everything else are left exactly as written.
The navigation
Name a navigation in source and the folders become its tree: a folder’s page is the branch, the files inside it
are the children, and order in the frontmatter decides what comes first. Pages without an order come last,
alphabetically.
It is available to your theme as an ordinary navigation, which is where a documentation sidebar comes from:
<nav>
<a v-for="item in site.navigation.docs" :key="item.id" :href="item.url">{{ item.title }}</a>
</nav>
See Views for what a theme receives.
Running it
bin/rails nibble:content:markdown # every collection written as files
bin/rails nibble:content:markdown docs # just this one
bin/rails nibble:content:markdown --dry-run # report what would change, write nothing
It prints what it did:
docs: created 12, updated 3, trashed 1
The folder decides what exists. A page whose file has been deleted is trashed, not left live — that is the difference between this and importing a content package, and it is why the collection must be the folder’s alone.
Running it again changes nothing. That is what makes it safe to run on every deploy, which is where it belongs: merge a change to a file, deploy, and the site has it.
In the control panel
Pages written as files are read-only. The listing and the page open normally, but the fields are locked and saving is refused — not just hidden, refused, because an edit that survived the afternoon and vanished at the next deploy would be worse than no edit at all.
Each page says which file it is written in and what to run after changing it, so somebody who finds a typo knows where to go.
When to use it, and when not
Use it when the content belongs with the code: documentation, reference material, anything reviewed in a pull request alongside what it describes.
Do not use it for content that people who do not use git need to change. That is what the control panel is for, and a locked collection is a frustrating answer to “can you fix this sentence”.