You are currently viewing the docs for Dioxus 0.7.0 which is under construction.

Streaming

This guide will show you how to use out-of-order streaming in Dioxus Fullstack to make your apps interactive sooner while waiting for async data.

What is Streaming?

The default rendering mode in dioxus fullstack waits for all suspense boundaries to resolve before sending the entire page as HTML to the client. If you have a page with multiple chunks of async data, the server will wait for all of them to complete before rendering the page.

When streaming is enabled, the server can send chunks of HTML to the client as soon as each suspense boundary resolves. You can start interacting with a page as soon as the first part of the HTML is sent, instead of tha waiting for the entire page to be ready. This can lead to a much faster initial load time.

Bellow is the same hackernews example rendered with and without streaming enabled. While both pages take the same amount of time to load all the data, the page with streaming enabled on the left shows you the data streaming in as it becomes available.

SEO and No JS

When streaming is enabled, all of the contents of the page are still rendered into the html document, so search engines can still crawl and index the full content of the page. However, the content will not be visible to users unless they have JavaScript enabled. If you want to support users without JavaScript, you will need to disable streaming and use the default rendering mode.

Enabling Streaming

You can enable streaming in the ServeConfig builder with the enable_out_of_order_streaming method. If you are launching your application through the dioxus::LaunchBuilder, you can use the with_cfg method to pass in a configuration that enables streaming:

src/streaming.rs
pub fn main() {
    dioxus::LaunchBuilder::new()
        .with_cfg(server_only! {
            dioxus::fullstack::ServeConfig::builder().enable_out_of_order_streaming()
        })
        .launch(app);
}

or if you are using a custom axum server, you can pass the config into serve_dioxus_application directly:

src/streaming.rs
#[cfg(feature = "server")]
#[tokio::main]
async fn main() {
    let addr = dioxus::cli_config::fullstack_address_or_localhost();
    let router = axum::Router::new()
        // Server side render the application, serve static assets, and register server functions
        .serve_dioxus_application(
            dioxus::fullstack::ServeConfig::builder()
                .enable_out_of_order_streaming()
                .build()
                .unwrap(),
            app,
        )
        .into_make_service();
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, router).await.unwrap();
}

Head elements with streaming

Head elements can only be rendered in the initial HTML chunk that contains the <head> tag. You should include all of your document::Link, document::Meta, and document::Title elements in the first part of your page if possible. If you have any head elements that are not included in the first chunk, they will be rendered by the client after hydration instead, which will not be visible to any search engines or users without JavaScript enabled.

The initial chunk of HTML is send after commit_initial_chunk is called for the first time. If you are using the router, this will happen automatically when all suspense boundaries above the router are resolved. If you are not using the router, you can call commit_initial_chunk manually after all of your blocking head elements have been rendered.

src/streaming.rs
/// An enum of all of the possible routes in the app.
#[derive(Routable, Clone)]
enum Route {
    // The home page is at the / route
    #[route("/")]
    Home,
}

fn Home() -> Element {
    let title = use_server_future(get_title)?;
    let description = use_server_future(get_description)?;

    rsx! {
        // This will be rendered on the server because it is inside the same (root)
        // suspense boundary as the `Router` component.
        document::Title { {title} }
        SuspenseBoundary {
            fallback: |_| {
                rsx! { "Loading..." }
            },
            AsyncHead {}
        }
    }
}

fn AsyncHead() -> Element {
    let description = use_server_future(get_description)?;
    // The resource should always be resolved at this point because the `?` above bubbles
    // up the async case if it is pending
    let current_description = description.read_unchecked();
    let current_description = current_description.as_ref().unwrap();

    rsx! {
        // This will be rendered on the client because it is in a
        // suspense boundary below the `Router` component.
        document::Meta { name: "description", content: "{current_description}" }
    }
}