Skip to content

Conversation

@EvanCarroll
Copy link

@EvanCarroll EvanCarroll commented Oct 27, 2025

I've updated this architecture to support a rendering pipeline which everything goes through.

  • Minification is now a middleware.
  • Livereload is now a middleware.

At the end of every pipeline we call write(). It's no longer called as IO call at the end of a function (which only know it was writing an index.html, and thus would minify .txt). This works and passes all tests,

  • livereload knows if it's in a pipeline generated from an HTML
  • minification knows if its in a pipeline generating HTML

With this infrastructure, this patch

AI summary of patch..

Core Architecture

  ┌─────────────┐
  │  Renderable │ (trait)
  └──────┬──────┘
         │
         ├─ Page
         ├─ Section
         ├─ Taxonomy
         ├─ Pager
         ├─ Feed
         ├─ Sitemap
         └─ ... (11 types)
         │
         ▼
  ┌──────────────────┐
  │    Pipeline      │
  ├──────────────────┤
  │ 1. Render        │◄── Tera templates
  │ 2. Metadata      │
  │ 3. Middleware    │◄── Pluggable chain
  │ 4. Process       │
  │ 5. Write         │
  └──────────────────┘
         │
         ├─ LiveReloadMiddleware
         ├─ MinifyMiddleware
         └─ [future: compression, encryption]
         │
         ▼
  ┌──────────────────┐
  │  ProcessedContent│
  └──────────────────┘
         │
         ▼
  ┌──────────────────┐
  │  ContentWriter   │
  ├──────────────────┤
  │ BuildMode::Disk  │─── File system
  │ BuildMode::Memory│─── In-memory cache
  │ BuildMode::Both  │─── Both
  └──────────────────┘

Key Components

  1. Renderable Trait (renderable.rs)
  • Purpose: Unified interface for all content types
  • Methods:
    • render() - produces HTML/XML/JSON from templates
    • metadata() - provides routing & content type info
  • Implementations: 11 types (Page, Section, Taxonomy, Feed, Sitemap, robots.txt, 404, Pager, Redirect, etc.)
  1. Pipeline (pipeline.rs)
  • Purpose: Orchestrates the 5-stage rendering process
  • Stages:
    a. Render: Template → String (via Tera)
    b. Metadata: Extract path, content type, permalink
    c. Context: Wrap in MiddlewareContext
    d. Middleware Chain: Sequential transformations
    e. Return: ProcessedContent ready to write
  1. Middleware Trait (middleware.rs)
  • Purpose: Extensible content transformation
  • Interface: process(&self, ctx: &mut MiddlewareContext)
  • Context includes:
    • content: String - the HTML/XML/JSON
    • metadata: ContentMetadata - path, template, content type
    • config: Arc - site configuration
  1. Concrete Middleware
  • LiveReloadMiddleware (middleware/livereload.rs):
    • Injects <script src="/livereload.js"> before
    • Only processes ContentType::Html
  • MinifyMiddleware (middleware/minify.rs):
    • Removes whitespace from HTML
    • Conditional on config.minify_html
    • Only processes ContentType::Html
  1. ContentWriter (writer.rs)
  • Purpose: Handles output destination
  • BuildMode:
    • Disk: Write to filesystem (public/ directory)
    • Memory: Store in SITE_CONTENT cache (for live reload)
    • Both: Do both operations

Design Benefits

  1. Separation of Concerns: Rendering, processing, and writing are isolated
  2. Type Safety: ContentType enum ensures correct middleware application
  3. Extensibility: Add new middleware without touching core logic
  4. Testability: Each component has unit tests
  5. Future-Ready: Designed for compression & encryption (per commit message)
  6. Unified Flow: All 11 content types go through the same pipeline

Before vs After

Before: Rendering logic scattered, minification/livereload handled per-type

After: Single pipeline with pluggable middleware - every rendered item flows through the same chain

