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.
The Index View
In most Perseus apps, you can just focus on building your app's templates, and leave the boilerplate entirely to the Perseus engine, but sometimes that isn't quite sufficient, like if you want to use one stylesheet across your entire app. In traditional architectures, these are the kinds of modifications you might make to an index.html
file that a framework inserts itself into, and you can do exactly this with Perseus! If you provide an index.html
file in the root of your project (not inside src/
), Perseus will insert itself into that!
However, if you're using Perseus, you probably don't want to be writing HTML right? You're supposed to be using Sycamore! Well, that's completely true, and so Perseus supports creating an index view with Sycamore code! You can do this like so:
mod error_pages;
mod templates;
use perseus::{Html, PerseusApp, PerseusRoot};
#[perseus::main]
pub fn main<G: Html>() -> PerseusApp<G> {
PerseusApp::new()
.template(crate::templates::index::get_template)
.template(crate::templates::about::get_template)
.error_pages(crate::error_pages::get_error_pages)
.index_view(|| {
sycamore::view! {
// We don't need a `<!DOCTYPE html>`, that's added automatically by Perseus (though that can be overriden if you really want by using `.index_view_str()`)
// We need a `<head>` and a `<body>` at the absolute minimum for Perseus to work properly (otherwise certain script injections will fail)
head {
title { "Perseus Index View Example" }
}
body {
// This creates an element into which our app will be interpolated
// This uses a few tricks internally beyond the classic `<div id="root">`, so we use this wrapper for convenience
PerseusRoot()
// Because this is in the index view, this will be below every single one of our pages
// Note that elements in here can't be selectively removed from one page, it's all-or-nothing in the index view (it wraps your whole app)
// Note also that this won't be reloaded, even when the user switches pages
footer { "This is a footer!" }
}
}
})
}
Note that you can also use .index_view_str()
to provide an arbitrary HTML string to use instead of Sycamore code.
It's also important to remember that whatever you put in your index view will persist across all the pages of your app! There is no way to change this, as Perseus literally injects itself into this, using it as a super-template for all your other templates!
Requirements for the Index View
Perseus' index view is very versatile, but there are a few things you HAVE to include, or Perseus moves into undefined behavior, and almost anything could happen! This mostly translates to your app just spitting out several hundred errors when it tries to build though, because none of the tactics Perseus uses to insert itself into your app will work anymore.
- You need a
<head>
. This can be empty, but it needs to be present in the form<head></head>
(no self-closing tags allowed). The reason for this is that Perseus uses these tags as markers for inserting components of the magical metadata that makes your app work. - You need a
<body>
. This needs to be defined as<body></body>
, for similar reasons to the<head>
. - You need a
<div id="root"></div>
. Literally, you need that exact string in your index view, or Perseus won't be able to find your app at all! Now, yes we could parse the HTML fully and find this by ID, or we could just use string replacement and reduce dependencies and build time. Importantly, you can't use this directly is you use.index_view()
and provide Sycamore code, as Sycamore will add some extra information that stuffs things up. Instead, you should useperseus::PerseusRoot
, which is specially designed to be a drop-in entrypoint for Perseus. It should go without saying that you need to put this in the<body>
of your app.
Note: you don't need the typical <!DOCTYPE html>
in your index view, since that's all Perseus targets, so it's added automatically. If, for some magical reason, you need to override this, you can do so with a control plugin.