Your First Site

If you followed the Quick Start guide, you should have a stub of your new page. It is really good place to start your journey with Stag!

Content

Websites don’t exist without content. Static site generators use some markup files to create HTML documents. These days Markdown is hands down the most commonly used markup language. Besides Markdown, Stag also supports Asciidoc, through Asciidoctor. You can integrate other markup languages by creating a custom plugin.

Stag reads input files from content directory and writes output documents to _output directory.

For now, let’s suppose that we’ll stick with Markdown. Inside the content directory there’s index.md file will generate the front page. We can put other files and each one will generate another page. Subdirectories inside content also have a meaning: they structure our site.

Metadata

Each page should provide at least some minimal metadata. For Markdown metadata is encoded in a front-matter, which is a special section of TOML at the beginning of Markdown file. It looks like this:

+++
title = "My First Post"
date = 2023-09-16
tags = ["foo", "bar"]
+++

For Asciidoc we can may its metadata fields specified by the format itself:

= My First Post
:date: 2023-09-16
:tags: [foo, bar]

We may put anything in metadata blocks, but some fields are more important than the others (sorry, there’s no democracy here):

  • title - page title;

  • date - page publication date, or its first publication date in ISO 8601 format;

  • lastmod - date of last modification; if not set, it’ll be automatically copied from date;

  • type - page type, used to select a different template than the default one.

Metadata fields may be accessed in templates by either page.metadata or shortcut: page.md. For example, we can set page’s title like that:

<title>Your First Site</title>

Template

When you run stag build or stag serve, Stag renders the content through Jinja templating engine. Rendered output will be put in _output directory.

Stag passes 3 variables to each template:

  • content - rendered output of page; in fact, it is the same as page.output.content;

  • page - all informations about page gathered by Stag and plugins before rendering

  • site - collection of pages and data about them.

Besides these variables, templates may also use template functions and macros.

Default theme is rather barebones so we probably need to create a new one. We can place it in a configurable themes/default directory. We may also want to run a built-in development web server, which automatically rebuilds _output when it detects any changes. To do this, run stag serve.

Tip
When you’re running stag serve and you don’t see your changes updated, check the console output to see if it had regenerated correctly.

Template Structure

In Stag each page has its own type, configured by a type metadata field. Default type is page for ordinary pages and list for listing pages, like taxonomies.

Stag chooses templates based on page.metadata.type and desired output format (HTML in our case). For ordinary pages it’ll use themes/default.page.html and for listing pages it’ll use themes/default/list.html.

Besides templates, themes need static files, like CSS or JavaScript files. Stag copies them from static subdirectory.

The overview of basic theme structure looks like this:

themes/
  default/
    static/
      favicon.png
      style.css
    base.html
    index.html
    list.html
    page.html
    rss.xml

Base Template

If we use several page types, we’ll inevitably of repeating the same HTML skeleton over and over again. To prevent it, we may use template inheritance. In our example we’re going to define a base template, base.html, which defines certain blocks filled by specific templates. There will be 3 blocks in our skeleton:

  • header - optional header, which will use the default greeting if child template doesn’t provide it

  • sidebar - optional sidebar, which will be empty if child template doesn’t provide it

  • main - main section for page content

{% from "stag.html" import absurl with context %} (1)

<!DOCTYPE html>
<html lang="{{ site.config.language }}"> (2)
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if page.md.author -%}<meta name="author" content="{{ page.md.author }}">{% endif %} (3)

<title>{%- if page.metadata.title -%}{{ page.metadata.title }} &middot; {% endif %}{{ site.config.title }}</title> (4)

<link rel="stylesheet" href="{{ absurl("style.css") }}"> (5)
<link rel="icon" type="image/png" sizes="16x16" href="absurl("favicon.png")">
</head>

<body>
<div id="layout" class="layout-center">

  {% block header %} (6)
  <header>
    <h1>Hello!</h1>
  </header>
  {% endblock %}

  {% block sidebar %}{% endblock %} (7)
  {% block main required %}{% endblock %} (8)

</div>
</body>
</html>
  1. First, we import absurl macro from built-in macros. Most macros require importing them with context in templates, which enables them to use site and page variables.

  2. Set page language to the value of language, which is configured in config.toml

  3. Check whether page has an author and add it to page’s <meta>.

  4. Check whether page has a title and use it to create the HTML <title> tag.

  5. Use absurl macro to create absolute URL to style.css. You should prefer to create all inter-site links with either ref, absref or absurl macros which allows to correctly create URLs relatively to configured base URL, and also when using stag serve.

  6. Start of header block. If not provided in child templates, header block will render its contents from base.html template. Blocks start with {% block name %} and end with {% endblock %}.

  7. Start and end of sidebar block.

  8. Start and end of main block. Main block is required: it is an error of child template to not provide it.

Each template which wants to reuse this skeleton must extend it by putting {% extends "base.html" %} inside.

Ordinary Pages

Ordinary pages render from page.html template.

Here, page.html template extends all of the blocks which base.html skeleton defines:

  • inside header we’ll put page’s title;

  • inside sidebar we’ll put page’s table of contents, if it is available and when it isn’t disabled;

  • inside main we’ll put page’s contents.

{% extends "base.html" %} (1)

(2)
{% block header %}
<div id="header"><h1>{{ page.md.title }}</h1></div>
{% endblock %}

