pub fn try_render(
config: &PageConfig<'_>,
ctx: &RenderCtx,
) -> Result<PageResponse, RenderError>Expand description
Render a page on the server: the page with its counts, or why not.
The bridge to 3.0.0’s render. Unlike the deprecated
render_page, it never substitutes an empty shell for a failure: a
missing module, a walk the module or data fails, an exceeded budget, an
invalid policy extension, a failed nonce draw and a panic each come back
as a RenderError with a stable code, the route and the component
path. The page is written into its own buffer and returned whole, or not
at all, so no partial page leaves. Forma logs none of these: the handler
chooses the response. (A caught panic still runs the process’s panic hook
first: the default hook prints it to stderr, and a reporting hook, such as
an error tracker’s, records it.)
It always renders on the server: config.render_mode steers only the
deprecated render_page. A route the build declares ssr: false is
served by render_browser_shell.
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use forma_server::{try_render, PageConfig, RenderCtx};
fn home(config: &PageConfig) -> Response {
match try_render(config, &RenderCtx::new()) {
Ok(page) => page.into_response(), // no-store, policy, headers
Err(error) => {
// Count it by code and route; serve the application's error page.
eprintln!("{error}");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}§Threads and panics
The walk is synchronous work on the caller’s thread, and forma-server
starts nothing and depends on no async runtime. At the measured 12.5
million nodes a second, a page past about 12,500 nodes holds the thread
past a millisecond; RenderCounts reports each page’s nodes and walk
time, so a handler whose counts show that can move the render into tokio’s
blocking pool itself (owned inputs, since the closure must be 'static):
async fn heavy(
manifest: Arc<AssetManifest>,
module: Arc<IrModule>,
slots: SlotData,
) -> Result<PageResponse, RenderError> {
tokio::task::spawn_blocking(move || {
let config = PageConfig {
title: "Ledger",
route_pattern: "/ledger",
manifest: &manifest,
config_script: None,
config_json: None,
body_class: None,
personality_css: None,
body_prefix: None,
render_mode: RenderMode::Phase2SsrReconcile,
ir_module: Some(&module),
slots: Some(&slots),
};
try_render(&config, &RenderCtx::new())
})
.await
.expect("the blocking task ran")
}A panic inside the render is caught and returned as
RenderError::Fault, which holds only while the application
unwinds on panic, as both templates leave it. Under panic = "abort" a
fault still ends the process with every request in flight.
Verified by: crates/forma-server/tests/try_render.rs > “a_panicking_component_returns_fault_the_handler_answers_500_and_the_next_request_renders”