Technical support for the rendering of modern front-end technologies

In this post, I’d like to discuss the option we have to accommodate using client-side rendering frameworks (e.g., Vue, React, Web Components…) while preserving essential properties such as fast page load, accessibility, support for older browsers or good indexation by search engines.
This is a follow-up to my post on the impact of modern front-end technologies on non-functional properties, addressing the most technical aspect of the discussion.

Glossary

First, the definition of some acronyms that we’ll use frequently in the rest of the discussion.

  • SPA: An SPA (Single-page application) is a web app implementation that loads only a single web document, and then updates the body content of that single document via JavaScript APIs such as Fetch when different content is to be shown.
  • SSR: Server-side rendering (SSR) refers to the practice of generating HTML content on the server and sending it to the client. SSR is opposed to client-side rendering, where the client generates the HTML content using JavaScript. Both techniques are not mutually exclusive and can be used together in the same application.
  • SE: A search engine is a software system that collects information from the World Wide Web and presents it to users who are looking for specific information.

Options

See below the list of options I considered so far. Please tell me if you think some other approach is worth considering.
Note that this discussion must be though from XWiki and Cristal perspective.

1.1/ Server-Side Rendering — Embedded

With Server-Side Rendering (SSR)[1], the server can evaluate the same template language as the client.
This allows to precompute the HTML before returning a page.
Therefore, even without JavaScript, a complete HTML page is returned to the client.
A client with JavaScript can then “hydrate” the page with additional content and interactivity, but the initial content is already enough to have a usable web page.
In the case of an SPA, further navigation is done purely client-side with asynchronous Ajax requests.

Pros:

  • fast initial page load speed and SE

Cons:

  • The template engine must be interpretable server-side. Which usually means being able to run a JavaScript interpreter, which does not pair well with our Java backend.[2]
  • This is also tightly binding the front-end app to the backend, which is not always desirable, in particular in the context of Cristal where we don’t always control the backend capabilities.

1.2/ Server-Side Rendering — Third Party

This case is close to 1.1, but the rendering is done by an independent Node.js server, responsible for rendering the SSR pages.

Pros:

  • easier to implement as this is natively supporting JavaScript for template rendering
  • no tight binding to a specific backend, making it a good fit for Cristal

Cons:

  • one more server to deploy and maintain, plus the need to share credentials etc
  • risk of latency when fetching data from the “backend” server (e.g., XWiki) to the “SSR” server over http

2/ Progressive Enhancement — Throwaway HTML

In this case, a version of the page with essential content (e.g., body, navigation tree, comments, etc.) is rendered so that users without JavaScript, or search engine indexers, get an initial content.
But, this content is discarded as soon as the JavaScript is loaded and running.
The main difference with the SSR option is that here the logic is duplicated, with dedicated templates written using a different templating language interpretable using Java (e.g., Velocity, Thymeleaf) being used.

Pros:

  • Good for performance and SE

Cons:

  • manual work and maintenance
  • larger resource consumption and network use, as the computed HTML content is not used afterward. The impact can be limited in the case of SPA, as this additional computation will only happen once for every full-page load, the rest of page rendering being done client side.

3/ Precomputing of data

In this option, no rendering is done server-side.
The main point is to precompute the data required for the first rendering directly during the initial response.
While this does not avoid the client having JavaScript to be able to get the UI, all the asynchronous requests required to render the full page as not needed anymore are they have been precomputed.
Once again, in the case of an SPA, the navigations after the first page load are done client side using Ajax queries.

Pros:

  • some code duplication is required, but probably less than in option 2, as we only need to aggregate the data without rendering them

Cons:

  • bound to the backend capabilities, so not always a good fit for Cristal
  • still a risk for SE, as JavaScript is required to actually render the page (the rendering is just faster since no asynchronous requests are required anymore on the first page load).

Conclusion

What do you think of the options proposed above?
Do you think some other options should be added to this list?
Thanks


  1. Vue’s documentation: Server-Side Rendering (SSR) | Vue.js ↩︎

  2. At the exception to experimental projects such as GraalVM/Polyglot or GitHub - caoccao/Javet: Javet is Java + V8 (JAVa + V + EighT). It is an awesome way of embedding Node.js and V8 in Java. ↩︎

Hi!
I don’t have much to add for option 1. and 3…

Option 2:

  • So far it was decided (not sure it was a vote or just a proposal) that XWiki wouldn’t care about progressive enhancement. Right now if a user does not have javascript, the UI is already very bugged.
  • Progressive enhancement is good for accessibility (users with limited band-width benefit a lot from it) but last we discussed it, accessibility alone was not enough of a reason to warrant such a large change in our coding practices.

