notes/architecture/05-FULLSTACK.md
The fullstack ecosystem enables seamless client-server communication through server functions, SSR, and hydration.
The #[server] macro generates dual code paths:
On Client:
// Creates request encoding/decoding
let client = ClientRequest::new(Method::POST, endpoint, &query);
let response = ServerFnEncoder::fetch_client(client, args, unpack).await;
let result = ServerFnDecoder::decode_client_response(response).await;
On Server:
// Creates Axum handler
fn __inner__function__(state, request) -> Response {
// Extract arguments from request
// Call actual function
// Serialize response
}
// Register globally
inventory::submit! {
ServerFunction::new(Method::POST, path, handler)
}
endpoint - Custom URL path prefix (default: /api)input - Request encoding (Json, Cbor, MessagePack)output - Response encodingmiddleware - Tower middleware layersheaders: HeaderMap, cookies: CookiesRequest:
/api/{function_name}?{query_params}Response:
enum RestEndpointPayload<T, E> {
Success(T),
Error(ErrorPayload<E>),
}
struct ErrorPayload<E> {
message: String,
code: u16,
data: Option<E>,
}
ServerFunction struct holds metadatainventory::submit! for compile-time registrationServerFunction::collect() returns all handlersDioxusRouterExt::register_server_functions() registers on Axumdioxus_ssr::Renderer instancesDisabled (default):
OutOfOrder:
StreamingRendererStreaming Chunk Structure:
<!-- Initial -->
<div>Header</div>
<div>Loading...</div>
<!-- Later -->
<script>window.dx_hydrate(mount_id, "data");</script>
<div hidden id="ds-1-r">Resolved content</div>
HydrationContext created at render startuse_server_future, use_server_cached, use_loaderwindow.__DIOXUS_HYDRATION_DATA__HydrationContext to componentsSerializeContextEntry<T> - Entry in hydration contextSerializedHydrationData - Base64-encoded CBORTakeDataError - Why data couldn't be retrievedFullstackContext and runtime handleExtension methods on axum::Router:
serve_static_assets() - Serves /publicserve_dioxus_application(cfg, component) - Full SSR + server functionsregister_server_functions() - Registers collected handlersserve_api_application(cfg, component) - API-onlyHandler wrapped in:
1. FullstackContext::scope() - provides context
2. LocalPool::spawn_pinned() - enables !Send futures
3. Middleware layers
4. Response header injection
Task-local context providing:
index - Custom IndexHtmlincremental - ISR cachecontext_providers - Injectable contextsstreaming_mode - Disabled or OutOfOrderClientRequest
├── url: String
├── method: Method
├── headers: HeaderMap
└── extensions: Extensions
EncodeRequest<In, Out, R> - Serializes and makes requestIntoRequest<R> - Converts to requestEncoding trait - Defines format (Json, Cbor)use_server_future - Waits on server, caches for clientuse_loader - Enhanced error handlinguse_server_cached - Caches computed valuesServerFnError enum standardizes representationAsStatusCode trait maps to HTTP statusanyhow::Error, StatusCode, custom typesconfig.provide_context(|| {
Box::new(Database::new()) as Box<dyn Any>
});
#[server]
async fn get_user(id: i32) -> Result<User> {
let db = consume_context::<Database>();
db.query(...).await
}
#[server(auth: AuthExtractor)]
async fn protected(data: String, auth: AuthExtractor) -> Result<String> {
// auth extracted from request
}
Or via middleware:
#[server]
#[middleware(AuthLayer::new())]
async fn protected_fn() -> Result<String> { }
pub struct CustomEncoding;
impl Encoding for CustomEncoding {
fn content_type() -> &'static str { "application/custom" }
fn encode(data: impl Serialize, buf: &mut Vec<u8>) -> Option<usize> { }
fn decode<O: DeserializeOwned>(bytes: Bytes) -> Option<O> { }
}
#[server(input = CustomEncoding, output = CustomEncoding)]
async fn my_fn(arg: String) -> Result<String> { }
#[server]
async fn custom_response() -> Result<String> {
let ctx = FullstackContext::current().unwrap();
ctx.set_response_headers(headers);
Ok("data".to_string())
}
Client Component
→ Call #[server] fn
→ Create ClientRequest, serialize args
→ HTTP POST /api/function_name
→ Server Router
→ FullstackContext::scope()
→ Extract args, call function
→ Serialize Result
→ HTTP Response
→ Client deserialize
→ Component receives value
Server:
Page Request → VirtualDom::new()
→ HydrationContext created
→ Render components
→ use_server_future serializes results
→ Inject __DIOXUS_HYDRATION__ script
→ HTTP 200 + HTML
Client:
Receive HTML
→ Parse hydration data
→ VirtualDom::new()
→ HydrationContext populated
→ Components render, hooks get cached data
→ Event listeners attached
→ Hydration complete
Global registration via inventory::submit! at compile time. Enables dynamic discovery without explicit registration.
Multiple & deref layers select correct trait impl:
FromRequest over DeserializeOwnedFromResponse over serializationRoutes hashed using xxhash64 of module path, preventing collisions with same-name functions in different modules.
Auto-deref tiers implementations: