You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem
When building a small SSR + hydration web app and opening in the browser
we get a part of the html but we have to wait the end of the server-side request to have the rest of the html.
The request is stalled, there is no hydration nor fallback UI (they are configured, though)
Steps To Reproduce
Starting from the example ssr_router, filling src/lib.rs and using App and ServerApp in the other files:
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use yew::html::RenderError;
use yew::prelude::*;
use yew_router::history::{AnyHistory, History, MemoryHistory};
use yew_router::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Routable)]
enum Route {
#[at("/")]
Home,
#[at("/hi")]
Hi,
}
#[cfg(target_arch = "wasm32")]
async fn async_delay<F>(f: F, duration: u64)
where
F: FnOnce() + 'static,
{
use gloo_timers::future::TimeoutFuture;
TimeoutFuture::new(duration as u32).await;
f()
}
#[cfg(not(target_arch = "wasm32"))]
async fn async_delay<F>(f: F, duration: u64)
where
F: FnOnce() + 'static,
{
use tokio::time::{sleep, Duration};
sleep(Duration::from_millis(duration)).await;
f()
}
#[derive(Serialize, Deserialize)]
struct UuidResponse {
uuid: Uuid,
}
#[cfg(feature = "ssr")]
async fn fetch_uuid() -> Uuid {
// reqwest works for both non-wasm and wasm targets.
let resp = reqwest::get("https://httpbin.org/uuid").await.unwrap();
async_delay(|| (), 30000).await;
let uuid_resp = resp.json::<UuidResponse>().await.unwrap();
uuid_resp.uuid
}
#[function_component]
fn Nav() -> Html {
html! {
<ul>
<li><Link<Route> to={Route::Home}>{"Home"}</Link<Route>></li>
<li><Link<Route> to={Route::Hi}>{"Hi"}</Link<Route>></li>
</ul>
}
}
#[function_component]
fn Content() -> HtmlResult {
let uuid =
use_prepared_state!((), async move |_| -> Uuid { fetch_uuid().await }).map_err(|e| {
println!("error: {:#?}", e);
e
});
let uuid2 = (match uuid {
Ok(Some(x)) => Ok(html! {
<div>{"Random UUID: "}{x}</div>
}),
Ok(None) => Ok(html! {
<div>{"Loading"}</div>
}),
Err(e) => Err(RenderError::Suspended(e)),
});
uuid2
}
#[function_component]
fn Hi() -> HtmlResult {
Ok(html! {
<div>{"Hi"}</div>
})
}
#[derive(Properties, PartialEq, Eq, Debug)]
pub struct ServerAppProps {
pub url: AttrValue,
pub queries: HashMap<String, String>,
}
#[function_component]
pub fn ServerApp(props: &ServerAppProps) -> Html {
let history = AnyHistory::from(MemoryHistory::new());
history
.push_with_query(&*props.url, &props.queries)
.unwrap();
html! {
<Router history={history}>
<Nav />
<Switch<Route> render={switch} />
</Router>
}
}
#[function_component]
pub fn App() -> Html {
html! {
<BrowserRouter>
<Nav />
<Switch<Route> render={switch} />
</BrowserRouter>
}
}
fn switch(routes: Route) -> Html {
match routes {
Route::Hi => html! {<Hi />},
Route::Home => {
let fallback = html! {<div>{"Loading..."}</div>};
html! {
<Suspense {fallback}><Content /></Suspense>
}
}
}
}
and src/bin/ssr_router_server
use std::collections::HashMap;
use std::convert::Infallible;
use std::path::PathBuf;
use axum::body::{Body, StreamBody};
use axum::error_handling::HandleError;
use axum::extract::Query;
use axum::handler::Handler;
use axum::http::{Request, StatusCode};
use axum::response::IntoResponse;
use axum::routing::get;
use axum::{Extension, Router};
use clap::Parser;
use futures::stream::{self, StreamExt};
use hyper::server::Server;
use tower::ServiceExt;
use tower_http::services::ServeDir;
use yew::ServerRenderer;
use simple_ssr::{ServerApp, ServerAppProps};
/// A basic example
#[derive(Parser, Debug)]
struct Opt {
/// the "dist" created by trunk directory to be served for hydration.
#[structopt(short, long, parse(from_os_str))]
dir: PathBuf,
}
async fn render(
Extension((index_html_before, index_html_after)): Extension<(String, String)>,
url: Request<Body>,
Query(queries): Query<HashMap<String, String>>,
) -> impl IntoResponse {
let url = url.uri().to_string();
let renderer = ServerRenderer::<ServerApp>::with_props(move || ServerAppProps {
url: url.into(),
queries,
});
StreamBody::new(
stream::once(async move { index_html_before })
.chain(renderer.render_stream())
.chain(stream::once(async move { index_html_after }))
.map(Result::<_, Infallible>::Ok),
)
}
#[tokio::main]
async fn main() {
let opts = Opt::parse();
let index_html_s = tokio::fs::read_to_string(opts.dir.join("index.html"))
.await
.expect("failed to read index.html");
let (index_html_before, index_html_after) = index_html_s.split_once("<body>").unwrap();
let mut index_html_before = index_html_before.to_owned();
index_html_before.push_str("<body>");
let index_html_after = index_html_after.to_owned();
let handle_error = |e| async move {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("error occurred: {}", e),
)
};
let app = Router::new()
.route("/api/test", get(|| async move { "Hello World" }))
.fallback(HandleError::new(
ServeDir::new(opts.dir)
.append_index_html_on_directories(false)
.fallback(
render
.layer(Extension((
index_html_before.clone(),
index_html_after.clone(),
)))
.into_service()
.map_err(|err| -> std::io::Error { match err {} }),
),
handle_error,
));
println!("You can view the website at: http://localhost:8081/");
Server::bind(&"127.0.0.1:8081".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
Launch the server: trunk build --release index.html && cargo run --release --features=ssr --bin ssr_router_server -- --dir=dist
Go to the url http://localhost:8081 and wait 30 seconds for the request to https://httpbin.org/uuid to complete:
You will see part of the UI, but no fallback UI. If you curl "http://localhost:8081", you will have to wait 30 seconds to get an answer
Expected behavior
Server side rendering should display <div>Loading...</div>
and hydration should hydrate with the request result: <div>Random UUID: 1be9a2ec-c62e-40af-9b38-9fccae823f84</div>
Environment:
Yew version: [v0.21]
Questionnaire
I'm interested in fixing this myself but don't know where to start
I would like to fix and I have a solution
I don't have time to fix this right now, but maybe later
The text was updated successfully, but these errors were encountered:
Problem
When building a small SSR + hydration web app and opening in the browser
we get a part of the html but we have to wait the end of the server-side request to have the rest of the html.
The request is stalled, there is no hydration nor fallback UI (they are configured, though)
Steps To Reproduce
Starting from the example
ssr_router
, fillingsrc/lib.rs
and usingApp
andServerApp
in the other files:and
src/bin/ssr_router_server
Launch the server:
trunk build --release index.html && cargo run --release --features=ssr --bin ssr_router_server -- --dir=dist
Go to the url
http://localhost:8081
and wait 30 seconds for the request tohttps://httpbin.org/uuid
to complete:You will see part of the UI, but no fallback UI. If you
curl "http://localhost:8081"
, you will have to wait 30 seconds to get an answerExpected behavior
Server side rendering should display
<div>Loading...</div>
and hydration should hydrate with the request result:
<div>Random UUID: 1be9a2ec-c62e-40af-9b38-9fccae823f84</div>
Environment:
Questionnaire
The text was updated successfully, but these errors were encountered: