Raw mode

I recently proposed that it should be possible to break up the markdown of an .md file in order to address sections of it seperately in the Tera templates. This is working well in my fork, and allowed me to simplify and clean up some of my pages and templates. However, it somehow still felt like I was working against Zola, so I figured I’d write a toy SSG to try to identify where that was coming from.

I think I have now identified what’s going on. Essentially, Zola is a very good blog generator, but if your website is not sufficiently like a blog, you’re going to have to do some ugly things.

The main difference between my toy SSG and Zola is that the content hierarchy represents the layout of the website rather than the structure of the data. The placeholders for HTML pages are .page files, which are pure TOML files. There is no concept of a section – if you want to have a page representing a section, just write a page representing a section. index.page does have one magical property which is that the weight defaults to None, meaning that it won’t, by default, appear in a sibling list, so this is probably a good place for it.

Each .page file has an optional [content] section listing files to load and an optional [data] section for ad hoc data. The only top level items are template to link the template to present the data to, and weight to control the ordering of siblings. There is no markdown involved. In fact markdown is explicitly out of scope in general. If you want to write markdown, rst, asciidoc or whatever, go right ahead, then convert it to HTML and reference the HTML file in the [content] section to present it to the templates.

Everything in the content hierarchy gets copied across verbatim to the public hierarchy other than the .page files, which get replaced with the created .html files, and any directories containing a file named .ignore. If you want SASS, SCSS or whatever, just write your code, stick an .ignore file in the directory containing that code and pre-process it to a CSS directory or whatever to be copied across.

Files listed under [content] may be HTML or TOML, and the HTML may be sharded. The templates have access to the data from all pages, the weighted relationship between siblings and the sub-directories of the directory they are in. This gives a huge amount of flexibility in structuring the data and text content of your website.

Since the content hierarchy is exactly the same as the layout of the site, all internal links can be handled as relative links. A variable called root is provided to the templates to facilitate this. {{root}}/css/styles.css, for example, refers to /content/css/styles.css from any .page file. This means that the whole public hierarchy can be dropped anywhere, and it just works. You can even test in the browser with file:// urls. This has allowed me to go back to my preferred workflow of uploading to a staging domain and then synchronising that with a production domain once the website has been thoroughly tested.

All of which is to say that in around 300 lines of Python, my toy SSG covers a great many of the feature requests in this forum. I have now thoroughly scratched my own particular itch, and will be using it going forward. I think, however, that there would be value in allowing Zola to operate in the same way for those that are not building blog-like websites. Obviously this way of operating is completely opposed to the normal way Zola operates, so it would have to be a separate mode that you could select in the config file.

I’m honestly not at all sure this is a good idea, but I thought I would put it out there for discussion.

That’s an interesting idea! Thank you for posting the summary here!

For me personally though, the main point of SSGs is separation between data and representation. I don’t want to think about <div>s when I’m writing a post and I don’t want to care about the content when I’m writing a theme. Markdown plays a crucial role here, since it’s pleasant to write format (as opposed to HTML/XML) without too much clutter (but with many known downsides ofc, which are too minor for a successor to emerge yet).

I think with section deprecation Zola will take a step to unifying the content layout and the website layout.

Correct me if I’m wrong, but the proposed approach feels more like a linker rather than a renderer?

For the website that drove the development I found that data and representation were actually better separated. The representation all ends up in the templates, and the HTML content generally has nothing more complicated than <p> and <em> tags in it. The images are just a TOML array of relative urls in the [data] section of the .page file. It probably also does not help that personally I am more comfortable writing HTML than markdown – my markdown always ended up spattered with HTML sections wherever the going got tough.

Interestingly, themes ended up explicitly out of scope. There was just no natural way to do them. If you wanted, though, you could download a “starter site” which gives the same effect with much less magic. Themes actually cut to the heart of the matter; its an implicit versus explicit thing. Zola excels at implicit: download a theme, put some markdown in the right place with the right magic headers and hey presto! you have a great looking blog. There is absolutely nothing wrong with this, as long as you are willing to play within the rules. It only breaks down when idiots like me want to use it to make a website where every aspect is controlled.

