DioxusLabs / dioxus

Fullstack app framework for web, desktop, mobile, and more.
https://dioxuslabs.com
Apache License 2.0
20.25k stars 775 forks source link

Allow SSG to generate content with resources pending #2919

Open samtay opened 1 week ago

samtay commented 1 week ago

Feature Request

Currently static site generation is only useful for truly static sites. Like a blog without comments.

This is because the static content that gets rendered will run use_resources to completion. So if you wanted to use SSG for a blog with comments, the initial page load would just contain whatever comments were present at the time the site was generated. Similarly if you had a feed or any dynamic content that might change from time to time, that content would just be stale from the time you generated the site.

Implement Suggestion

I think that there should be an option (perhaps in ssg::Config?) to render static routes with all resources in the state of UseResourceState::Pending, and then have the client side run the resources to completion on page load. This way, your blog would render immediately with potentially nice skeleton placeholders in the comment section, and then they'd hydrate with the actual resources client side.

This functionality would basically just let ssg be a better version of web -- all of its existing functionality, plus the fast render and SEO benefits from ssg / fullstack.

I'm interested in working on this if it is feasible as a first Dioxus issue.

ealmloff commented 1 week ago

There are a few different ways to use use_resource: 1) The default use_resource without suspense will only run on the client in fullstack/ssg. This version will act like what you described 2) use_resource with suspense will run on the server and client in ssg, but fail to hydrate because the data isn't serialized between the server and client 3) use_server_future is a version of use_resource that serializes the result from the server to the client to fix hydration issues

There are situations where you want a future to only suspend on the client during SSG which currently requires you to config out the suspense on the server. We should probably add a method/hook that supports that usecase.

I could see situations where it is useful to do async work during suspense even during the SSG render. For example, my personal site fetches data for each card asynchronously from Github. You may want to combine client side data that is fresh with server side data that is baked at build time in the same application, so I don't think this should be a setting in the ssg config

samtay commented 1 week ago

You may want to combine client side data that is fresh with server side data that is baked at build time in the same application, so I don't think this should be a setting in the ssg config

Yeah, totally get that some resources make sense to fetch at build time. Global config probably doesn't make sense. I suppose the good news for me is that it is indeed possible to do what I want, I just checked and after removing the suspense (replacing with a simple match resource.value() { None => rsx! { PlaceholderComponent {} }, ... }), SSG does work for me! Yay!

However, it seems like leaky implementation details that options (1), (2), (3) have such varying behaviors. Perhaps they should all be made to work consistently, and then as you mentioned, add a new method that can configure whether or not to suspend based on the dx platform? Something like

// perhaps keep the default behavior to render all resources
let static_but_remote_resource = use_resource(fetch_content_that_is_largely_static);
// and mark ones that should only be run dynamically
let some_dynamic_feed_resource = use_resource(fetch_dynamic_content).hydrate_only();

but with better names?

ealmloff commented 1 week ago

SolidJS does something interesting here. They now expose a ssrLoadFrom option that sets the initial value for ssr and hydration and then reruns the future on the client.

I agree, the options make more sense as methods because you can stack them. You can choose to .start_on_client() and .suspend() with the same future. Ideally, the easiest option should be the option we recommend by default. Currently, most of the docs point towards use_resource to run futures, but that causes poor performance on the server. use_server_future adds an additional serialization requirement, so it isn't enabled by default.

Instead it might be better to require serialization by default and add a method to skip serialization:

// Acts like `use_server_future` does today with serialization requirement
use_resource().suspend()?;
// Acts like `use_resource` does today without serialization requirement
use_resource().run_on_client().suspend()?;