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.

Build Paths

As touched on in the documentation on the build state strategy, Perseus can easily turn one template into many pages (e.g. one blog post template into many blog post pages) with the build paths strategy, which is a function that returns a Vec<String> of paths to build.

Note that it's often unwise to use this strategy to render all your blog posts or the like, but only render the top give most commonly accessed or the like, if any at all. This is relevant mostly when you have a large number of pages to be generated. The incremental generation strategy is better suited for this, and it also allows you to never need to rebuild your site for new content (as long as the server can access the new content).

Note that, like build state, this strategy may be invoked at build-time or while the server is running if you use the revalidation strategy (incremental generation doesn't affect build paths though).

Usage

Here's the same example as given in the previous section (taken from here), which uses build paths together with build state:

use perseus::{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 build_paths_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("build_paths")
        .build_paths_fn(get_build_paths)
        .build_state_fn(get_build_state)
        .template(build_paths_page)
}

// We'll take in the path here, which will consist of the template name `build_paths` 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> {
    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 `build_paths` (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![
        "".to_string(),
        "test".to_string(),
        "blah/test/blah".to_string(),
    ])
}

Note the return type of the get_build_paths function, which returns a RenderFnResult<Vec<String>>, which is just an alias for Result<T, Box<dyn std::error::Error>>, which means that you can return any error you want. If you need to explicitly return Err(..), then you should use .into() to perform the conversion from your error type to this type automatically. Perseus will then format your errors nicely for you using fmterr.

Also note how this page renders the page /build_paths by specifying an empty string as one of the paths exported from get_build_paths. It's a very common structure to have something like /blog and then /blog/<post-name> as a website structure, with /blog being a list of all posts or the like. However, Perseus would require this to all be done in the same template (since it's all under the same URL), so you'd need to use an enum as your state that would then render an alternative view for the root page. Alternatively, you could use a /post/<post-name> and /posts structure, which would let you use two different templates.