🛶 Migrating from client side rendering to incremental static regeneration with NextJS

CSRSSRStatic generationISR

Last updated

9 min read

The original versions of this portfolio were purely client-side rendered, you would hit this page, and your browser would need to do all the work to make the page appear, which would be calling my backend API a couple of times for data, then requesting all the various assets and bit of media to show. and then render all of the React code into HTML. This is slow, and what's more, SEO and social media previews did not work, so I couldn't even really show off my work effectively. On top of this, every page view hit my dinky backend service with requests that returned the same data every time for every request. a lot of wasted work to show a glorified static webpage.

🩹 Putting a plaster on the problem

I was quite hesitant to bite the bullet and venture into learning about Server side rendering and how to set that up with vanilla React, my first few forays into this territory left me quite demoralised. It's great to see that `create-react-app` is now dead, as it entices you in with a quick and easy way to build a React app, but then hobbles you when you try to do anything more than `npm run dev`.
So I tried investigating if there was some way to at least get SEO and social media previews working with a client-side rendered site easily, and it turns out there was. Enter Prerender.io a tool that caches pages. I was able to prerender and cache this portfolio's pages and redirect web crawlers, social media bots and other entities to the Prerender.io cache while regular human users would hit the page directly.
It all worked out quite well, I had contentful webhooks notifying my backend service that it was time to regenerate its cache, once the cache was synchronised, the backend would then call the Prerender.io API to trigger the portfolio pages to recached. Voila, social media previews were working, my SEO scores started to increase, and the website was starting to appear in Google! Hurray! 🎉
Looking at this Architecture diagram, you can see the flow of data is becoming quite complex for a simple portfolio page and blog, the backend service now calls all sorts of third parties and is mainly in control of ensuring the website is cached, which if Prerender.io is down, changes in the CMS (Contentful) would be missed and SEO and social media would be shown out of date information
So at least my goal was achieved in so far as that SEO and social media previews were working. It was time to start paying back some technical debt

🌞 Server Side Rendering but wrong

After trying to pluck teeth from hens and attempting to force a `create-react-app` to play nice with server-side rendering and failing, I wasn't too much up to the task. but I had just started a new job at loveholidays.com, and their flagship site is a server-side rendered react app that users `Razzle` to compile and bundle the site.
It apparently offered the simplicity of `create-react-app` but with SSR out of the box, So I decided to migrate my React components and P5js sketches to a new Razzle-based project, but it soon became apparent Razzle wants you to configure it yourself, needing the copious amount of webpack configuration even to get bundle compression and source maps in place. The migration to the new project structure was quite simple, but it required a lot of trial and error to get the page to work properly, from memory, this migration did not make it into the prod and was quickly killed in favour of another React framework

💫 Now we know what we don't want

We can now see what we want, and that is a framework, that can support SSR without needing to become a 10x webpack engineer and perform arcane JSON rituals just to get some rinky-dink portfolio page off the ground.
I had heard rumbling that NextJS was the king of React frameworks, but I didn't pay it much attention, as I think I was lumping it next to `NestJS` which is server-side Angular 🤮 and `NxJS` which is a build system for mono repo, both are technologies that I used in previous roles and just made things more complex than they needed to be really. But I think that I was so frustrated with Razzle that I wanted to find something with more batteries included instead of having to assemble the system myself.
NextJS offered a super simple way to build single and multi-page apps, with server-side rendering, static generation, and even something fancy called incrementation static regeneration
I was able to get a proof of concept, with a base portfolio site in place with server-side rendering in place very quickly, I didn't need to fight with JSON and configuration files, and I didn't need to look up how to do bundle compression, or touch webpack for that matter. I was able to create the project using `npx create-next-app@latest`, moving my files into place, and then setting up the server-side rendering using their simple-to-use API.
I did have a few quibbles with a few of my components needing to be client-side rendered, for example, the P5js sketches and the CV PDF preview. but using Nextjs' `next/dynamic` packages I was able to now render the majority of components on the server, and then render the canvas-based components purely client side, ultimately the best of both worlds.
Once on a roll, it was very simple to get everything in place, and have the page act and feel like it did when it was purely client-side rendered. The P5js sketches worked as expected, the CV preview was rendered, and the page was animated as it was supposed to be. Great! so I shipped the Nextjs project and deleted the old `create-react-app`.
With the old project deleted, I could now remove two sources of complexity from my project
  1. Prerender.io since we no longer need pages to be cached to get SEO and social media previews working anymore
  2. Nginx Since Nextjs comes with its own server, I did not need the complexity of an nginx instance to serve my client-side javascript bundles anymore
