Exploration for Server-side Rendering

Yes, that would be the disadvantage of doing things that way.

You would have to load the data outside of the Elm app and pass it in as a flag.

That is how elm-pages is working, but it has all the initial data for a view as meta data associated with the content it wants to render - so already it is imposing restrictions on how you structure your app for SSR. I still feel this is the best way though.

Hi all, Iā€™m the author of elm-pages and since itā€™s come up on this thread (and this thread has been discussed a bit in the #elm-pages channel in the Elm slack), I thought Iā€™d chime in here.

If you want to see a live site with elm-pages (including Pre-Rendering and all), this is a good example page:

The Pre-Rendering handoff to the client-side Elm seems to be working quite smoothly.

Summary

In a nutshell, right now elm-pages uses Server-Side Rendering and gives you all the SEO and performance benefits there.

Server-Side Rendering (SSR) vs. Pre-Rendering

elm-pages doesnā€™t technically do SSR, but the effect is very similar. It would be more accurate to say that it does Pre-Rendering. It simply uses Puppeteer via webpack under the hood to go through and render all the static routes in your app elm-pages app.

So it technically doesnā€™t hydrate the pre-rendered Elm app, but rather it serves up and renders the pre-rendered HTML, and once thatā€™s done it fetches the Elm bundle and initializes a fresh Elm app which then takes over the DOM and replaces it with the same content. Thatā€™s more of an implementation detail, though. From the userā€™s perspective, Iā€™ve found that there isnā€™t an observable difference.

Blemishes with current approach

There is one issue which has come up, but I think itā€™s more of a virtual-dom bug than an inherent shortcoming with the Pre-Rendering approach.

The issue is that <img> tags reload when the Elm code takes over the DOM from the Pre-Rendered HTML. I believe this is caused by VirtualDom_virtualize causes flashes on <img> Ā· Issue #144 Ā· elm/virtual-dom Ā· GitHub, which @ktosiek mentioned in the GitHub - ktosiek/elm-ssr-demo: A toy app showing SSR for Elm readme. It seems that you can work around this, though, by using Html.Attributes.attribute "src" rather than Html.Attributes.src, as @ktosiek does here: elm-ssr-demo/src/Main.elm at c4cb2e270edf8e284da6316b7bffbe48ebb9dcae Ā· ktosiek/elm-ssr-demo Ā· GitHub. Also, it doesnā€™t even seem to be a problem with the way that modern browsers do in-memory caching (they reload the image, but it seems to be instant so thereā€™s no flash unless you have cache turned off in dev tools).

Other than that, it appears that taking over the DOM has no disadvantagesā€¦ you could have CSS animation keyframes or anything else and it seems that itā€™s all handled well and transitions over without being noticeable. At least I havenā€™t been able to find anything else that causes jankiness. If you know of anything else, Iā€™d be curious to hear about it!

Pre-fetching data on the server

I think that elm-pages solves this problem pretty nicely with the StaticHttp API. This gives you a way to fetch data when the site is built, so you can display that data in your initial pre-rendered view (rather than loading spinners).

I know this isnā€™t necessarily something that would be universally helpful, but I think itā€™s been working well for elm-pages.

SEO for user-specific data

elm-pages also allows you to use any data that you fetch from your StaticHttp requests (which could include hitting a CMS with public user data) to build up both your views and your <head> tags for SEO. I know that a JAMstack approach isnā€™t the right solution for a lot of products, so of course if thatā€™s not the right architecture for your app then you wouldnā€™t be able to leverage something like the elm-pages StaticHttp API.

Keeping Server and Client Renders Consistent

The approach that elm-pages takes is that it provides the StaticHttp API to let you feed initial data into your Pre-Rendered page. So you have StaticHttp data immediately available, without going through any update cycles at all. Then, you can get any other data from your update (like say a more real-time data feed, like a sports scoreā€¦ StaticHttp data is updated every time you build your site, which you can trigger as needed, but not every minute). But that update function doesnā€™t get called at all in the Pre-Rendering phase for elm-pages. I think this is sufficient to allow you to load in what you need so you have it on init.

There are certain types of data that you have access to in your elm-pages init function which will not be present when it is Pre-Rendered:

  • Initial URL fragment and query parameters (Pre-Rendering happens at build-time, not when a page is requested, so it doesnā€™t know which fragments or query parameters will be used in advance)
  • Flag values can be different between the server render and the client render (for example, the dimensions of the browser window)

So as long as youā€™re mindful of not depending on data which will differ between the Pre-Render and the client-side render, you can create a really seamless experience I think.

I donā€™t think that these considerations are very different from an SSR approach, except for the case of the URL fragment and query parameters. But this is just a design decision for the types of problems that elm-pages is trying to solve (sites that you can serve up ridiculously cheaply, securely, and performantly using a CDN rather than a traditional server).

If there was a different philosophy, you could imagine a framework that uses a similar approach but performs the Pre-Rendering step on-demand for each request from a server. That would allow you to pass the exact URL to the Pre-Rendering step.

Performance Tradeoffs with SSR/Pre-Rendering

In terms of the performance benefits, itā€™s worth considering the tradeoffs with Pre-Rendering and SSR. If the user is logged in, then theyā€™re probably a repeat visitor, in which case using a service worker to cache the application shell seems like the appropriate optimization approach to me.

Pre-Rendering and SSR lead to a faster initial render (First Contentful Paint), but they lead to a larger amount of data being fetched (because you have to download the HTML and the JS bundle). And because more work has to be done overall (parsing and rendering the full HTML first, and then loading up the JS bundle), this tends to slow down the Time to Interactive (TTI).

elm-pages does some optimizations with service worker caching, too, but itā€™s a really tricky area that Iā€™m still working on.

Takeaways

I hope that gives some food for thought! I think that SSR is something that has a lot of different potential design decisions that could be made, many of which are imperfect. So I think itā€™s really good to keep in mind specific use cases. I think it would be productive to hear some very specific use cases that people need SSR for. For example, if building something like Discourse and you need to fetch data from the server and then add some SEO tags to make sure the site is accessible to all web crawlers and performant on all web crawlers.

Would love to hear what types of problems people would like to solve using this type of SSR functionality, and how SSR would help them solve it, so we can drive the discussion based on real-world use cases.

13 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.