This three part blog series talks about web performance, UX, and premature optimization, and gives a brief on when & where to consider optimizations in your Software Development Cycle. Nonetheless, the series also offers you various ideas on how to fix said performance issues that I earlier learnt in the hard way. It is a blog that I didn’t have when I needed it, so I wrote it.
FRONTEND OPTIMIZATION
Websites nowadays fail to perform well on user inputs and actions. Poorly developed frontend code can easily break the user experience and the adoption rate.
Your Web Application,
- Could have high user volumes, built to be delivered to the browser by a CDN for faster loading/caching, and designed with resilient architectures, well-performing backends and disaster recovery mechanisms.
- Could also load blazingly fast within 1s and have the prettiest looking UI anyone has ever seen with lazy loading, code splitting, and all other load-time optimizations.
Conversely, your application might have a poorly performing runtime frontend code, which breaks the entire experience for end-users in the long run. If your application is highly dynamic/real-time and relies mostly on user actions, there is a high chance that your application is client-side rendered (CSR) with technologies like React, Angular, or Vue. Hence, optimizing the front end becomes crucial to delivering a seamless user experience.
EMBRACING NATIVE APPS
A efficient frontend should provide instant response for the action performed. Users expect a native feel to the web applications that they use in any form factor (desktop, mobile) as the line between native apps and standard web applications is becoming thinner day by day through Progressive Web Apps (PWA). Optimizing your app can drastically impact your conversion rate and click-through rates.
Caring About Performance: Too Early or Too Late?
“Move fast, break things” is a common motto around fast-moving projects. Although this is a good approach to ship “working” products fast, it becomes very easy to forget about writing manageable performant code. In essence, developers would be more focused on delivering the results first and secondly, caring about the performance later. Eventually, based on the application, the performance tech debt piles on and becomes complex.
Hacky/patchy fixes would be made to critical parts of the application to fix the performance issues at the very end of the project. It could often lead to various unknown side effects on other parts of the project that no one in your team has ever seen before. Initially, developers write simple codes that are easy to understand and takes less time to write. Thus, writing an effective code has a cost (time and resources) attached to it. Without proper documentation, the code base becomes riddled with cryptic performance hacks.
“We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.”
– Donald Knuth
HANDLING Premature Optimizations
This does not mean that every line of code that you write should have a performance-saving gimmick.
- A proper performance fix is implemented only when it can be measured. Unmeasured performance fixes can very often lead to unexpected bugs and issues.
- Caring about optimizing the non-critical part of your application is a huge waste of time and resources.
- However, fixing performance issues during the wrong time in your development cycle can eventually result in a negative outcome.
While starting on a task or a project, some good premature optimizations could be…
- Restructuring your files and folders, breaking your code into functions/components.
- Enforcing the usage of types on dynamically typed languages (optimizing workflow).
- The flow of data to-and-fro between parent and child components.
and some bad premature optimizations could be…
- Using profilers and fixing minor issues frequently without any feedback from your users.
- Using complex data structures and algorithms where a simple Array and inbuilt sort function would do the job.
When starting, it is necessary to think big. It should be less about “Should I use a for or forEach loop?“, and more about “Should I break down this huge component into sub-components to reduce unnecessary re-renders?“.
Measuring frontend performance
Runtime performance is a tricky problem to solve. The trickier part is measuring the performance and sniffing out the heavy components. However, there are various tools available to measure the frontend performance. It is always helpful to identify the main pain points of the application manually by clicking around. Identify components/pages taking most of the load and use it as a starting point. There can be various ways to measure performance depending on your app’s use case and complexity.
- Manual testing
- Using Chrome DevTools
- Measuring performance on the code level
- console.time(), console.timeEnd()
- performance.measure()
- react-addons-perf
- Using a profiler
- React DevTools profiler
- Angular DevTools profiler
After an initial round of testing, you might get an idea of where and how to start optimizing your app.
Ways to Optimize
There are plenty of different ways to optimize your application depending on the tech stack used, the frequency and shape of the data from the server, application use-case, and so on. This write-up covers some of the methods in the increasing level of complexity. As the complexity increases, the performance fix becomes very particular and context-specific to your application.
- Caching and Memoization
- Layout Reflow & Thrashing
- Virtualization
- Delay and Debounce render
- Thinking outside the box
- Offloading to web workers
- Offloading to canvas
- Offloading to GPU/GPGPU (experiment)
Now that we understand when and where to include performance fixes in your software development cycle, the next two blogs in this three part series will tell you HOW to do it.