In early 2015 a customer wanted something we hadn’t done before: a website with all content residing behind a REST API. We decided we wanted to go the extra mile and started looking at possible solutions.
After seeing more frequent mentions in various online publications we were convinced to build the front-end side in React. We started out with one condition: “Everything had to be isomorphic”.
Sounds pretty amazing?
vivavlaanderen.radio2.be: Blood, sweat and refactoring
The first two days went very well. The next fifty, not so much.
There’s a tremendous amount of complexity you have to keep in mind when developing an isomorphic application. You need to ensure that all code you write has to run both on the server and in the browser (or add conditions). When navigating between pages in the app you have to make sure you can transition from every possible state. Users may have different things already loaded depending on what they visited earlier or what requests are still ongoing.
Add to this, the whole React community was very much in flux (thank you) at that point. Two big refactors were done:
- React 0.13 started supporting ES6 classes, thus Babel was added and which converted most of the code to cleaner ES6 versions.
- Fluxible got a large upgrade that resulted in needing to rewrite all actions with promises instead of callbacks. The quality of the code got tons better because of this, but it was a huge task.
When you visit vivavlaanderen.radio2.be this is what is happening:
- Initialize Fluxible stores and context.
- React Router runs.
- Fire off the action promises defined in ‘serverLoadActions’, specified on the routes triggered through a static property on the route handlers.
- The actions each fire off a set of other (smaller) action promises and await them all.
- Fluxible store and plugin state is dehydrated.
- The HTML is rendered with the selected route handler, frozen store state and dehydrated JSON.
- Extract the headers that need to be set from a Fluxible store and set them op Express.
- Send the rendered HTML (through our Varnish).
- Your browser renders the HTML and CSS.
- Rehydrate the JSON blob embedded in the HTML and build up the stores.
- React Router runs.
- Fire off action promises in ‘clientLoadActions’.
- The actions each fire off a set of other (smaller) action promises.
- React re-renders when the stores broadcast changes.
There’s one major issue though. In the browser, the same actions can be fired off multiple times before they can complete. To counter this, all actions first check if they have to run at all (loading artist 27 is only done when artist 27 isn’t already in the store), and if they do have to run, they all register that they have started. The loadArtist(27)-promise will store itself with it’s signature and parameters as key in a map so we can retrieve it later. If, while loadArtist(27) is pending it gets called again, the promise from the store is retrieved and returned instead of starting a new one.
How most actions work
- Check if the parameters are valid
- Check if the action is necessary (has this resource been loaded/errored already)
- Check if the action isn’t already running – if it is, await the existing promise instead.
- Kick off the actual work (usually a request to our API).
- Preprocess the result and kick off the appropriate event.
- Store picks up an event, processes and stores the data, emits a change (async).
- The action deregisters itself from the running-actions-store
In hindsight, choosing isomorphic SPA for Viva Vlaanderen was not the best path to take. We would have been better off to go with a classic only-server-rendered site with some interactive components. This would have saved a whole lot of hassle with hydrating and dehydrating, and action-state-watching. The end user experience in some case would have been better too. Lastly, development time would have been reduced.
Summer 2015: sharing the fun on canvas.be
New project, same client.
After drawing some conclusions we decided to go back to a more classic Laravel/Drupal hybrid for our second (and much bigger) website for this client. However, this time around, we made all interactive components on the site in React, using only Webpack for the build process (JS, CSS and static images).
At this point Redux was the new hot buzz that everyone agreed on. React Hot Loader worked well and Webpack could do SASS and CSS very well too. ESLint combined well with Babel and everyone was ready to make the effort and learn React (again…).
The biggest interactive parts for Canvas.be are the tv-programming guide (https://www.canvas.be/tv-gids) and the video faceting/filtering (desktop/tablet only, https://www.canvas.be/video). They’re both small Redux apps. There have been a lot of changes since the site launched and very nicely all developers can still find their way around the code and work.
Building these browser-only React components was much more enjoyable after the monstrosity that was created for Viva Vlaanderen, and most importantly this gave us a successful test case going forward on future projects.
Winter 2015: Server-rendered React bliss
Again, new project, same client. radio1.be & klara.be
This time around the client provided the API, we’d need to build a site on top of it. We decided to start with a clean slate and redo everything in React.
One thing we knew from the start was that we didn’t need to go full isomorphism. Instead staying with a traditional website with traditional page loads. After all it is what browsers are built for and they make development easier. Full isomorphism was a fun idea, but not advantageous for us anymore.
The end result
A beautiful, lean, server-rendered React website.
It’s not the exact purpose of React, however not having to learn a new templating engine while providing a unified development experience made it fantastic to work on.
We made heavy use of Babel features and we had to pull everything through Webpack, both client and server code. Because not everything is 100% static, some components were still isomorphic and rehydrated per component on the front-end (http://www.klara.be/programmagids is a good example).
We ended up with the following Webpack config:
After discussions within the group we agreed that this is absolutely the best way of working for us. Everything feels uniform and tightly integrated, it’s testable, there are excellent development tools, it’s fast and it’s fun to code in. We can still do interactive isomorphic components when we have to, and with the libraries and tools that we have made already, development becomes simple.
This sounds cool to you and you want to work in the best Digital Agency in Belgium? We are looking for some tech profiles: https://www.wieni.be/jobs/