Thanks for starting this discussion!
Lucas C.

Hi,

It might help if we start by listing some requirements that have to be checked when integrating modern front-end technologies in both XWiki and Cristal.

For XWiki I think we should continue using server-side rendering and try to decouple the UI from the back-end (storage and services). I would also like to use more progressive enhancement (without throwing away much).

The option 3 from your list is not clear to me. You say:

but also:

So is option 3 another form of SSR?

Otherwise, from the options you listed, I’m tempted by 1.2 (because I’d like to see the UI decoupled from the back-end) with some progressive enhancement (because I’m old-school :slight_smile: ).

Thanks for starting this discussion,
Marius

Good points, this is the set of requirements I have in mind:

  • support for Search Enging Crawlers
  • (optional) support for Javascript-less usage
  • rich(er) UI experience for users with javascript enabled and modern browsers
  • support for older browers
  • Single Page Application (SPA) - minimize the need to fully reload the page after a first page load

Sorry, the first sentence is a typo (going to fix it). To rephrase, in option 3 only a serializable (in json) data-structure is computed server-side. Then the client takes over and use client-side rendering to generate the UI. But, with the data-structure being available directly in the initial, the rendering is expected to be faster since we save a few server calls.

Note that I’m currently reading Bing Webmaster Tools
In the guidelines, they mention the need to minimize the numbers of http resources required to render the page. This includes 1) the REST requests to get the data 2) the http requests to retrieve the static assets (JavaScript files, images, etc.).

In the light of this, I think option 3 is likely not the best approach.
The more I read about how search engine are working (which is a new topic to me, feel free to suggest good reading resources), the more I’m convinced that a good and semantically rich HTML is mandatory on the initial response.

Note that in the bing blog post linked above, they also propose Dynamic Rendering where the website adapts its response to the user agent (i.e., having a specific response of SE bots).

This could be considered a variant of Option 2, but only for Search Engine.
But since it would still require the effort to maintain server-side templates, I’d favor just opting for Option 2.

PS: In the light of what I understand so far, I think the best option is option 2, but I feel like my conviction is not yet backed by enough data.

My worry with option 2 is that the discarded HTML can easily become outdated / broken without us noticing it, because most of the users will use a browser that has JavaScript. We’d have to write and maintain 2 sets of automated tests, for JavaScript and no-JavaScript, which is costly.

If we go with progressive enhancement, we should try to re-use as much as possible from the initial HTML.

That was my worry as well. We should do a more details analysis if we agree this seems the way to go. But my thinking is:

  • critical elements are the ones that are essential for user navigation and SE bots. I have in mind (though we should make it configurable):
    • the page content
    • the navigation tree
    • some extra tabs: comments, information probably, history and attachments less so
  • for all of those, it seems to me that a semantically sound HTML should be programmatically usable by JavaScript for the initial load.
  • if the initial HTML is used for JavaScript loading, breaking the HTML should be caugh by integration tests

I made a quick prototype for Option 2 using HTML + Vue. But it could easily be ported to use web components instead.

You can get the sources on manuelleduc/progressivexp.
The README details of to get the experiment running.

I think it shows that generating a minimal HTML and transforming it to richer UI elements is possible.
Though I think we should limit this approach to elements that need to be available very early:

  • page content
  • document tree
  • top menu
  • maybe some bottom tabs such as information or comments

Then, the rest of the elements can be loaded and used in a more standard way.

WDYT?

To me, what’s important is that we don’t expose or require any specific third-party front-end framework like Vue.js 3 as an API, as otherwise we’ll have huge problems should we need or want to replace it - and given the history of front-end frameworks, it is almost certain that we’ll have to replace it in a few years.

Further, it is important to me that we choose a technology that doesn’t impact our ability to make XWiki fast without huge engineering efforts. As elegant as solution 1.2 sounds, I already see the performance problems. If a simple rights or group membership check in the UI code is suddenly a REST call instead of a simple native API call, performance will suffer. And adding caching for all that feels as if we’ll have to rewrite XWiki in TypeScript. I’m also quite negative towards requiring the deployment of a second server. I hear that Solr was explicitly chosen because it can be embedded, I don’t see why we should choose otherwise now.

For these reasons, I think I would lean towards option 1.1, though as mentioned in the other thread, I don’t agree on making XWiki an SPA, so navigation would still be server-side. I would also prefer if we could use something more lightweight than Vue.js at least for simple components such that we don’t spread Vue.js across the whole UI. I’m thinking here about either evolving our rendering/wiki macro/UIX framework into supporting generating web components with both server- and client-side rendering and scripting, or maybe also really just writing native web components without any framework (but of course with a library of web components that we provide to make it easy to, e.g., add icons).