Chapter 1. The Frontend Landscape

I remember a time when web applications were called rich internet applications (RIAs) to differentiate them from the traditional, and more static, corporate websites. Nowadays we can find many RIAs, or web applications, across the World Wide Web.

Today, a lot of software as a service (SaaS) with more or less complex user interfaces (UIs) allows us to print our business cards on-demand, watch our favorite movies or live events, order a few pepperoni pizzas for us and our guests, manage our bank accounts from our comfortable sofas, and do many, many other things that make our lives easier.

As CTOs, architects, tech leads, or developers, when we start a greenfield project, we can create a single-page application or an isomorphic one, whose code can run both in the server and the client, or even work with a bunch of static pages that run in our cloud or on-premise infrastructure.

While we now have such a broad range of options, not all of them are fit for every job. To make the right decision for our projects, we need to understand the challenges we will face along the way.

Before we jump into the topic of this book, let’s analyze the current architectures available to us when we work on a frontend application.

Micro-Frontends Applications

Micro-frontends are an emerging architecture inspired by microservices architecture.

The main idea behind them is to break down a monolithic codebase into smaller parts, allowing an organization to spread out the work among autonomous teams, whether collocated or distributed, without the need to slow down their delivery throughput.

However, thinking about parallelism in the backend world, designing an application program interface (API) and encapsulating the logic into a microservice is actually the easiest part. When we realize there is significantly more to take care of, we will understand the complexity of the microservices architecture that adds not only high flexibility and good encapsulation between domains but also an overall complexity around the observability, automation, and discoverability of a system.

For instance, after creating the business logic of a service, we need to understand how a client should access our API, if it’s an internal microservice that should communicate with other microservices we need to identify a security model.

Then we need to deal with the traffic that consumes our microservice, implementing techniques for spike traffic like autoscaling or caching for instance.

We also need to understand how our microservice may fail, we may fail gracefully without affecting the consumers and just hiding the functionality on the user interface, otherwise we need to have resilience across multiple availability zones or regions.

Working with microservices simplify the business logic to handle, however we need to handle an intrinsic complexity at different levels like networking, persistence layer, communication protocols, security and many others.

This is also true for micro-frontends, if the business logic and the code complexity are reduced drastically, the overhead on automation, governance, observability and communication have to be taken into consideration.

As with other architectures, micro-frontends are not suitable for all projects; nevertheless, they can provide a new way to structure our frontend applications, solving some key scalability challenges we have encountered in the past not only from a technical perspective but also from an organizational one.

Too often I have seen great architectures on paper that didn’t translate well into the real world because the creator didn’t take into account the environment (company’s structure, culture, developers skills, timeline, etc.) where the project would have been built.

Melvin Conway’s Law said it best:

“Any organization that designs a system (defined more broadly here than just information systems) will inevitably produce a design whose structure is a copy of the organization’s communication structure.”

Conway’s Law could be mitigated with the Inverse Conway Maneuver, which recommends that teams and organizations be structured according to our desired architecture and not vice versa.

I truly believe that mastering different architectures and investing time on understanding how many systems work allow us to mitigate the impact of Conway’s Law, because it gives us enough tools in our belt to solve both technical and organizational challenges.

Micro-frontends, combined with microservices and a strong engineering culture, where everyone is responsible for their own domain, may result in a real silver bullet—complex to build but foolproof when used.

This architecture can be used in combination with other backend architecture such as a monolith backend or service oriented architecture (SOA), although micro-frontends suit very well when we can have a microservices architecture, allowing us to define slices of an application that are evolving together.

In this book, we are going to explore the possibilities provided by this architectural style and how to design a coherent architecture for our applications.

Single-Page Applications

Single-page applications (SPAs) are probably the most used implementations. They consist of a single or a few JavaScript files that encapsulate the entire frontend application, usually downloaded upfront.

When the web servers or the content delivery network (CDN) serves the HTML index page, the SPA loads the JavaScript, CSS, and any additional files needed for displaying any part of our application.

Using SPAs has many benefits. For instance, the client downloads the application code just once, at the beginning of its lifecycle, and the entire application logic is then available upfront for the entire user’s session.

SPAs usually communicate with APIs by exchanging data with the persistent layer of the backend also known as the server side. They also avoid multiple round trips to the server for loading additional application logic and render all the views instantaneously during the application life cycle.

Both features enhance the user experience and simulate what we usually have when we interact with a native application for mobile devices or desktop, where we can jump from one part of our application to another without waiting too long.

In addition, an SPA fully manages the routing mechanism on the client side.

What this means is, every time the application changes a view, it rewrites the URL in a meaningful way to allow users to share the page link or bookmark the URL for starting the navigation from a specific page. SPAs also allow us to decide how we are going to split the application logic between server and client. We can have a “fat client” and a “thin server,” where the client side mainly stores the logic and the server side is used as persistence layer, or we can have a “thin client” and a “fat server,” where the logic is mainly delegated to the backend and the client doesn’t perform any smart logic but just reacts to the state provided by the APIs.