(2)
{% block sidebar %}
{%- if page.toc and page.md.get("toc", True) -%} (3)
<aside id="sidebar">
  {{ page.toc.content }}
</aside>
{%- endif -%}
{% endblock %}

(2)
{% block main %}
<main>
  {% if page.md.get("tags") -%}
  <div class="taglist">
    Tags:
    {%- for tag in page.md["tags"] -%}
      <a class="tag" href="{ ref("tags/" + slugify(tag)) }">{{ tag }}</a>{%- if loop.nextitem -%}, {%- endif -%} (4)
    {%- endfor %}
  </div>
  {%- endif %}

  <article id="post" class="content">
    {{ content }} (5)
  </article>
</main>
{% endblock %}
  1. Mark that this template extends base.html skeleton.

  2. Beginnings of overridden blocks.

  3. Generate table of contents in the sidebar. Table of contents is stored separately from page contents inside page.toc.content. Additionally, our template checks if table of contents is disabled for a particular page through page’s metadata.

  4. Use tags assigned to the page to generate links to taxonomy terms. We’re using slugify() function to create a string passed to ref() macro. It isn’t perfect, but works and is rather straightforward.

  5. Enter page’s content

Front Page

Many web pages have a front page which looks different from the other pages. We can achieve this in Stag by assigning a specific page type for content/index.md file. For example, something like this will force Stag to use index.html template to render the page:

+++
type = "startpage"
+++

Hello and welcome to my front page!

Then we can follow the same techniques which we have used previously to create a separate theme for the front page.

Taxonomies

Pages can be grouped in taxonomies which we define in config.toml. This improves navigation for users, who can view similar pages. For example, we may create two taxonomies: category and tags.

[[taxonomies]]
key = "tags"
singular = "tag"
plural = "tags"

[[taxonomies]]
key = "category"
singular = "category"
plural = "categories"

We add page to taxonomy by assigning it in metadata:

+++
tags = ["personal", "cooking"]
category = "Recipes"
+++

Taxonomies are "special" kinds of pages, because they are virtual. It means that they do not have a physical file which represents each taxonomy in content directory. Instead, stag groups them under the path constructed from their plural form (so for example under /tags/personal, /tags/cooking and /categories/recipes).

Note
If Stag encounters a file which is named after the name of taxonomy, it incorporates it. See description of Taxonomies Plugin for details.

There are three ways we use taxonomies in templates. First, we can get list all taxonomy terms assigned to a particular page. For example, if we’d like to list all tags assigned to a page, we could do it like that:

page.html
{% from "stag.html" import ref with context %}

{% if "tags" in page.metadata -%}
<ul>
{% for tag in page.metadata["tags"] -%}
  <li><a href="{ ref("tags/" + slugify(tag)) }">{{ tag }}</a></li>
{%- endfor %}
</ul>
{% endif %}

We may also list all pages assigned to taxonomy term. Such list is by default rendered by a list template:

list.html
{% if page.term %}
<ul>
{% for termpage in page.term.pages -%}
  <li><a href="{{ termpage.relurl }}">{{ termpage.md.title }}</a></li>
{%- endfor %}
</ul>
{% endif %}

Finally, we can list all of taxonomy terms. Pages which group taxonomy terms (so for example /tags and /categories) have a separate type: taxonomy. This way we can have a separate template for them, instead of placing a complex logic in e.g list.html template:

taxonomy.html
<h1>List of {{ page.taxonomy.plural }}</h1>

<ul>
{% for term in page.taxonomy.terms -%}
    {%- set no = term.term.pages | length -%}
    <li>
        <a href="{{ term.relurl }}">{{ term.metadata.title }}</a>
        <small>({{ no }})</small>
    </li>
{%- endfor %}
</ul>

RSS Feed

RSS feeds are XML files which are used to notify about changes on your website. To create one, we should add 2 files:

  • content/rss.xml - source for our RSS feed, which will change the type of this page to rss; this file can be otherwise empty;

  • themes/default/rss.xml - template used for generating pages of type rss.

content/rss.xml
+++
title = "Recent changes"
type = "rss"
+++
themes/default/rss.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
      <title>{{ page.md.title }}</title>
      <link>{{ site.config.url }}</link>
      <description>{{ site.config.title }} - recent content</description>
      <generator>stag - git.goral.net.pl/stag.git</generator>
      {%- if site.config.language -%}<language>{{ site.config.language }}</language>{%- endif -%}
      <lastBuildDate>{{ page.md.date | rfc822format }}</lastBuildDate>
      <atom:link href="{{ page.url }}" rel="self" type="application/rss+xml" />

      {%- set subdir = page.source.reldirname -%}
      {% for item in (site.subpages_of(subdir, recursive=True) |
          pagetype("html") |
          selectattr("md.lastmod") |
          sort(attribute="md.lastmod_timestamp", reverse=True))[site.config.get("maxfeeds", 20)] %}
      <item>
        <title>{{ item.md.title }}</title>
        <link>{{ item.url }}</link>
        <guid>{{ item.url }}</guid>
        <pubDate>{{ item.md.lastmod | rfc822format }}</pubDate>
        <description>{{ item.output.content | escape }}</description>
      </item>
      {% endfor %}
  </channel>
</rss>