This architecture mirrors common web frameworks (Express.js middleware, ASP.NET pipeline) and provides a clean foundation for adding post-processing features like
compression or encryption.


  • write_content isn't smart enough to do what we need.
  • this is a cleaner architecture.
  • Now pages with txt templates are not minified.
  • Add test for text template whitespace preservation with minification
    • Test verifies that non-HTML templates (e.g., .txt) preserve whitespace
    • Created text_content.txt template with multiple spaces, tabs, and newlines
    • Created test page using the text template
    • Test passes with the minification fix (ed36c1e6) and fails without it
    • Updated page count assertions due to new test page

Addresses #3006

* `write_content` isn't smart enough to do what we need.
* this is a cleaner architecture.
* Now pages with txt templates are not minified.
* Add test for text template whitespace preservation with minification
  * Test verifies that non-HTML templates (e.g., .txt) preserve whitespace
  * Created text_content.txt template with multiple spaces, tabs, and newlines
  * Created test page using the text template
  * Test passes with the minification fix (ed36c1e6) and fails without it
  * Updated page count assertions due to new test page
Now all rendering is followed up with a pipeline of middlewares which is
aware of context. Minify and livereload are now middlewares in this
pipeline. Everything rendered goes through the same pipeline.

This infrastructure will easily support compression and encryption.
@EvanCarroll EvanCarroll changed the title Change where minification happens Implement a rendering pipeline with middle wares. Nov 7, 2025
@EvanCarroll EvanCarroll changed the title Implement a rendering pipeline with middle wares. Implement a rendering pipeline with middlewares. Nov 8, 2025
@EvanCarroll EvanCarroll changed the title Implement a rendering pipeline with middlewares. Implement a rendering pipeline with middleware (adds support for Brotli and Gzip, cleans up minification) Nov 8, 2025
@EvanCarroll EvanCarroll force-pushed the next branch 4 times, most recently from 35f306c to 9a81c46 Compare November 10, 2025 05:00
This does not compress on the fly. We should consider adding that as an
argument to --serve to. This only serves the compressed files zola
generates
Previously the middleware could only produce one text, and one binary
file. Now this limitation is gone. We can produce multiple text and
binary files.
Use WebAssembly for argon2, and Web Crypto API to decrypt.

Support modes of encryption based on paths and implement with
middleware.

* Direct key (env)
* Password supplying (uses Argon2) (env, or `config.toml`)
* Key generation

In the future we may also introduce a nice-password generator but for
now this proves out the idea.

See ENCRYPTION.md for more information on the middleware.

Closes getzola#2993
@EvanCarroll
Copy link
Author

@Keats if you determine this architecture isn't something you want to move towards, please tell me so I can make adjustments or quit closing out issues.

@ccjmne
Copy link
Contributor

ccjmne commented Nov 10, 2025

Seems really cool, but the current MR is a lot for a human to digest.
Do you think you could keep the encryption middleware in another PR for the time being? Though maybe @Keats can review it like that.

@EvanCarroll
Copy link
Author

EvanCarroll commented Nov 10, 2025

Seems really cool, but the current MR is a lot for a human to digest. Do you think you could keep the encryption middleware in another PR for the time being? Though maybe @Keats can review it like that.

Fair and certainly doable. Because the architecture isn't cleanly in one patch it makes this patch even more complex. I tweak it a few times. If this is needed. what I can do is rewrite the the patches entirely to sink the commits separately. It's a waste of time though if that's not needed.

Actually if the middlewares themselves had their own config parser, they would be entirely self-contained and this would be trivial..

The next patch that I want to do pulls out markdown rendering putting that into the middleware too. And closes out #2930, and #2420 and #2790. This would make adding first-class Tyrst the same way trivial.

@Keats
Copy link
Collaborator

Keats commented Nov 17, 2025

It is indeed way too big to review. A few other things:

  • The issue for secret content is closed so for now it shouldn't be implemented
  • Same-ish for pre-compression, I don't think it's super important
  • I think it's too complex overall? All we need to have is a way to differentiate HTML and other content and do different operations when wrtiting the content.

@EvanCarroll
Copy link
Author

EvanCarroll commented Nov 17, 2025

I'll get back this and rebase the patch to make it easier to process. I got one more project I have to get off my plate. The architectural changes, should make all the middle wares one file (or relatively small) so you can pick whatever middleware you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants