Area | Automatic behavior |
|---|---|
Server action signing | Action tokens are signed over the route, target node, typed action id, payload bytes, expiry, and nonce. Tampered tokens are rejected. |
Replay protection | verify_once stores recently used action token identities and rejects replay of the same token. |
Expiry | Server action tokens are short lived. Expired tokens are rejected before reducer dispatch. |
Body size | Server action request bodies larger than MAX_SERVER_ACTION_BODY_BYTES are rejected. |
Origin checks | ServerRenderer::with_allowed_action_origin lets the host restrict action posts to known origins. |
Private route caching | ServerPrivate routes are not inserted into the public full-page cache. |
Revalidated action safety | A revalidated page that renders signed action forms fails the render instead of caching request-specific tokens. |
Cache keys | Revalidated page cache keys include normalized query data, app identity, locale, theme fingerprint, build id, and declared vary headers. |
Asset paths | Generated asset serving rejects path traversal and serves only project/static/generated asset roots. |
Worker DOM policy | Browser workers can mutate only allowed nodes or semantic targets through the generated bridge. Unsafe URLs and inline event attributes are rejected. |
Island DOM replacement | Fission widget islands render HTML through the Fission HTML renderer; replacement operations are still checked by the bridge protocol before application. |
Session cookies | Session cookies are HttpOnly. When signing_key_env is set, cookie values are signed and tampered cookies are rejected. |
[server.sessions]
cookie_name = "shop_session"
signing_key_env = "SHOP_SESSION_SECRET"
secure = true
same_site = "lax"
let renderer = ServerRenderer::configured(app)?
.with_allowed_action_origin("https://cards.example")
.with_allowed_action_origin("https://www.cards.example");
[dependencies]
fission = { version = "0.3", features = ["server"] }
[dependencies]
fission = { version = "0.3", features = ["server", "server-axum"] }
tower = "0.5"
use axum::{routing::any, Router};
use fission::server::{axum_adapter, ServerRenderer};
use std::sync::Arc;
use std::time::Duration;
use tower::limit::RateLimitLayer;
let renderer = Arc::new(ServerRenderer::configured(pokemon_card_store_server())?);
let app = Router::new()
.fallback(any(axum_adapter::handle))
.with_state(renderer)
.layer(RateLimitLayer::new(120, Duration::from_secs(60)));
Hook point | Use it for | Where it lives |
|---|---|---|
HTTP middleware | Rate limits, auth sessions, request logging, CORS, compression, TLS/proxy policy, body limits | Axum, Actix, Hyper, or your chosen Rust HTTP stack |
Server renderer builder | Action origin allow-list, cache provider, action signer, viewport defaults, render pass limit | ServerRenderer configuration before serving |
App model | Permission checks, tenant checks, cart rules, inventory updates, cache invalidation requests | Fission reducers, jobs, and services |
[server.cache]
provider = "pipeline"
[[server.cache.layers]]
name = "hot"
provider = "moka"
policy = "hot-only"
max_capacity = 2048
[[server.cache.layers]]
name = "shared"
provider = "redis"
policy = "write-through"
url_env = "REDIS_URL"
prefix = "cards"
[server.http]
base_url = "https://cards.example"
[server.http]
trust_proxy_headers = true
Test | Expected result |
|---|---|
Tampered action token | 403 and no reducer dispatch |
Replayed action token | First request succeeds, second request is rejected |
Oversized action body | 413 |
Wrong origin | 403 when an origin allow-list is configured |
Revalidated page with action form | Render fails before cache write |
Private route | No public full-page cache entry is written |
Path traversal asset request | 400 |
Worker operation outside policy | Bridge validation rejects the operation |
Tampered signed session cookie | New session is created and the tampered value is ignored |