This version of the documentation is outdated, and features documented here may work differently now. You can see the latest stable version of the docs here.

Incremental Generation

Arguably the most powerful strategy in Perseus is incremental generation, which is an extension of build paths such that any path in the template's root path domain (more info on that concept here) will result in calling the build state strategy while the server is running.

A perfect example of this would be an retail site with thousands of products, all using the product template. If we built all these with build paths, and they all require fetching information from a database, builds could take a very long time. Instead, it's far more efficient to use incremental generation, which will allow any path under /product to call the build state strategy, which you can then use to render the product when it's first requested. This is on-demand building. But how is this different from the request state strategy? It caches the pages after they've been built the first time, meaning you build once on-demand, and then it's static generation from there. In other words, this strategy provides support for rendering thousands, millions, or even billions of pages from a single template while maintaining static generation times of less than a second!

Also, this strategy is fully compatible with build paths, meaning you could pre-render you most common pages at build-time, and have the rest built on-demand and then cached.

Usage

This is the simplest strategy in Perseus to enable, needing only one line of code. Here's the example from earlier (which you can find here) that uses incremental generation together with build paths (and of course build state, which is mandatory for incremental generation to work):

// This is exactly the same as the build paths example except for a few lines and some names

use perseus::{blame_err, RenderFnResult, RenderFnResultWithCause, Template};
use sycamore::prelude::{view, Html, View};

#[perseus::make_rx(PageStateRx)]
pub struct PageState {
    title: String,
    content: String,
}

#[perseus::template_rx]
pub fn incremental_generation_page(state: PageStateRx) -> View<G> {
    let title = state.title;
    let content = state.content;
    view! {
        h1 {
            (title.get())
        }
        p {
            (content.get())
        }
    }
}

pub fn get_template<G: Html>() -> Template<G> {
    Template::new("incremental_generation")
        .build_paths_fn(get_build_paths)
        .build_state_fn(get_build_state)
        // This line makes Perseus try to render any given path under the template's root path (`incremental_generation`) by putting it through `get_build_state`
        // If you want to filter the path because some are invalid (e.g. entries that aren't in some database), we can filter them out at the state of the build state function
        .incremental_generation()
        .template(incremental_generation_page)
}

// We'll take in the path here, which will consist of the template name `incremental_generation` followed by the spcific path we're building for (as exported from `get_build_paths`)
#[perseus::autoserde(build_state)]
pub async fn get_build_state(path: String, _locale: String) -> RenderFnResultWithCause<PageState> {
    // This path is illegal, and can't be rendered
    // Because we're using incremental generation, we could gte literally anything as the `path`
    if path == "incremental_generation/tests" {
        // This tells Perseus to return an error that's the client's fault, with the HTTP status code 404 (not found) and the message 'illegal page'
        // You could return this error manually, but this is more convenient
        blame_err!(client, 404, "illegal page");
    }
    let title = path.clone();
    let content = format!(
        "This is a post entitled '{}'. Its original slug was '{}'.",
        &title, &path
    );

    Ok(PageState { title, content })
}

// This just returns a vector of all the paths we want to generate for underneath `incremental_generation` (the template's name and root path)
// Like for build state, this function is asynchronous, so you could fetch these paths from a database or the like
// Note that everything you export from here will be prefixed with `<template-name>/` when it becomes a URL in your app
//
// Note also that there's almost no point in using build paths without build state, as every page would come out exactly the same (unless you differentiated them on the client...)
pub async fn get_build_paths() -> RenderFnResult<Vec<String>> {
    Ok(vec!["test".to_string(), "blah/test/blah".to_string()])
}

All we need to do is run .incremental_generation() on the Template, and it's ready.

Note that this example throws a 404 Not Found error if we go to /incremental_generation/tests, which is considered an illegal URL. This is a demonstration of preventing certain pages from working with this strategy, and such filtering should be done in the build state strategy.