My thoughts on scalable React application 🎉
In this post, I would like to jot down something on my go-to scalable architecture for the next modern Front-End web application. In fact, the content of this post is also describing my curated knowledge accumulated over years as professional software engineer as well. Stay tuned and read on guys 😎
First and foremost, let's talk about the goal of scalable front-end web application and how success should be measured 😁. Given my experience with OSS community, Axon DEMS UI maintainer, FE lover - I strongly believe the below bullet points are what a successful web application looks like:
- Performance
- Accessibility
- Internationalization
- SEO
- Linting for best practices
- Image optimization
- Script optimization
- Font optimization
- Having design system in place
- Minimal but complete dependencies
- Group folders by domain not type
- Use case test coverage
- Favor static rendering over dynamic rendering whenever possible
- Avoid Redux at all cost - favor query solution like react-query or urql
- TypeScript - will shine at scale (can be skipped if you just prototype something 😬)
The KPI above are listed as a high level overview only. Now, let's go over a much more lengthy and detailed factors which I have learnt from the last 5 years of professional FE development with React. The order is from essential to strongly recommended 😎
Essential
-
Problem: Hand written utilities. At first, we might think the problems are simple enough yet to bring another library to the stack. Or even think that it would be lighter for our final bundle size. I was there, I did that for several years until I realized that I had kept reinventing so many wheels without contributing to many fantastic successful projects. And I always place correctness over performance! Once said by one of my favourite programmers - Tanner Linsley. "I don't create libraries unless I have to because it's just creating more work for me to maintain". I have done the same recently on Zoomable Video and Media Players at Axon
Solution: Look for well-known and sustainable open source projects to power up your application in the long run. Examples would be lodash, day.js, react-use, et cetera -
Problem: Tangling your third party code with your application code. This is something I have learnt lately from Nuxt Conference via one of Nuxt ambassador - Josh Deltener
Solution: Apply enterprise plugin architecture via Event Bus. Given a single event dispatched, this will then trigger all event handlers accordingly in a centralized place instead of scattering all around your codebase
-
Problem: Serve side rendering static page. SSR is nice and good for SEO but when it comes to performance for your application, it's no match for static side rendering. Given a simple listing page, your SEO properties are most likely are known ahead of time already and you need to fetch content over API to render the page before delivering it back to users. This forced your users wait certain amount of time via server's communication to render a page which costs CPU power.
Solution: we could leverage SSG / ISR to pre-render all listing pages to deliver content to users as fast as possible via CDN caching since they are all static. Then, browsers can take it from here to fetch the main content to get latest data on demand
-
Problem: Having no design system in action. This results in less than optimal user experience across your applications - take Gmail, Google, YouTube, Firebase, et cetera from Google. They all offer cohesive and smooth experience to their users thanks to their Material UI design system specification. It's very tempting to start with all hard-coded values to get your UI components look pixel perfect as per design. In long run, it won't scale as you need to reuse components across your products
Solution: Spend time with your design team to spec out a design system in place. Components like Button, Menu, Divider, Text, Spacer, Slider, et cetera should be built up front before start scaling your application. Moreover, things like themes, internationalization, RTL support must be carefully planned from the get-go to avoid any regret in the future. I highly recommend TailwindUI and ChakraUI Pro for your application given its robustness, responsiveness and accessibility built-in (which is one of factors for your site ranking in many search engines)
-
Problem: Using Redux for your application. Redux became a real deal back in 2015 by Dan Abramov. Its problems lay in the lack of architectural design until style guide came recently, but given its verboseness, slow performant at scale and hard-to-get in the long run. I would never recommend Redux for any modern application given my painfulness of maintaining a large enterprise level for the last years at Axon. Working with Redux can be described in the image below (you are in the long queues of actions before getting to the main points) 🥲
Solution: the web community has become much smarter. Solutions like GraphQL's Urql and react-query are dominating the modern web thanks to its robustness and lightweight. That's where we all should be headed to when applying data fetching libraries in our application
-
Problem: Using bare Fetch API to consume web API. There are several things I could not like about base Fetch API. First, it does not throw any errors on none 2xx status from server response - we have to manually check for ok property and decide what to do next. Second, Fetch API does not have progress event to indicate loading status of our requests
Solution: I would highly recommend Axios library instead given its battle-tested, well-maintained by the community. It's also go-to solution for data fetching from Vue community -
Problem: Using base image. We live in the world of mobile first development. Most images on the web have not been served responsively with the right format for browsers to consume easily.
Solution: All key players in the web community (Google, Vercel and Nuxt teams) have partnered to create best in class Image component for developers to consume - Next/Image and Nuxt/Image respectively
Strongly recommended
-
Problem: Not using environment variables for different environments. This will lead to many pitiful events such as your dev environments trigger data to your production APIs. I had been there - trust me 😅
Solution: Apply environment variable supported by your frameworks of choice
-
Problem: Full page reload. It's tempting to think that full page reload is okay as long as it's fast enough until... Things do not work the way it's supposed to be. Let's take mobile application, the app never gets full-reload - this helps them to preserve previous states while keep gaining attraction from users. Every time we do full page reload, we will lose all states and harness extra requests on our server
Solution: Favor partial load whenever possible via special links / routing on the web
-
Problem: Not customizing error pages. This will make your users having a hard time reading some default error which is not specific to your brand
Solution: Tailor-made your errors pages
-
Problem: Not testing enough. One of my brilliant friends told me about his working strategy. His testing time would typically triple his development time and he would automate as much as possible since machine is much more powerful than us 😄
Solution: Write test, not many, mostly integration
-
Problem: Not checking your code before committing. Many developers have tendency to rely on CI to do the job instead of doing reasonable amount of work on their machine. As your teams grow, this will slow down your CI
Solution: Apply lint-staged to early check your changes and avoid silly mistake such as incorrect typing - believe me - I had been the worst on this kind of mistake and caused our CI to waste hours of execution 😆
-
Problem: Not using Develop Preview Ship - DPS model. It's very often we want to have fantastic CI to do certain things for us such as linting, testing, type-checking, et cetera without giving much attention on Preview. Given I'm a huge fan of demo over deadline over years. I love having my PR get built and ship a demo URL to my colleagues at work visually. In other words, seeing is believing 🤩
Solution: Using deployment platform CI provider like Netlify Preview or Vercel DPS to maximize your development experience 😎
Reference:
- Next.js Commerce
- The Elements of UI Engineering
- How to properly internationalize a React application using i18next
There you have it guys, my personal recommendation on how scalable web application architecture should look like. Together, we make the web faster. Peace out and until next time 🙃