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.

Revalidation

While the build state and build paths strategies are excellent for generating pages efficiently, they can't be updated for new content. For example, using these strategies alone, you'd need to rebuild a blog every time you added a new post, even if those posts were stored in a database. With revalidation, you can avoid this by instructing Perseus to rebuild a template if certain criteria are met when it's requested.

There are two types of revalidation: time-based and logic-based. The former lets you re-render a template every 24 hours or the like, while the latter allows you to re-render a template if an arbitrary function returns true.

Time-Based Revalidation Usage

Here's an example of time-based revalidation from here (note that this uses incremental generation as well):

// This page exists mostly for testing revalidation together with incremental generation (because the two work in complex
// ways together)

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

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

#[perseus::template_rx]
pub fn revalidation_and_incremental_generation_page(state: PageStateRx) -> View<G> {
    view! {
        p { (format!("The time when this page was last rendered was '{}'.", state.time.get())) }
    }
}

pub fn get_template<G: Html>() -> Template<G> {
    Template::new("revalidation_and_incremental_generation")
        .template(revalidation_and_incremental_generation_page)
        // This page will revalidate every five seconds (and so the time displayed will be updated)
        .revalidate_after("5s".to_string())
        // This is an alternative method of revalidation that uses logic, which will be executed every itme a user tries to
        // load this page. For that reason, this should NOT do long-running work, as requests will be delayed. If both this
        // and `revaldiate_after()` are provided, this logic will only run when `revalidate_after()` tells Perseus
        // that it should revalidate.
        .should_revalidate_fn(|| async { Ok(true) })
        .build_state_fn(get_build_state)
        .build_paths_fn(get_build_paths)
        .incremental_generation()
}

// This will get the system time when the app was built
#[perseus::autoserde(build_state)]
pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause<PageState> {
    Ok(PageState {
        time: format!("{:?}", std::time::SystemTime::now()),
    })
}

pub async fn get_build_paths() -> RenderFnResult<Vec<String>> {
    Ok(vec!["test".to_string(), "blah/test/blah".to_string()])
}

This page displays the time at which it was built (fetched with build state), but rebuilds every five seconds. Note that this doesn't translate to the server's actually rebuilding it every five seconds, but rather the server will rebuild it at the next request if more than five seconds have passed since it was last built (meaning templates on the same build schedule will likely go our of sync quickly).

Time Syntax

Perseus uses a very simple syntax inspired by this JavaScript project to specify time intervals in the form xXyYzZ (e.g. 1w, 5s, 1w5s), where the lower-case letters are number and the upper-case letters are intervals, the supported of which are listed below:

  • s: second,
  • m: minute,
  • h: hour,
  • d: day,
  • w: week,
  • M: month (30 days used here, 12M ≠ 1y!),
  • y: year (365 days always, leap years ignored, if you want them add them as days)

Logic-Based Revalidation Usage

Here's an example of logic-based revalidation from here (actually, this example uses both types of revalidation):

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

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

#[perseus::template_rx]
pub fn revalidation_page(state: PageStateRx) -> View<G> {
    view! {
        p { (format!("The time when this page was last rendered was '{}'.", state.time.get())) }
    }
}

pub fn get_template<G: Html>() -> Template<G> {
    Template::new("revalidation")
        .template(revalidation_page)
        // This page will revalidate every five seconds (and so the time displayed will be updated)
        .revalidate_after("5s".to_string())
        // This is an alternative method of revalidation that uses logic, which will be executed every itme a user tries to
        // load this page. For that reason, this should NOT do long-running work, as requests will be delayed. If both this
        // and `revaldiate_after()` are provided, this logic will only run when `revalidate_after()` tells Perseus
        // that it should revalidate.
        .should_revalidate_fn(should_revalidate)
        .build_state_fn(get_build_state)
}

// This will get the system time when the app was built
#[perseus::autoserde(build_state)]
pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause<PageState> {
    Ok(PageState {
        time: format!("{:?}", std::time::SystemTime::now()),
    })
}

// This will run every time `.revalidate_after()` permits the page to be revalidated
// This acts as a secondary check, and can perform arbitrary logic to check if we should actually revalidate a page
pub async fn should_revalidate() -> RenderFnResultWithCause<bool> {
    // For simplicity's sake, this will always say we should revalidate, but you could amke this check any condition
    Ok(true)
}

If it were just .should_revalidate_fn() being called here, this page would always be rebuilt every time it's requested (the closure always returns true, note that errors would be Strings), however, the additional usage of time-based revalidation regulates this, and the page will only be rebuilt every five seconds. In short, your arbitrary revalidation logic will only be executed at the intervals of your time-based revalidation intervals (if none are set, it will run on every request).

Note that you should avoid lengthy operations in revalidation if at all possible, as, like the request state strategy, this logic will be executed while the client is waiting for their page to load.