---
title: FrameworkBuilder configuration
description: Configure FrameworkBuilder, security defaults, static mounts, and response behavior.
---

`FrameworkBuilder` turns routes, static services, and hardening layers into the Axum router you run in production.

Use this page when you need the complete setup surface in one place. Use [Routing](/docs/framework/routing), [Responses](/docs/framework/responses), [Static assets](/docs/framework/static-assets), and [Deployment](/docs/framework/deployment) for deeper workflow examples.

## Minimal FrameworkBuilder Setup

```rust
use axum::{routing::get, Router};
use proa::{HeadConfig, HeadUrlPolicy};
use proa::axum::{FrameworkBuilder, RouteResponse};
use crate::{layouts::RootDocument, pages::HomePage};

async fn home() -> RouteResponse {
    RouteResponse::ssr(RootDocument { children: HomePage })
}

let routes = Router::new().route("/", get(home));
let head = HeadConfig::default()
    .with_urls(HeadUrlPolicy::new("https://example.com", "/")?);

let app = FrameworkBuilder::new()
    .with_head(head)
    .with_dynamic_routes(routes)
    .build();
```

`RootDocument` is the `LAYOUT` component containing the document markup and head outlet; `HomePage` declares `ROLE = PAGE`. See [Layouts and Pages](/docs/framework/layouts-and-pages) for complete implementations. Replace the public origin with trusted application configuration.

Use `with_dynamic_routes` for an ordinary Axum router. Route specifications are optional: use `with_dynamic_routes_and_metadata` when you also need their route-mode or other specification metadata. Legacy route titles do not populate the managed head; use `render_head` or app-wide defaults.

