Migrating from a monolith to Next.js at Studocu — Part 1
Welcome to our article series, where we dive into Studocu’s frontend evolution. This series offers a high-level overview of the challenges we’ve faced and our strategies to overcome them, focusing on how we balance user experience while also improving developer efficiency. Discover the strategic actions we’ve taken to strengthen our position in e-learning, driving our platform forward and keeping our development team agile.
History
In 2013 Studocu’s journey began with a monolith architecture, a logical and common starting point for most startups. This architecture served us well in our early days, enabling rapid development and deployment as the early vision was brought to life.
Our platform, designed to provide students with access to high-quality study materials, quickly grew in popularity, but as we expanded our services and user base, the limitations of our initial setup became a bottleneck. It was time for a change.
What were the challenges with the monolith?
At Studocu, our commitment to providing an exceptional user experience was increasingly made difficult by the limitations of our monolithic architecture.
For example, here are some of our problems we faced:
1. Core Web Vitals
It became hard to meet the modern web’s performance standards, impacting our search engine rankings and user satisfaction.
Being compliant with the latest Core Web Vitals is crucial for Studocu. We aim to deliver top-notch performance, as it directly impacts user experience and also our SEO rankings. Ensuring compliance with these standards is a priority, reflecting our commitment to maintaining a competitive edge and satisfying user demands for a quick and responsive website.
Enhancing Core Web Vitals was a challenge as solutions didn’t come out of the box. Staying up to date with the latest performance standards required manual interventions and custom workarounds. This setup made it increasingly difficult to maintain pace with evolving web standards, setting back our ability to quickly implement updates and optimizations. The lack of out-of-the-box solutions for performance improvements meant dedicating more resources to ensure compliance with Google’s Core Web Vitals, making us manually develop, test, and implement strategies to boost site speed, responsiveness, and visual stability
2. Generating Critical CSS with Penthouse
Critical CSS has been essential for improving our site’s load time, however, the process, powered by Penthouse, was inconvenient .
It helped us fix most of our Cumulative Layout Shift (CLS) problems, but it also made our build process more complex. This new layer of added complexity led to longer deployment times and introduced bugs and edge cases that sometimes caused layout shifts, going against our original goal of improving site performance.
3. Redux Store challenges
The tight coupling of our Redux store with the PHP backend in a Multi Page Application environment introduced significant complexities. The store, being directly populated with data coming from the PHP controllers, means that there’s a possibility that some modifications within the controllers could potentially affect the entire codebase, although this happens rarely. The problem usually is the assumption of reusability of the data between pages. Given a particular slice of redux state you might assume it has the same shape between pages, but there might be subtle differences between how different page controllers prepare its data, so reusing data/types between different pages poses a great challenge.
Manually validating and crafting types for the global state, actions, and more than 100 selectors is a huge effort in itself, and if that’s not enough — in a lot of cases — it has to be done for multiple types of pages because of the differences described above, so managing and understanding the global state has become difficult and causes a lot of headache amongst our developers.
Another challenge is not just in maintaining the Redux store but also in navigating through the existing code, and not knowing which has become deprecated or redundant. Consequently, this can lead to a reluctance to do necessary refactoring.
4. Developer experience
The developer experience was far from ideal. Spinning up the dev environment was taking longer and longer, we couldn’t sensibly cache our build assets, and were missing features like hot module reloading.
Using Docker in our legacy system significantly added to these challenges. As our product and its services grew, spinning up PHP, Redis, and MySQL and other containers the startup time for our development environment drastically increased. This growing number of services and technologies introduced further challenges in debugging, complicating our development workflow.
Despite significant improvements made by our Cloud Infrastructure team and others to improve on these 5 points, the impact of the challenges outlined above contributed to a slowdown of new feature development.
In transitioning into a new architecture, we are trying to improve both Developer Experience (DX) and User Experience (UX) challenges described above. By improving DX, we enable quicker feature rollouts and fixes, directly benefiting users with faster access to improvements, this strategy accelerates feedback on A/B tests and enhances UX. Additionally, preparing for future technologies like streaming and Edge computing further elevates user satisfaction, ensuring our updates positively impact both developers and users alike.
Our legacy flow
In our legacy system, a user request would initiate a multi-step process, all the data gathered in PHP controllers was forwarded to an Express server, which then populated the Redux store and rendered the page with React. This response was sent back to PHP and finally delivered to the user.
Below is a visual representation of this simplified legacy rendering workflow, highlighting the complexities and the steps involved in responding to user requests.
Goals
Our primary goal in migrating to Next.js was to enhance our platform’s scalability and performance. We aimed to build a more efficient and flexible ecosystem capable of supporting our growing feature set and evolving business needs without compromising on our user experience.
A key to our decision was the extensive community that Next.js have and its reputation for excellent overall performance, which aligns with our needs. The framework’s ability to facilitate smoother transitions played an important role, promising to improve developer efficiency, enabling faster, safer collaboration, deployments and fostering an environment that encourages innovation.
Look forward to part two, where we will dig into the practical impacts of these changes, illustrating how our technological leaps forward benefit both users and the platform’s growth.
=> Looking for an amazing job? ➡️ Check out our open positions