Scaling Plum Guide — Modernising and Scaling the frontend
Where we started and how we are scaling our frontend architecture for team, product and company growth.
👶 How it started
The first version of the Plum Guide platform was built from scratch by Imran and Tom Williamson in 3.5 months. That’s an entire booking platform and internal tools for users, marketing, finance and sales management.
The first version was born as a .Net MVC web app and, in a short period of time, it quickly grew to thousands of lines of code across the stack.
The frontend was built using server side rendered Razor views, sprinkled with jQuery for interactivity and Ajax calls - a very common pattern around 2015.
🤕 Growth pains
The best kind of pain!
For start-ups, shipping features is a priority. When you’re experimenting at an early stage and doing everything to move the growth needle as quick as possible, your codebase can take a battering:
- Complex pages with business logic seeping across the stack
- Low code coverage and test-cases churning at a high rate
- Build times increasing and deployment pipeline becoming very slow
- Lack of reusable components
When I joined Plum Guide the pace and growth were constantly increasing, as well as the technical debt. This led to inefficiencies, and with further growth on the horizon, they soon would become critical.
The issues were beginning to slow down the team, so a plan was required to deal with them while maintaining the pace of delivery.
Easier said than done, of course.
✨ Frontend — Getting faster, slowly
Our overall approach was:
- Modernise the stack
- Create new features in leaner isolated modules
- Migrate and remove the legacy code as we go
Migrating from jQuery, bower and grunt to React and Webpack
As the UI became more complex and a lot of logic was being written on the client side, we opted to lean on a framework.
We set off creating a few POCs (Proof of Concept) using React, Vue and Angular. Based on our requirements, performance and product vision we decided to use React.
React made it easy for us to create new features in isolation. We build these as isolated testable pieces of functionality and deploy them in isolation from the main codebase.
We employed Salami Tactics, or the Strangler Pattern, as its known in other contexts, to slowly break apart and migrate large pages into smaller, well-structured and testable ones.
As these modules grew, they started to take over and replace piece-by-piece the big chunky pages built in Razor views and jQuery, until only the server rendered page shells (for SEO) remained and the rest of the code was made of client side rendered React.
With time we introduced Redux to handle local data, which meant we could push some server side rendering logic to the frontend (these pages still relied on server rendered Razor Views).
The way we accomplished this in practice warrants its own blog post, which we plan on doing soon.
Decoupling backend from frontend
Decoupling our backend from frontend also allowed to us greatly speed up our build, deployment and test pipelines as well as our development speed.
We gradually separated our single build pipeline into separate smaller ones. This way we can now build, test and deploy frontend code independently from backend code. If a new feature requires changes in both, we can just deploy both at the same time.
Any new features use the new pipelines, leaving the old ones alone. We now run deployment for React in a separate CI process and deploy it to a CDN. Resulting in deployment taking a fraction of the time it took previously!
This change also carved out the way for frontend developers to work on UI features without having to run the entire project locally. With new code we moved the Razor views logic into Redux, the data fetching into API endpoints or by injecting it directly in the window via an INITIAL_STATE object
Now frontend developers can run parts of the app locally and simply point to a staging API instance for rapid local development.
This allows us to start better leveraging the React ecosystem and create more complex data-driven interfaces which are better isolated and easily testable.
Building a component library
Now we have far less dependencies on the server side to build complex interfaces and handle its complex state. We can also develop new features easily without needing the entire monolith project to run locally and our deployments take a fraction of the time they used to.
That does not mean we would not end up in a similar place as we were going, but in a different ecosystem, so we needed to make sure our code was not only smaller (which means easier to test), but also more composable and reusable.
Once again React lent itself very easily to this.
We started by moving components we built initially in react to the library, and building smaller ones to serve as building blocks.
Not unlike Atomic Design we built more complex components on top of these ones, and now, most pages are more than 70% built with reusable components, taking less than half the time it took to build similar pages before!
As we mature this library, we’re working closely with our product and design team to build a comprehensive component library with solid design patterns.
🥡 The biggest takeaway — TL;DR
If you take anything from this article is this:
- Identify your biggest bottlenecks that are slowing down the team down
- Make a plan on how tackle them — progressively, avoid big re-writes.
- Settle on a solution with your team, commit to it.
- Keep track of you goal and progress, adjust and and improve as you go
- Legacy codebases will always exist for a long time, so do not try to migrate all at once, or you will stall.
- Tech debt is a tool, not a problem. Manage it well, and you will move fast and deliver value while making incremental improvements.
It’s been almost a year since we started on this path and we’ve made several new improvement to the new stack, introduced React SSR, isolated complex backend services to smaller, leaner ones, and are working on a robust design system.
🚀 We’re just getting started
Plum recently closed its Series B round, which is very exciting news for all of us. We’re continuing to grow fast and and so is our engineering team.
We’re working on very interesting problems, and scalability continues to be the biggest theme in the room.
If this is something you find interesting, we’re hiring!