Published: January 22, 2025
QuintoAndar significantly improved its web performance by reducing its Interaction to Next Paint (INP) by 80%, leading to a 36% increase in conversions year-over-year. Recognizing the importance of fast, responsive sites for user engagement, we implemented a "Code Yellow" to prioritize performance across all teams.
Using tools like Real User Monitoring (RUM) and techniques such as async/await for long task optimization and React transitions, QuintoAndar successfully reduced interaction times and improved the user experience. The changes—including removing third-party pixels and rendering optimizations,—resulted in better performance metrics, going from 42% to 78% of pages now meeting INP's "good" threshold of 200 milliseconds or less, and only 6.9% of pages offering a poor experience versus 32% when we started.
The problem
QuintoAndar is the largest housing platform in Brazil, with listings also active in several Latin American countries. Search is the largest online channel in real estate, this means that acquiring organic traffic is vital to its business. Additionally, providing an excellent user experience is crucial to keeping users engaged, helping users find their dream homes.
At the beginning of 2024, QuintoAndar realized that, while they likely had the best platform in the market, they could be delivering a better user experience leading to higher conversion rates. This became evident with the introduction of Interaction to Next Paint (INP) as a Core Web Vital, and, in fact, QuintoAndar had the worst INP compared to our competitors.
Aware of the negative impact of a high INP on user experience, QuintoAndar's SEO and Web Performance team decided to take action. With a well-defined action plan, they began working on a series of technical and content improvements aimed not only at reducing INP, but also at enhancing user engagement and click-through rates.
This is the story of how QuintoAndar managed to reduce INP by 80%, resulting in a significant increase in conversions and user experience improvements. In this case study, the strategies implemented, the challenges faced, and the results achieved will be explored.
Code Yellow: Prioritizing web performance
Aware that web performance is crucial not only for user experience but also for overall business success, and knowing that a fast and responsive site results leads to higher engagement and better user retention, QuintoAndar understood that achieving these results required a continuous and coordinated effort across the organization. This led to QuintoAndar instituting a "Code Yellow".
The concept of "Code Yellow" originated at Google as a response to the need for improved speed, granting a designated leader the authority to recruit anyone within the company to assist, regardless of their current projects.
At QuintoAndar, the "Code Yellow" acted as an internal alert system designed to prioritize web performance improvements within the organization. When the "Code Yellow" was declared, it triggered immediate and coordinated action from various teams within the company to address and resolve performance-related issues.
How QuintoAndar identified major opportunities and applied optimizations
Delays over 200 milliseconds are noticeable to users, and any significant lag beyond that impairs a good user experience. This is why the INP metric is so important: It assesses a page's overall responsiveness to user interactions by observing the latency of all click, tap, and keyboard interactions that occur throughout the page lifecycle.
However, improving this metric requires a deep dive into the details. For QuintoAndar, the first step was to identify which stages and elements of the user experience were responsible for slow interactions. This can be done using Real User Monitoring (RUM) techniques, which allow for detailed tracking of slow interactions. This includes breaking down the INP into sub-parts such as input delay, processing time, and presentation delay, as well as analyzing Long Animation Frames (LoAF).
Through this process, it was possible to identify, for example, that certain elements of the property search experience were causing a 4 second interaction time at the 75th percentile (affecting 25% of users). By optimizing long tasks, significant improvements were achieved in many slow interactions affecting INP. This was done by employing async/await to create yield points in QuintoAndar's JavaScript code:
function yieldToMain () {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
In this way, useful visual feedback for the user can occur more quickly. In QuintoAndar's case, a spinner was rendered, the main thread was yielded to for other possibly higher priority tasks, and then the rest of the work to be initially done could resume after yielding:
async function handleFilterClick () {
showLoadingSpinner();
await yieldToMain(); // Yield point
await loadFilterData();
showModal();
}
Another widely used technique—which is essential for those building applications with React—is the use of transitions. Since React now supports transitions, QuintoAndar could use the useTransition hook to update application state without blocking the user interface.
import React, { useState, useTransition } from 'react';
function App() {
const [isPending, startTransition] = useTransition();
const [value, setValue] = useState('');
const onInputChange = event => {
setValue(event.target.value) // high-priority
startTransition(() => {
// Time-consuming task—for example, filter and update the list...
});
}
return (
<div className="App">
<input
value={value}
onChange={onInputChange}
placeholder='Start typing...'
/>
</div>
);
}
export default App;
Along with the techniques mentioned, QuintoAndar implemented other improvements such as the use of memoization, debouncing, abort controllers, Suspense, resulting in improvements to INP.
For example, in the previous code example, debouncing could be applied, which is a technique that delays the execution of a function until a certain period of inactivity has passed. This helps prevent unnecessary updates when the user is typing quickly.
useEffect(() => {
const handler = setTimeout(