Over the past several decades, different schools of thought have prevailed on whether fat or thin clients are a better solution.

Despite these arguments, however, both approaches have their pros and cons. The best choice always depends on the type of application we are creating.

For example, I found it very valuable to have a thin client and a fat server when I was targeting cross-platform applications. It allowed me to design a feature once and have all the clients deployed on multiple targets react to the application state stored on the server.

When I had to create desktop applications where storing some data offline was an essential feature, I often used a fat client and a thin server instead. Rather than managing the state logic in two places, I managed it in one and used the server for data synchronization.

However, SPAs have some disadvantages for certain types of applications. The first load time is usually longer than other architectures because we are downloading the entire application instead of only what the user needs to see. If the application isn’t well designed, the download time could become a killer for our applications, especially when they are loaded with an unstable or unreliable connection on mobile devices, like smartphones and tablets.

Nowadays we can cache the content directly on the client in several ways to mitigate the problem. A technique worth a mention is for sure the progressive web apps.

Progressive web apps provide a set of new possibilities based on service workers, a script that your browser runs in the background separate from a web page for enhancing the user experience of a client application served on desktop and mobile devices with flaky or totally absent connections.

Thanks to service workers we can now create our caching strategy for a web application, with native APIs available inside the browsers.

This pattern is called offline first, or cache first, and it’s the most popular strategy for serving content to the user. If a resource is cached and available offline, return it first before trying to download it from the server. If it isn’t in the cache already, download it and cache it for future usage. As simple like that but very powerful for enhancing the user experience in our web application, especially on mobile devices.

Another disadvantage relates to search engine optimization (SEO). When a crawler, a program that systematically browses the World Wide Web in order to create an index of data, is trying to understand how to navigate the application or website, it won’t have an easy job indexing all the contents served by an SPA unless we prepare alternative ways for fetching it.

Usually, when we want to provide better indexing for an SPA, we tend to create a custom experience strictly for the crawler.

For instance, Netflix lowers its geofencing mechanism when the user-agent requesting its web application is identified as a crawler rather than serving content similar to what a user would watch based on the country specified in the URL. This is a very handy mechanism considering that the crawler’s engine is often based in a single location from which it indexes a website all over the world.

Downloading all the application logic in one go can be a disadvantage as well because it can lead to potential memory leaks when the user is jumping from one view to another if the code is not well implemented and does not correctly dispose of the unused objects. This could be a serious problem in large applications, leading to several days or weeks of code refactoring and improvements in order to make the SPA code functional.

It could be even worse if the device that loads the SPA doesn’t have great hardware, like a smart TV or a set-top box. Too often I have seen applications run smoothly on a MacBook Pro quad-core and then fail miserably when running on a low-end device.

SPAs last disadvantage is on the organizational side. When an SPA is a large application managed by distributed or colocated teams working on the same codebase, different areas of the same application could end up with a mix of approaches and decisions that could confuse team members. The communication that overhead teams use to coordinate between themselves is often a hidden cost of the application.

We often completely forget about calculating the inefficiency of our teams, not because they are not capable of developing an application but because the company structure or architecture doesn’t enable them to express it in the best way possible, slowing down the operations, creating external dependencies, and overall generating friction during the development of a new feature.

Also, the developers may feel a lack of ownership considering many key decisions may not come from them considering the codebase of a large SPA may be started months if not years before they join the company.

All of these situations are not presented in form of an invoice at the end of the months but they impact on the teams throughput considering that a complex codebase may slow down drastically the teams potential of delivery.

Isomorphic Applications

Isomorphic or universal applications are web applications where the code between server and client is shared and can run in both contexts.

This technique brings some benefits when used correctly. It is particularly beneficial for the time to interaction, A/B testing, and SEO, for instance, thanks to the possibility to generate the page on the server-side, a crawler can index the final page faster which leads us to have full control on how fast Isomorphic applications can be designed in different ways.

These web applications share code between server and client, allowing the server, for instance, to render the page requested by the browser, retrieve the data to display from the database or from one or multiple APIs, aggregate it together, and then pre-render it with the template system used for generating the view in order to serve to the client a page that doesn’t need additional round trips for requesting data to display.

Because the page requested is pre-rendered on the server and is partially or fully interpreted on the backend, the time to interaction is enhanced. This avoids a lot of round trips on the frontend, so we won’t need to load additional resources (vendors, application code, etc.) and the browser can interpret a static page with almost everything inside.

An SEO strategy can also be improved with isomorphic applications because the page is server-side rendered without the need for additional server requests. When served, it provides the crawler an HTML page with all the information inside ready to be indexed immediately without additional round trips to the server.

Isomorphic applications share the code between contexts, but how much code is really shared? The answer depends on the context.

For instance, we can use this technique in a hybrid approach, where we render part of the page on the server side to improve the time to interact and then lazy-load additional JavaScript files for the benefits of both the isomorphic application and the SPA. The files loaded within the HTML page served will add sophisticated behaviors to a static web page, transforming this page into an SPA.

