Fix ssg race condition (#3521)

* Fix server crash logging and opening fullstack apps

* Fix race condition during SSG

* move route prerendering into a separate stage

* Fix static site generation with dx serve

* only proxy the fullstack server if ssg is disabled

* test nested-suspense with ssg in playwright
This commit is contained in:
Evan Almloff authored and GitHub committed 2025-01-08 19:52:10 -08:00
1 parent 343c48f5ad
commit ecc419df0a
18 files changed
+370 -214

No files matched your search

@@ -0,0 +1,114 @@
// This test is used by playwright configured in the root of the repo
// Tests:
// - SEO without JS
// - Streaming hydration
// - Suspense
// - Server functions
//
// Without Javascript, content may not load into the right location, but it should still be somewhere in the html even if it is invisible
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
pub fn app() -> Element {
rsx! {
SuspenseBoundary {
fallback: move |_| rsx! {},
document::Style {
href: asset!("/assets/style.css")
}
LoadTitle {}
}
MessageWithLoader { id: 0 }
}
}
#[component]
fn MessageWithLoader(id: usize) -> Element {
rsx! {
SuspenseBoundary {
fallback: move |_| rsx! {
"Loading {id}..."
},
Message { id }
}
}
}
#[component]
fn LoadTitle() -> Element {
let title = use_server_future(move || server_content(0))?()
.unwrap()
.unwrap();
rsx! {
"title loaded"
document::Title { "{title.title}" }
}
}
#[component]
fn Message(id: usize) -> Element {
let message = use_server_future(move || server_content(id))?()
.unwrap()
.unwrap();
rsx! {
h2 {
id: "title-{id}",
"{message.title}"
}
p {
id: "body-{id}",
"{message.body}"
}
div {
id: "children-{id}",
padding: "10px",
for child in message.children {
MessageWithLoader { id: child }
}
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Content {
title: String,
body: String,
children: Vec<usize>,
}
#[server]
async fn server_content(id: usize) -> Result<Content, ServerFnError> {
let content_tree = [
Content {
title: "The robot says hello world".to_string(),
body: "The robot becomes sentient and says hello world".to_string(),
children: vec![1, 2, 3],
},
Content {
title: "The world says hello back".to_string(),
body: "In a stunning turn of events, the world collectively unites and says hello back"
.to_string(),
children: vec![4],
},
Content {
title: "Goodbye Robot".to_string(),
body: "The robot says goodbye".to_string(),
children: vec![],
},
Content {
title: "Goodbye World".to_string(),
body: "The world says goodbye".to_string(),
children: vec![],
},
Content {
title: "Hello World".to_string(),
body: "The world says hello again".to_string(),
children: vec![],
},
];
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
Ok(content_tree[id].clone())
}
@@ -1,123 +1,12 @@
// This test is used by playwright configured in the root of the repo
// Tests:
// - SEO without JS
// - Streaming hydration
// - Suspense
// - Server functions
//
// Without Javascript, content may not load into the right location, but it should still be somewhere in the html even if it is invisible
#![allow(non_snake_case)]
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
use nested_suspense::app;
fn main() {
LaunchBuilder::new()
.with_cfg(server_only! {
dioxus::fullstack::ServeConfig::builder().enable_out_of_order_streaming()
ServeConfig::builder()
.enable_out_of_order_streaming()
})
.launch(app);
}
fn app() -> Element {
rsx! {
SuspenseBoundary {
fallback: move |_| rsx! {},
document::Style {
href: asset!("/assets/style.css")
}
LoadTitle {}
}
MessageWithLoader { id: 0 }
}
}
#[component]
fn MessageWithLoader(id: usize) -> Element {
rsx! {
SuspenseBoundary {
fallback: move |_| rsx! {
"Loading {id}..."
},
Message { id }
}
}
}
#[component]
fn LoadTitle() -> Element {
let title = use_server_future(move || server_content(0))?()
.unwrap()
.unwrap();
rsx! {
"title loaded"
document::Title { "{title.title}" }
}
}
#[component]
fn Message(id: usize) -> Element {
let message = use_server_future(move || server_content(id))?()
.unwrap()
.unwrap();
rsx! {
h2 {
id: "title-{id}",
"{message.title}"
}
p {
id: "body-{id}",
"{message.body}"
}
div {
id: "children-{id}",
padding: "10px",
for child in message.children {
MessageWithLoader { id: child }
}
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Content {
title: String,
body: String,
children: Vec<usize>,
}
#[server]
async fn server_content(id: usize) -> Result<Content, ServerFnError> {
let content_tree = [
Content {
title: "The robot says hello world".to_string(),
body: "The robot becomes sentient and says hello world".to_string(),
children: vec![1, 2, 3],
},
Content {
title: "The world says hello back".to_string(),
body: "In a stunning turn of events, the world collectively unites and says hello back"
.to_string(),
children: vec![4],
},
Content {
title: "Goodbye Robot".to_string(),
body: "The robot says goodbye".to_string(),
children: vec![],
},
Content {
title: "Goodbye World".to_string(),
body: "The world says goodbye".to_string(),
children: vec![],
},
Content {
title: "Hello World".to_string(),
body: "The world says hello again".to_string(),
children: vec![],
},
];
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
Ok(content_tree[id].clone())
}
@@ -0,0 +1,27 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use nested_suspense::app;
fn main() {
dioxus::LaunchBuilder::new()
.with_cfg(server_only! {
ServeConfig::builder()
.incremental(
IncrementalRendererConfig::new()
.static_dir(
std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.join("public")
)
)
.enable_out_of_order_streaming()
})
.launch(app);
}
#[server(endpoint = "static_routes")]
async fn static_routes() -> Result<Vec<String>, ServerFnError> {
Ok(vec!["/".to_string()])
}