There’s something in that, for sure. Perhaps my toy SSG is in actuality a fundamentally different interpretation of what an SSG actually is than that underlying Zola. It certainly makes most sense as a completely separate tool, unconnected in any way with Zola; my suggestion for including it as an alternate mode of Zola is purely driven by the fact that this forum suggests people are trying to force Zola to do the things that my toy SSG does very naturally and finding that experience frustrating.

I should probably at least give some code samples. These are the files involved in creating index.html:

content/index.page:

template = "home.html"
content.main = "data/index.html"

[data]
title = "Emily Hurst Millinery"

[[data.featured]]
bgimg = "images/featured-ss25.webp"
href =  "collections/ss25/index.html"
text =  "Explore the SS25 Collection"

[[data.featured]]
bgimg = "images/featured-about.webp"
href =  "about.html"
text =  "About Emily Hurst"

content/data/index.html:

<!-- shard:hero -->
  <h1>CRAFTED BY TRADITION</h1>
  <h2>DESIGNED FOR TODAY</h2>
<!-- shard:blurb -->
  <h2>Discover the beauty of craftsmanship that stands the test of time</h2>

  <p>Luxury headwear created by blending timeless craftsmanship with modern
    design. Using traditional straw techniques alongside carefully sourced
    materials, Emily honours heritage and endeavors to renew endangered crafts.
    All pieces are carefully crafted by hand to add a touch of elegance, while
    making a positive impact on the environment.</p>

templates/home.html:

{% extends "base.html" %}
{% block header %}
<link rel="preload" as="image" href="images/hero.webp" fetchpriority="high">
{% endblock %}
{% block content %}
<div class="j-page__content" style="background-image: url(images/hero.webp);">
<div class="j-hero-title">
  {{ page.content.main.hero[0].html }}
</div>
<div class="j-homepage-blurb">
  {{ page.content.main.blurb[0].html }}
</div>

{% if page.data.featured %}
<div class="j-featured">
  <h1>FEATURED</h1>
  <hr>
  <div class="j-featured__cards">
    {% for card in page.data.featured %}
    <div class="j-featured__card"
         style="background-image: url({{card.bgimg}});">
      <a href="{{card.href}}">{{card.text}}</a>
    </div>
    {% endfor %}
  </div>
</div>
{% endif %}
</div>
{% endblock %}

Pondering further on this…

I think that ultimately the best way to integrate this into Zola would be a new command, zola raw. This would mean the only change to the current codebase would be an extra few lines in the command line parsing, and that there is an expectation that none of the other Zola commands could be used when using “raw” processing.

My thinking is that I will port the prototype from Python/Jinja to Rust/Tera at some point in the next few months, ensuring that the TOML crate is the same as used by Zola. Since the only dependencies will be this TOML crate and Tera, integrating this code would be a very small addition to Zola’s footprint. I will build it as a library crate with a standalone binary, and I will leave it up to @keats as to whether he wants to integrate it. If he does, great, if not, the standalone binary will be available to anyone that wants it.

Please would an admin change the title of this thread to “Raw mode”, as I think that more accurately describes what’s being discussed. Many thanks!

The prototype site generator that I built to explore why Zola wasn’t working for me has now evolved into a nice little application in its own right. Documentation and download from https://hursts.org.uk/ssg, source code on github, if you’re interested.

Unfortunately, the directions it evolved in have taken it too far away from Zola to realistically fold it in as an alternative mode; the coding would be straightforward, but the documentation would be all but impossible.

A couple of features could easily and very usefully be added to Zola if the maintainers want to take a look. The [data] and [content] sections would make a really nice addition to Zola’s frontmatter concept and the sharding has also turned out very nicely, particularly when combined with loading from the [content] section. I think it works much better than my original markdown sharding idea.