With this approach, we can decide how much code is shared on the backend based on the project’s requirements.

For example, we can render just the views, inlining the CSS and the bare minimum JavaScript code to have an HTML skeleton that the browser can load very quickly, or we can completely delegate the rendering and data integration onto the server, perhaps because we have more static pages than heavy interactivity on the client side. We can also have a mixed approach, where we divide the application into multiple SPAs with the first view rendered on the server side and then some additional JavaScript downloaded for managing the application behaviors, models, and routing inside the SPA.

Routing is another interesting part of an isomorphic application because we can decide to manage it on the server side, only serving a static page any time the user interacts with a link on the client.

Or we can have a mixed approach. We can use the benefits of server-side rendering for the first view, and then load an SPA, where the server will do a macro routing that serves different SPAs, each with its own routing system for navigating between views. With this approach we aren’t limited to template libraries; we can use virtual document object model (DOM) implementations like React or Preact. Many other libraries and frameworks have started to offer server-side rendering out of the box, like Vue with Nuxt.js, Meteor, and Angular.

As you can see, isomorphic applications won’t have much of an impact on your existing backend technology stack.

The last thing to mention about isomorphic applications is that we can integrate A/B testing platforms nicely without much effort.

A/B testing is the act of running a simultaneous experiment between two or more variants of a page to see which one performs the best.

In the past year or so, many A/B testing platforms had to catch up with the frontend technologies in not only supporting UI libraries like JQuery but also embracing virtual DOM libraries like React or Vue. Additionally, they have to make their platforms ready for hybrid mobile applications, as well as native ones.

The strategy these companies adopted is to manage the experiments on the server side where the developers have full control of the experiments to run on the clients. This is obviously a great advantage if you are working with an isomorphic application because you can pre-render on the server the specific experiment you want to serve to a specific user. Those solutions can also communicate with the clients via APIs with native mobile applications and SPAs for choosing the right experiment.

But isomorphic applications could suffer from scalability problems if a project is really successful and visited by millions of users. Because we are generating the HTML page pre-rendered on the server, we will need to create the right caching strategy to minimize the impact on the servers.

In this case, if the responses are highly cacheable, CDNs like Akamai, Fastly, or Cloudfront could definitely improve the scalability of our isomorphic applications by avoiding all the requests hitting origin servers.

Organization-wise, an isomorphic application suffers similar problems as an SPA whose code base is unique and maintained by one or multiple teams.

There are ways to mitigate the communication overhead if a team is working on a specific area of the application without any overlap with other teams. In this case, we can use architecture like Backends for Frontends (BFF) for decoupling the API implementation and allow each team to maintain their own layer of APIs specific to a target.

Static-Page Websites

Another option for your project is the static-page website, where every time the user clicks on a link you are loading a new static page. Fairly old school, I know, but it’s still in use—with some twists.

A static-page website is useful for quick websites that are not meant to be online for a long period, such as ones that advertise a specific product or service we want to highlight without using the corporate website, or that are meant to be simple and easier to build and maintain by the end user.

In the last few years, this type of website has mutated into a single page that expands vertically instead of loading different pages. Some of these sites also lazy-load the content, waiting until the user scrolls to a specific position to load the content.

The same technique is used with hyperlinks, where all the links are anchored inside the same page and the user is browsing quickly between bits of information available on the website. These kinds of projects are usually created by small teams or individual contributors. The investment on the technical side is fairly low, and it’s a good playground for developers to experiment with new technologies and new practices or to consolidate existing ones.

JAMStack

In recent years a new frontend architecture raised a good success called JAMStack (JavaScript, APIs, and Markup).

JAMstack is intended to be a modern architecture, to help create fast and secure sites and dynamic apps with JavaScript/APIs and pre-rendered markup, served without web servers.

In fact the final output is a static artifact composed by HTML, CSS and JavaScript, basically the holy trinity of frontend development.

The artifact can be served directly by a CDN considering the application doesn’t require any server-side technology to work. One of the simplest way for serving a JAMStack application is using Github pages for hosting the final result

In this category we can find famous solutions like Gatsby.js, Next.js or Nuxt.js.

The key advantages of these architectures are better performances, cheaper infrastructure and maintenance considering they can be served directly by a CDN, great scalability because we serve static files, higher security due to decrease of attack surface and easy integration with headless CMS.

JAMStack is a great companion for many websites we have to create especially considering the frictionless developer experience.

In fact, frontend developers can focus only on the frontend development and debugging, this usually means a more focused approach on the final result.

Summary

Over the years, the frontend ecosystem has evolved to include different architectures for solving different problems. A piece has been missing, though: a solution that would allow scale projects with tens or hundreds of developers working on the same project. Micro-frontends are that piece.

Micro-frontends will never be the only architecture available for frontend projects. Yet they provide us with multiple options for creating projects at scale.

Our journey in learning micro-frontends starts with their principles, analysing how these principles should be leveraged inside an architecture, and how much they resemble microservices.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.6.75