-
-
Notifications
You must be signed in to change notification settings - Fork 129
Description
Hello! As previously discussed, it would be nice to be able to specify a skeleton for server components. I use the word skeleton and not "loading" because it is not really primarily for loading. Though it can also be used as a loading state for server components, it is also very useful in places where JS is not available (like when prefetching HTML, web scrapers (and SEO stuff), page fragment links, for users with JS disabled, etc.).
I know this may not directly correlate to v5 code, but in v4 adding support for this required just one line of code:
var LustreServerComponent = class extends HTMLElement {
static get observedAttributes() {
return ["route"];
}
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = '<slot name="skeleton"></slot>'; // <- this is the one new line!
this.#observer = new MutationObserver((mutations) => {
// ...
});
}
// ...
}Then, it can be used like:
pub fn render_with_skeleton(name: String, skeleton: element.Element(msg)) {
element.element(
"lustre-server-component",
[server_component.route("/" <> name)],
[html.div([attribute.attribute("slot", "skeleton")], [skeleton])],
)
}For user-discoverability and ease, I might suggest adding a skeleton function to the server_component module, which could be like so:
pub fn skeleton(element: element.Element(msg)) {
html.div([attribute.attribute("slot", "skeleton")], [skeleton])
}which could then easily be used like:
element.element(
"lustre-server-component",
[server_component.route("/" <> name)],
[server_component.skeleton(html.p([], [html.text("Spooky!")]))],
)If you think this is a good practice, the server_component.component function could also be changed to take a skeleton element list (could be passed as [] if not wanted) to encourage users to provide one.
Edit: after working on this further, I have two more things to note:
- A skeleton-like stand-in for a server component is already possible with Lustre as-is by rendering something where the server component will eventually go. However, when the server component loads, the entire component will flash momentarily. Adding the skeleton slot allows for a seamless replacement by the server component.
- The user needs to provide a place for the skeleton slot to go after the server component is rendered, so they have to include something like the following in the server component. It is not bad, but it is not as clean as I'd like it to be.
pub fn hide_skeleton() {
html.slot([
attribute.name("skeleton"),
attribute.style([#("display", "none")]),
])
}Edit again: if there was a way to have the server statically generate the first state of a server component and then hydrate it later with a real connection (like we can do with client components), that would encompass this plus more. I don't know all the implications of how that would be done, though, and if it is even reasonable because of the quick-changing nature of the data the server component is probably serving.