Building a theme
Building a theme
What a theme is, what its directory holds, how a request becomes a rendered page, and how to work on one day to day.
A theme is everything a visitor sees: layouts, views, components and styles, plus the schema it needs to render. This guide introduces Tidewater’s theme and how Nibble uses it.
After reading this guide, you will know:
- What a theme holds, and how to start one of your own.
- How a request becomes a rendered page, and which parts of that are yours.
- How the active theme is chosen.
- What to expect from the development loop.
1. Starting a theme
Every site starts with crumbs, the theme Nibble ships. It is Nibble’s, and an upgrade replaces it, so the first
thing you do is copy it:
bin/rails nibble:generate:theme tidewater
That copies vendor/nibble/themes/crumbs to site/themes/tidewater, renames its npm package to
@nibble-theme/tidewater, registers it with npm and makes it the active theme in config/nibble.yml. Commit it with
package-lock.json.
Warning
Do not edit vendor/nibble/themes/crumbs. It is replaced on every upgrade, which refuses to go ahead while it is
changed. Your theme is yours for good.
2. What a theme holds
site/themes/tidewater/
├── theme.yml name, version, and the theme API it was written for
├── package.json the theme's own npm dependencies
├── layouts/ the frame around a page — default.vue
├── views/ one view per kind of page, each with an optional .yml query beside it
│ ├── sets/ one component per block in a replicator
│ └── errors/ 404.vue and 500.vue
├── components/ your own components
├── lib/ your own TypeScript helpers
├── styles/ theme.css, built by Vite
└── schema/ collections, blueprints and so on this theme needs
The types generated from the schema are site/types.d.ts, imported as @site/types. Do not edit them.
# site/themes/tidewater/theme.yml
name: Tidewater
handle: tidewater
version: 0.1.0
nibble: '^1'
description: Tidewater, a theme for Nibble.
nibble: '^1' pins the theme API, not Nibble’s version. A theme written for theme API 1 keeps working however
many Nibble releases come out, and a site will not start with a theme built for a different one.
Important
A theme never ships content. It is layouts, views, components, styles and schema. Pages and posts belong to the site — in its database, or in a content package — so one theme can serve many sites without carrying any of them.
3. How a page is rendered
- Redirects are checked first, from a cached table. A moved page costs one lookup.
- The URL is resolved — to an entry, a file’s page, a taxonomy term, a collection’s or taxonomy’s index, or
nothing, which renders
views/errors/404.vue. - The view is chosen — the entry’s own template, its blueprint’s, its collection’s, or
default. - The view’s query sidecar runs. A
.ymlbeside the view lists the extra content the page needs, and Nibble fetches it. See Queries. - The view renders on the server with Vue, inside its layout, and is hydrated in the browser. Visitors and search engines get finished HTML.
- The response is cached against everything it used, so publishing any of it clears exactly the pages that depended on it.
Steps 3 to 5 are yours; the rest is Nibble’s.
Note
Because Nibble runs every query itself (step 4), it knows exactly what each page depended on. That is why a view never fetches its own data: a page that fetched for itself could not be cached precisely.
4. The active theme
The active theme is named by theme: in config/nibble.yml, or by the NIBBLE_THEME environment variable when
the file does not say. Without either, it is crumbs.
5. The development loop
bin/dev runs Rails, Vite and server-side rendering together. Saving an existing view, component, style, schema file
or content file shows up on the next reload.
Warning
Restart bin/dev after adding a view, a block or a layout, or after changing the active theme. New files
are only picked up when the asset server starts; until then the browser console reports
Page not found: theme/<view> and a new block renders nothing.
npm run lint checks your theme with ESLint, including that it imports only @nibble, @theme, relative paths
and its own dependencies, which is what keeps it swappable. npm run format lays it out with Prettier.
Theme dependencies are ordinary npm packages in the theme’s own workspace:
npm install highlight.js -w @nibble-theme/tidewater
Commit the lockfile afterwards. It is shared with Nibble, and an upgrade rebuilds it rather than installing from it, so a conflict there is harmless.
6. The guides in this section
| Guide | Covers |
|---|---|
| Views and layouts | which view renders which page, layouts, blocks and error pages |
| Queries | the sidecar that fetches what a page needs |
| Theme components | rich text, images, blocks, pagination, SEO, menus and forms |
| SEO, sitemaps and feeds | what search engines and feed readers receive |
| The Content API | reading the same content over HTTP |