The builder returns an Axum router; bind a TCP listener and pass both to `proa::axum::serve()` to run it. See [the complete application example](/docs/framework/sitemaps#run-an-application) for route setup, discovery endpoints, and server startup.

## FrameworkBuilder Methods

| Method | Use it for |
|--------|------------|
| `FrameworkBuilder::new()` | Create the builder with secure defaults. No manifest or configuration is required. |
| `.with_head(head)` | Install app-wide [managed head configuration and defaults](/docs/framework/metadata#configure-app-wide-defaults) for rendered routes. |
| [`.with_base_path(base_path)`](#frameworkbuilder-base-paths-and-rsjs-assets) | Set the public URL prefix for a mounted application. |
| [`.with_public_origin(origin)?`](/docs/framework/sitemaps) | Set the public HTTP(S) origin used by discovery endpoints. |
| [`.with_sitemap(entries)`](/docs/framework/sitemaps) | Serve one sitemap using the final public origin and base path. |
| [`.with_sitemaps(files)`](/docs/framework/sitemaps#multiple-sitemaps) | Serve named sitemap documents plus a sitemap index; accepts results from an awaited async generator. |
| [`.with_robots(robots)`](/docs/framework/robots) | Serve `/robots.txt` from a document or awaited generator result, with bot-specific groups and automatic sitemap/index links. Behind a prefix-stripping proxy, forward origin-root `/robots.txt` to this handler. |
| [`.with_rsjs_assets()`](#frameworkbuilder-base-paths-and-rsjs-assets) | Serve the linked RSJS runtimes and generated modules. |
| `.with_static_service(router)` | Mount a router under `/static`. |
| `.with_dynamic_routes(router)` | Merge an Axum router without a metadata map. |
| `.with_dynamic_routes_and_metadata(router, metadata)` | Merge Proa route specs plus computed route metadata. |
| `.with_body_limit(bytes)` | Override the global request body cap. |
| `.with_cross_origin_protection(bool)` | Toggle browser cross-origin unsafe request protection. |
| `.with_trusted_origin(origin)` | Add an exact trusted origin such as `https://admin.example.com`. |
| `.with_security_headers(config)` | Configure frame policy, HSTS, and CSP. |
| `.with_request_timeout(duration)` | Override the default request timeout. |
| `.without_request_timeout()` | Disable the timeout layer. Prefer raising the timeout first. |
| `.with_concurrency_limit(max)` | Add a global in-flight request cap. |
| `.with_catch_panic(bool)` | Convert panics into plain 500 responses instead of resetting the connection. |
| `.build()` | Produce the final Axum `Router`. |
| `.try_build()?` | Build the router while returning invalid discovery configuration as an error. |

## FrameworkBuilder Base Paths and RSJS Assets

These methods configure separate parts of the application:

- `with_base_path(BasePath)` sets the public mount used by `cx.url(...)`
  and framework-generated URLs. Island query/mutation requests follow it only
  when declared with `cx.url(...)` or `FetchRequest.apply_base_path: true`.
  Managed static CSS/JS URLs follow it too. Its default is the origin root.
- `with_rsjs_assets()` installs routes for the RSJS runtimes and linked modules.
  Enable it when this app serves its own island assets, including at the origin
  root. It is opt-in so applications can supply their own asset handlers.

For an app exposed at `/apps/demo/` by a prefix-stripping proxy:

```rust filename="src/app.rs"
use proa::axum::{BasePath, FrameworkBuilder};

let app = FrameworkBuilder::new()
    .with_base_path(BasePath::new("/apps/demo")?)
    .with_rsjs_assets()
    .with_dynamic_routes_and_metadata(routes, metadata)
    .build();
```

Keep routes such as `/docs/start` and `/static/...` unprefixed. Compose
application links with `cx.url("/docs/start")` and handler redirects
with `app_url("/")`. Pass the original, unprefixed manifest to the builder.

See [linked island asset serving](/docs/framework/static-assets#serving-linked-island-assets)
for the installed routes and custom-router alternative. The
[URL prefix deployment guide](/docs/framework/deployment#serving-behind-a-url-path-prefix)
covers startup environment configuration, proxy behavior, and coverage limits.

## FrameworkBuilder Production Defaults

`build()` applies conservative defaults:

| Default | Value |
|---------|-------|
| Body limit | `1 MiB` through Axum `DefaultBodyLimit`. |
| Request timeout | `30s` for response production. Streaming bodies keep flowing after the response head is produced. |
| Cross-origin protection | On for unsafe browser requests. |
| Security headers | `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, and `Referrer-Policy: strict-origin-when-cross-origin`. |
| Concurrency limit | Off until configured. |
| Panic catching | Off until configured. |

Keep the global body limit small. For uploads, prefer a dedicated nested router with a larger Axum body limit instead of raising the whole app.

## FrameworkBuilder Security

Security defaults live on their own pages, because they are policy decisions rather than configuration plumbing:

| Builder call | Covered in |
|---|---|
| `.with_security_headers(config)`, `.with_strict_csp()`, `.with_csp_policy(policy)` | [Headers and CSP](/docs/framework/security-headers) |
| `.with_trusted_origin(origin)` | [Cross-origin and CSRF](/docs/framework/cross-origin) |

Cross-origin protection, the 1 MiB body limit, and the 30-second request timeout are applied by `build()` without configuration. See [Security](/docs/guides/security) for what Proa hardens and what stays yours.

## FrameworkBuilder Static Services

Mount generated assets under `/static`:

```rust
use tower_http::services::ServeDir;

let static_router = axum::Router::new().nest_service("/", ServeDir::new("public"));

let app = FrameworkBuilder::new()
    .with_static_service(static_router)
    .build();
```

For public directories, keep untrusted writers out of the served tree. `ServeDir` rejects `..` traversal, but deployments should still treat symlinks and generated files deliberately.

## RouteResponse Quick Reference

| Constructor | Use it for |
|-------------|------------|
| `RouteResponse::ssr(component)` | Sync `WebRenderSync` pages. |
| `RouteResponse::ssr_async(component)` | Async pages that do not use a loader. |
| `RouteResponse::ssr_async_with_loader(component, loader)` | Async pages with a typed loader. |
| `RouteResponse::ssr_stream(component)` | Streaming pages without a loader. |
| `RouteResponse::ssr_stream_with_loader(component, loader)` | Streaming pages with a typed loader. |

Response modifiers:

| Modifier | Applies to |
|----------|------------|
| `.with_head(config)` / `.without_head()` | Enable managed metadata for one response, or keep its document in manual mode. |
| `.with_loader(loader)` | Async and streaming responses with typed loaders. |
| `.with_render_mode(mode)` | Async render plans; chunked modes stream, while static and agent modes buffer. |

Use [Responses](/docs/framework/responses) for examples and [Data loading and streaming](/docs/framework/data-loading) for loader and streaming details.
