Static Site Generation

Start static, go dynamic

Raindeer is a dynamic web framework but it can also export markdown files to a static site. This allows for a site that can start off static and become dynamic, or the reverse. In fact, this very documentation site is generated from a Raindeer application.

File Structure

Place your markdown files in app/pages.

URLs

File paths define the URL: A path of app/pages/docs/static.md will result in the URL http://example.com/docs/static.

The humble underscore: In summary, an underscore _ followed by a string or number until a - or / adds metadata to a folder/file whilst hiding that part of the filepath from the URL.

Assets

Put publicly accessible assets such as images and files in the /public folder. The URL for a file like /public/logo.png will be /logo.png.

Markdown

GitHub flavoured markdown is supported out of the box.

Required Frontmatter:

---
title: Heading 1
published: true
---

## Heading 2

Raindown

In addition to Markdown, Raindown provides macros to make working with Markdown files easier.

Table of Contents

Insert a table of contents that links to each heading on your page:

<{ :toc }>

Quote [UNRELEASED]

Insert a <blockquote> with correct <figure> semantics and author attribution:

<{ quote: 'Name' }>
  The sentence said by some guy who died
<{ :quote }>

List

List markdown files, filtered by specified metadata:

<{ list: item folder: 'basics' }>
  {item.title}
<{ :list }>

Available metadata:

List items are sorted by order by default. Change the ordering with:

<{ list: item folder: 'basics' order: :created_at }>
  {item.title}
<{ :list }>

Embed [UNRELEASED]

Include the contents of another markdown file:

<{ embed: "/components/our-story.md" }>

Note

File paths can be relative (../) or absolute (/)

Custom Component

Use nodes and Antlers to create custom components with their own HTML and access to Ruby.

First define your component:

class CustomComponent < LowNode
  def render
    <html>{"Any text that you can dream of"}</html>
  end
end

Then call it in a Markdown/Raindown file:

<{ CustomComponent }>

Warning

If you namespace your component then you need to configure that namespace in Raindown

Metadata

Metadata is made available to Raindown macros (such as "List") and the Raindeer application (See "Layout").

Explicit metadata

Add metadata via the markdown file's frontmatter:

---
tags: ['tag_1', 'tag_2', 'tag_3']
date: 2099
---

Implicit metadata

Folder tags

Underscored values in file paths are automatically converted to metadata. For example, in the following file path...

app/pages/docs/_basics/_1-getting-started.md

...the _basics and _1 prefixes produce metadata that is equivalent to the following frontmatter:

---
folder: basics
order: 1
path: /docs/getting-started
---

path is automatically available too without manual entry, representing the absolute URL path.

Note

Separate multiple folder tags with &:
app/pages/docs/_1&_basics/_1-getting-started.md

Label tags [UNRELEASED]

Any uppercase text between two square brackets [] is considered a label tag. This is useful for defining the current status of an article, project or feature. For example:

A label tag can be put in title: frontmatter or ## heading and NOT have its characters visible in the URL or anchor link. Additionally it outputs CSS classes for more label-like styling.

Collections

Collections are arrays of records that can be rendered anywhere; a page, a node, and optionally accessed directly via URL. Unlike other static site generators, you don't need to configure where collections live. Using the two established patterns of The humble underscore™ and Implicit metadata, simply put underscore prefixed markdown files in underscore prefixed folders.

For example:

/app/pages/_cards/_fast.md

The _cards adds folder: cards metadata to every child file while hiding the folder from the URL, and the _fast.md hides the file from the URL. This file is now accessible only through metadata. Render these files elsewhere with lists:

Rendering in a page

<{ list: card folder: 'cards' }>
  {card.title}
<{ :list }>

Rendering in a node

class CardsNode < LowNode
  def initialize
    @cards = Raindeer.pages.list(folder: 'cards')
  end

  def render
    <{ for: card in: @cards }>
      {card.title}
    <{ :for }>
  end
end

Layout

By default a new Raindeer application contains a PageRenderer that renders a markdown file that matches the URL request. In this file you can wrap a LayoutNode around the page's HTML output, just like you would do in a typical Raindeer application.

Building

Raindeer bypasses the normal async server when building a static site, requesting routes and their corresponding markdown files directly in-process.

To export app/pages to a static site run:

rain build

This exports a package of typical website files in the /build folder that can be uploaded to any web service that accepts static sites such as GitHub, GitLab, Cloudflare and Codeberg Pages.