When you first start trying to figure out SSR with the goal of partial hydration like I did, you find it is complex for two reasons:
- You figure out which components you need to mount.
- You need to mount them with the right props.
At first this seems relatively simple, but there are some non-obvious pitfalls.
Here are my thoughts from building Elder.js and solving these problems.
I believe in most cases, when someone is using Svelte for SSR they see Svelte as a templating language for the server that should also get the sweet sweet Svelte goodness on the client.
Given this view, partial hydration makes sense... but long term I believe things eventually evolve into a situtation where "front-end" framework becomes a misnomer.
While "partial hydration"/"islands" seems like what people want... what they really want is interactivity on the client without the crazy bundle sizes.
My goal with Elder.js was to make a repeatable solution for this situation, but my perspective is shifting.
Long term I believe the most frameworks will simplify this whole process by giving you the client mount code to include in your HTML (or including it for you) during SSR.
I think that is the only obvious way frameworks will move past whatever the stage after the "islands" stage of hydration is.
This pushes the implementation details to the framework but it also gives the framework control over future changes and optimizations.
My long term wishlist for Svelte revolves around two ideas:
- During SSR, Svelte should generate the mount code to include on your page for you. Given a
hydrate:client="true"
it will spit out the code AND props needed to mount it. - Only hydrate what is needed. This basically means Svelte would be controling separate parts of the DOM that aren't within the same tree. Short term Svelte could emit multiple root components, but long term there are probably other answers.
- During SSR users would have to specify where the location of "client" version of the Svelte component as this would need to be known during mounting. (possible file name issues due to hashing.)
- I'm not a bundler guy, but it seems that during bundling both the client component and SSR component would need to be generated at the same time.
- This dramatically simplifies the process of building an SSR app.
- By having Svelte emit the mount code it gives the Svelte core team control all of the implementation details along with full control over future optimization options and changes.
- SSR bundle sizes don't have the same limitations of client bundles. This could allow lots of smarts (as outlined below) to be included during render.
- Things like
stores
could easily be shared across components without having to do backflips for the rollup gods. (tongue in cheek, but major issue for Elder.js) - Because Svelte is generating the mount code, no more requirement to wrap things you want to hydrate in
#uniqueid
in order to mount it. I believe Svelte could generate the DOM targets pretty easily as it generates scoped CSS.
Assuming Svelte is handing you the SSR mount code. Lots of things are possible to optimize this code and the props.
- A js proxy could be used to only mount what is needed and used on the client. Rich has proven this pretty well with Object Cull
- Props could be compressed for large payloads. A good example is a JSON object with repeating keys. (I think brotli/gzip does some of this but there has to be a browser impact)
hydrate:client={preload:true}
could automatically add the required code to thehead
returned during SSR.
If I had a short term wishlist it would be something like this:
- A way to tell Svelte that a component has been SSR'd. (minimize dom thrashing that impacts LCP)
- A way to further specify (if SSR'd) that the props used during mounted match rendering. (basically skip updating the component on mount)
- In SSR mode ALWAYS emit CSS (similar to when in rollup) not just the CSS that would be generated if all values are truthy.