Building modern Web Applications: 5 Essential Frontend Architecture Principles
In this article, I present five architectural principles for building a modern frontend. I first heard about these principles in a great talk by Natalia Venditto. They opened my eyes, so I try to explain these principles and my interpretation of them in more detail.
I love the following frontend architecture principles presented, which I first learned about in the talk "Beyond Micro-Frontends and the Jamstack - Architecting and Integrating Decoupled Applications in the Cloud for Enhanced User Experience" by Natalia Venditto in the O'Reilly Series "Software Architecture Superstream: Frontend Architectures".
Interestingly, a few months ago I was looking at architecture principles. Check out the following blog post.
In her presentation, she divides the principles for frontend architecture into two sections.
- Always! - Principles that we should always flollow.
- When Possible... Principles we should follow when possible.
I discussed with Natalia Venditto whether these points are «Frontend Architecture Principles», «Frontend Design Principles», «Frontend Design Desicions», «Frontend Best Practices» or «Frontend Design Recommendations».
Use the term you like best. In any case, these points are very useful and should guide you in the development of your frontend so that you achieve your quality goals like performance efficiency, portability, compatibility, scalability and resiliency.
Principle 1: Async or defer load, consider critical path
This is a principle we should always follow.
The loading strategy of web assets such as images, fonts and JavaScript in frontend development is key.
It's worth taking a look at the Google's Core Vitals. The Core Vitals are metrics that define the user experience of a web app. This metrics are also intensively checked by the Google Search Engine.
Especially, in a client-side rendering scenario, the loading of JavaScript modules is crucial. So if we want to load JavaScript, there are especially three main ways to do this:
- The standard way (not recommended)
- With the
defer
keyword (recommended according to this principle) - With the
async
keyword (recommended according to this principle)
The standard way
Let's take a look at the standard way. The standard way is a normal <script>
tag in the html markup like the following:
<html>
<head>
...
</head>
<body>
...
<script src="script.js" />
...
</body>
</html>
The disadvantage of this way of loading JavaScript is the following, if we look at the timeline and the behavior of the browser in this case.
From this point of view, there are two disadvantages:
- The loaded and executed script can only read the DOM before loading.
- If the script is "heavyweight", it can block the entire browser.
The defer way
Loading a JavaScript file or JavaScript code using the defer way is a much more elegant solution. defer
tells the browser to proceed with the DOM parsing and load the script in the background. After the DOM is parsed and the script is loaded, the script is executed. This leads to a non-blocking first initial rendering.
<p>...</p>
<script defer src="my-fancy-script.js"></script>
<!-- This p tag will be directly rendered -->
<p>...</p>
The async way
Loading a script with the async
attribute identifies the loading as completely independent. This means that browser doesn't wait for the async script. It is loaded and executed completely independently.
<p>...</p>
<script async src="my-fancy-script.js"></script>
<!-- This p tag will be directly rendered -->
<p>...</p>
Principle 2: Tree-shake, bundle consciously and eleminate dead code
This is a principle we should always follow.
JavaScript bundles are a way of packing several JavaScript files into a single file. Bundling reduce the number of http requests and facilitate caching. Tree-shaking is an optimization technique that removes unused code from JavaScript bundles. This can significantly reduce the size of the bundle, which can improve page load times and the overall frontend performance.
With active tree-shaking I get the following benefits:
- Reduced bundle size: Tree-shaking can significantly reduce the size of your JavaScript bundles. This can improve page load times and overall performance.
- Improved caching: Smaller bundles are more likely to be cached by the browser. This can further improve loading times for returning visitors.
- Reduced bandwidth usage: Smaller bundles use less bandwidth.
If you are building a modern JavaScript application, you should definitely be use tree shaking. It is a simple and effective way to improve the performance of your application.
Different modern JavaScript bundlers supports tree-shaking:
Principle 3: Define and respect a performance budget
This is a principle we should always follow.
Setting and adhering to a performance budget in frontend development is critical to creating fast, responsive and user-friendly web applications.
A performance budget sets acceptable performance targets for various metrics, such as the web vitals of Google, e.g. First Input Delay (FID), Largest Contentful Paint (LCP), and Time to Interactive (TTI).
By setting these metrics, developers can prioritize performance optimization efforts and ensure that their applications meet user expectations.
The key steps to setting and sticking to a performance budget:
1. Set performance goals: Determine the key performance metrics that are critical to the user experience. These can include FID, LCP, TTI, and other metrics based on your user's needs and expectations.
2. Analyze performance: Measure the current performance of your application with tools such as Lighthouse. This will give you a baseline understanding of your application's performance before you make any optimizations.
3. Identify large and blocking bundles: Use a bundle analyzer (e.g. webpack-bundle-analyzer) to identify the specific bundles of your frontend that are causing performance issues.
4. Prioritize the optimization: Based on the identified performance bottlenecks, prioritize the optimization to address the most critical issues first. Focus on improving the metrics that have the biggest impact on the user experience.
5. Monitor performance continuously: Monitor the performance of your web app continuous to ensure that it remains within the set performance budget. Integrate tools like Lighthouse CI in your CI/CD pipelines to automate performance testing and reporting.
6. Adjust performance budget as needed: As your application evolves and user expectations change, review your performance budget and adjust the thresholds as needed. This will ensure that your web app continues to provide a high-quality user experience.
By following these steps, you can effectively set and respect a performance budget, and ensure that your frontend applications meet user expectations in terms of speed, responsiveness, and overall user experience.
Principle 4: Stick to web platform APIs and web standards
This is a principle that we should follow whenever possible.
This principle is simple to understand. Utilise whenever possible the power of the web platform APIs and web standards whenever possible.
It starts with being familiar with the native web standards such as HTML, CSS and JavaScript. That sounds really simple, but is it really?
And it continues with the knowledge of the capabilities of native web platform APIs such as:
- Web Storage API
- Geolocation API
- Media Capture and Streams API
- Notifications API
- Web RTC API
- Payment Request API
- ...
With this knowledge you'll be able to create advanced and powerful web applications.
Principle 5: Use new generation frontend frameworks
This is a principle that we should follow whenever possible.
This speaks in favor of using modern and innovative frontend development frameworks to create user interfaces.
These frameworks offer several advantages over traditional frameworks, including:
- Improved performance and efficiency: The new generation frameworks are designed to optimize performance and reduce the amount of code required to create advanced user interfaces. This results in faster rendering, better responsiveness and more efficient use of resources.
- Increased developer productivity: These frameworks often provide a more structured approach to creating and maintaining complex applications. This can significantly increase development productivity and reduce the time it takes to bring new features to market.
- Active and growing communities: These frameworks usually have large and active communities of developers who contribute to their development, provide support and share their knowledge. This fosters a vibrant ecosystem of tools, libraries, and best practices that further enhance the development experience.
List of some of these frameworks:
Comments ()