Even though I could see that I was making great progress in improving my portfolio, there were still several areas of improvement that could be made, Server side rendering is good, but since my backend service just delivers the same data over and over again, only changing when the CMS is updated, the grand majority of network calls to the backend are somewhat wasteful. Ultimately we needed to venture into the territory of

⚡️🎈 Static Generation

As I mentioned previously, my portfolio is a glorified static webpage, only changing when the CMS is updated, there is no real way for the user to interact, or update information. it's an online resume that I use to get work.
Server-side rendering means the page is rendered each time the user accesses it on the server, and then the fully compiled javascript bundles are delivered to the user. Whereas static generation happens when the server comes online, and only when it comes online, from then on the server serves these ready-to-go javascript bundles.
I needed something that sits right in the middle between server and static, I ultimately want to serve a static site and reap all the performance benefits of one, but I also want to have the site's pages regenerated whenever the CMS is updated. Server-side rendering would catch all changes since refreshing the page would collect and render all new data, whereas pure static generation would not
Luckily Nextjs has a feature called incremental static regeneration (ISR), where Nextjs can regenerate pages on the fly, meaning that you get the best of both worlds, static pages and more importantly up-to-date static pages, without the faff of redeploying your frontend app.
My initial peek at this feature saw me trying out ISR based on an interval, and this worked perfectly fine, my page was constantly up-to-date and static. The problem is that the content on my portfolio doesn't always update every 300 seconds, it updates whenever I update the CMS, and these updates can be quite spikey, with many updates in a short space of time, and then nothing for months.
Again luckily Nextjs have thought about this and offer ISR on demand, where you can trigger a regeneration of page(s) of your website programmatically. This can be done by setting up an API route in your Nextjs server. Once triggered your Nextjs server will recall `getStaticProps()` on the pages that you revalidate.
With this super powerful tool in place, Once my backend service receives a Contentful webhook request, my backend can then prompt the frontend to revalidate the affected pages, meaning that my portfolio is always up to date, and the whole process is purely server-driven instead of the frontend driving the process. Reducing network noise and over-fetching data to generate pages with the same content as before.

🤔 Conclusion

I achieved what I set out to do, got SEO and social media previews working, and in the process carved out a much more server-driven and efficient distributed system to deliver my portfolio to browsers with the latest content.
Looking at the above architecture diagram, we have removed a good chunk of the complexity put in place to prerender and cache the portfolio. However, I feel that there is a bit more work to do to drive more complexity regarding the backend service interacting with Auth0 and redirecting around to regenerate caches and prompt the front end to regenerate itself.
Ultimately this journey was an interesting exploration of several technologies, identifying their weaknesses and strengths and finding the best tool for the job, through this, I learnt that achieving the goal could have been done in a myriad of ways. It all comes down to how much complexity you are comfortable with juggling really. I ultimately wanted to strike a balance between using something off the shelf and having the control required to configure the system the way I wanted (without webpack 🤦‍♂️).
I think that this exercise helped me grasp the concepts of client, server-side rendering and static generation. In a way that I had not fully understood before. So as a bonus, I feel I learnt something valuable.

Blog

Contact

The Sauce Forge
LinkedIn
Github
DeviantArt
MoxField

Get in touch!