Chapter Zero is the Bridgetown starter for technical guides. Scaffolding before your first real chapter.
It is the foundational setup you need before diving into actual content: a landing page, ordered chapters with a sidebar, blog, contact form, newsletter slot, support banner, and auto-generated social preview images. Like "chapter 0" in a book, it is the starting point. You bring the lessons.
minitestrails.com runs a full curriculum on this shell.
git clone https://github.com/minitestrails/chapter-zero.git
cd chapter-zero
bin/setup
bin/devbin/setup installs Ruby gems and npm packages, creates .env from .env.sample when missing, and runs an initial site build. .env sets BRIDGETOWN_ENV=development so analytics stay off and your dev Stripe support link is used when configured.
- Ruby: version in
.ruby-version(rbenv, asdf, or chruby recommended) - Node.js + npm: for Tailwind, esbuild, and Stimulus
- Optional: ImageMagick (
magickorconvert) andrsvg-convert(librsvg) for build-time OG PNG generation (see OG images)
Open http://localhost:4000 (override with PORT if needed).
Read the introduction chapter first while running bin/dev before customizing anything. It explains what ships in the box, how search and OG images work, and the customize checklist - skipping it means re-discovering the same wiring by trial and error. Chapter 0 has development_only: true and is omitted from production builds.
After setup, customize site metadata and landing partials, then add your first lesson at src/_guide/01-your-topic.md (chapter 1). /guide/lesson-one ships as a coming soon placeholder so you can see how unpublished chapters look in the sidebar.
- Dark DaisyUI theme with Tailwind CSS v4 (see Theming with DaisyUI below)
- Landing page: hero, featured testimonial, how-it-works steps, audience cards, featured chapters, testimonials grid, newsletter slot, author card (
src/_partials/landing/; edit markup directly or delete sections you do not need) - Guide collection: Markdown chapters under
src/_guide/withorder,slug, andlayout: guide_chapter - Blog collection: dated posts under
src/_blog/ - Components: DaisyUI-based UI in
src/_components/(shared,guide,blog); layouts and landing partials stay thin - Newsletter (ConvertKit):
Shared::NewsletterFormon the landing page, in coming-soon chapters, and anywhere else you render it. Setconvertkit_form_idandconvertkit_accountinsite_metadata.yml; the form stays hidden until both are set. Section copy lives insrc/_partials/landing/_newsletter.erb. - Google Analytics: optional GA4 via
google_analytics_idinsite_metadata.yml. The tracking snippet insrc/_partials/_head.erbloads in production only (not during localbin/dev). - Privacy page: starter copy at
src/privacy.md, published at/privacyand linked from the footer. Edit it to describe your analytics, newsletter, support links, and contact form. - OG images: per-chapter and per-post 1200×630 PNGs at build time
- Netlify-friendly contact form markup on
/contact - Site search: real-time Lunr search in the header via bridgetown-quick-search; indexes guide chapters, blog posts, and pages
- Heading anchors:
#permalinks onh2–h4in guide and blog content (plugins/builders/heading_anchors.rb) - Code copy: copy-to-clipboard buttons on fenced code blocks (Stimulus
clipboardcontroller on guide and blog prose) - Stimulus: Hotwired Stimulus for client-side behavior that is not included in stock Bridgetown (see JavaScript with Stimulus below)
Chapter Zero is a static site built with Bridgetown. Collections (guide, blog), layouts, partials, plugins, and bin/bridgetown deploy all follow Bridgetown conventions.
For configuration, content model, and deployment details, use the official documentation: bridgetownrb.com/docs.
Bridgetown bundles frontend assets with esbuild and a frontend/javascript/index.js entrypoint, but does not ship Stimulus by default. Chapter Zero adds Hotwired Stimulus for small, declarative client-side behavior.
- Package:
@hotwired/stimulusinpackage.json - Bootstrap:
frontend/javascript/index.jsstarts a Stimulus application and exposes it aswindow.Stimulus - Controllers: files in
frontend/javascript/controllers/named*_controller.js(or*-controller.js) are auto-registered at build time
The identifier comes from the filename: clipboard_controller.js → data-controller="clipboard". Nested folders use double dashes (folder/foo_controller.js → data-controller="folder--foo").
- Create
frontend/javascript/controllers/my_feature_controller.js - Extend Stimulus's
Controllerclass - Attach it in a template with
data-controller="my-feature"
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
// runs when the element appears in the DOM
}
}Use data-action attributes to wire events (for example data-action="click->my-feature#handleClick"). Restart or let bin/dev rebuild the frontend bundle after you add or change controllers.
| Controller | Where | Purpose |
|---|---|---|
clipboard |
guide_chapter.erb, blog_post.erb prose wrapper |
Copy buttons on fenced code blocks |
See Code copy for details on the clipboard controller.
The UI is styled with DaisyUI component classes on top of Tailwind CSS v4. Buttons, cards, alerts, the support banner, and most layout chrome use DaisyUI patterns.
The active theme is set in frontend/styles/index.css:
@plugin "daisyui" {
themes: dark --default;
}To add themes, change the default, or customize colors and radius, follow the DaisyUI themes documentation. After you change CSS, restart bin/dev so Tailwind picks up the update.
Work through these before you write lesson one:
src/_data/site_metadata.yml: title, tagline, email, logo and favicon paths,testimonials_max,hide_suggest_topic, optionalconvertkit_form_id+convertkit_account, Stripesupport_url/support_url_development,google_analytics_id,contact_form_actionsrc/_partials/landing/: landing page sections (hero, steps, audience, newsletter, author card, and so on). Edit the markup in place or remove<%= render "landing/…" %>lines fromsrc/index.mdfor sections you do not want.src/_data/testimonials.yml: reader quotes (feedback,highlighted_text, optionalimage_pathfor site images orimage_urlfor external URLs). Setfeatured: trueon one entry for the spotlight; the masonry grid shows up totestimonials_maxmore (default 5 insite_metadata.yml).src/images/: logo, favicon,blog-og-background.png,og-image.png(keep default OG paths in sync withimagefront matter onindex.mdandguide.md)config/initializers.rb: set productionurlto your domain (https://yourdomain.com)src/_guide/01-…: your first real content chapter at order 1 (replace or delete the sample lesson-one placeholder when you are ready). Keep00-introduction-chapter-zero.mdfor local docs - it does not publish to production.
Set show_chapter_zero_credit: false in site_metadata.yml if you do not want the footer link to minitestrails.com/chapter-zero.
Run bin/dev and preview at http://localhost:4000 after each batch of changes.
UI is component-first: Ruby classes in src/_components/ with DaisyUI classes in matching .erb templates. Shared chrome (header, footer, support banner) and landing sections (src/_partials/landing/) keep pages and layouts small.
Guide chapters are Markdown in src/_guide/ with front matter like:
title: Your chapter title
description: One line for cards and OG images
order: 1
layout: guide_chapter
slug: your-url-segment
featured: true # optional; show on the landing page featured chapters section (sorted by order)
hide_content_feedback: true # optional; omit or set false to show the feedback linkThe slug becomes /guide/your-url-segment/. order controls sidebar sort and featured chapter order on the landing page.
Use is_coming_soon: true for chapters you have not published yet: readers see the coming-soon card and newsletter prompt instead of body content (see /guide/lesson-one).
Set hide_content_feedback: true on a guide chapter or blog post to hide the feedback link at the bottom of the page.
Set development_only: true to include a resource only in development builds (chapter 0 uses this). Production builds omit it from the site, sidebar, search, and OG output.
Search is powered by bridgetown-quick-search. The bar lives in src/_partials/_header.erb and indexes guide chapters, blog posts, and pages at build time into /bridgetown_quick_search/index.json.
Front matter on any page or collection document:
exclude_from_search: true- omit from the index (used on the landing page, contact, and privacy)quick_search_content: "…"- override the text indexed for that page (defaults to rendered page content)
The search component accepts Liquid variables (see _header.erb for the live config):
| Variable | Description |
|---|---|
placeholder |
Input placeholder text |
input_class |
CSS classes on the search input |
theme |
"dark" or "light" (popup theme) |
snippet_length |
Character length of each result snippet (default 142) |
display_collection |
Show which collection each result belongs to |
The results popup is a shadow-DOM web component. Tweak it in frontend/styles/bridgetown-quick-search.css with CSS variables on bridgetown-search-results (--link-color, --divider-color, --text-color, --border-radius) or ::part() selectors. See the plugin README for details.
These are small UX additions on top of stock Bridgetown-they ship with Chapter Zero and need no extra setup.
plugins/builders/heading_anchors.rb adds a # link after every h2, h3, and h4 inside <article> that has an id. Readers can click or copy a permalink to that section.
Give headings stable IDs in Markdown with Kramdown's attribute list syntax:
## Local development {#local-development}Styles live in frontend/styles/guide-chapter.css (scroll-margin-top keeps anchored headings clear of the header).
Guide chapters and blog posts wrap prose in data-controller="clipboard" (guide_chapter.erb and blog_post.erb). The Stimulus controller in frontend/javascript/controllers/clipboard_controller.js adds a copy button to each pre.highlight block. Styles are in frontend/styles/clipboard.css. No extra markup in your Markdown-fenced code blocks with a language tag get copy buttons automatically. See JavaScript with Stimulus for how controllers are registered.
Guide chapters and blog posts are Markdown (Kramdown + GFM). For a rendered reference of headings, lists, code, tables, footnotes, and more, see the sample post /blog/hello-world (Hello from the blog).
Render callouts with Shared::Tip in guide Markdown via ERB. In .erb files use a single %; in guide Markdown escape as %% so Bridgetown does not evaluate at build time. Blog posts are plain Markdown-no ERB components.
Fenced blocks with a language tag get syntax highlighting and a copy button (see Code copy). Examples are in the Markdown reference post.
Posts live in src/_blog/ with layout: blog_post, a date, and a slug. They appear on the landing page and at /blog.
Chapter and blog posts get auto-generated 1200×630 PNGs at build time (saved under output/og/).
During bin/dev, open any OG image directly in the browser by inserting /og/ into the page URL and appending .png:
http://localhost:4000/og/guide/introduction-chapter-zero.png
The pattern is /og/{collection}/{slug}.png for guide and blog collections. Examples:
- Guide chapter:
/og/guide/introduction-chapter-zero.png(matches/guide/introduction-chapter-zero/in development) - Blog post:
/og/blog/hello-world.png(matches/blog/hello-world/)
In production, swap the host: https://yourdomain.com/og/guide/your-chapter-slug.png.
The /og/… URLs are served by a Roda route in server/routes/og_image.rb. During bin/dev, if a pre-built PNG is missing, the route generates one on the fly from output/og/{collection}/manifest.json.
Only collections listed in OG_COLLECTIONS are handled:
OG_COLLECTIONS = %w[guide blog].freezeIf you add a new collection and want the same /og/{collection}/{slug}.png previews, add its label to OG_COLLECTIONS in server/routes/og_image.rb. For build-time PNG generation, update the matching OG_COLLECTIONS constant in plugins/builders/site_og_images.rb as well.
The build needs:
- ImageMagick (
magickorconverton PATH) - rsvg-convert (librsvg)
bin/bridgetown deployPublish the output/ folder. Netlify config is included in netlify.toml.
MIT. See LICENSE.