Geschätzte Lesezeit 33 minutes
Crafting Good Frontend Architecture: Key Principles and Best Practices
This article explores the key principles, benefits, and practical steps for establishing and maintaining a good frontend architecture, including modularity, reusability, and the importance of a shared mental model among team members. Stay ahead in web development by leveraging best practices and tools for automated checks, guided by Melanie Bucher – a dedicated architecture enthusiast.
Overview
- Goals and principles of a good frontend architecture
- What does frontend architecture include?
- Benefits of good frontend architecture
- Key principles in frontend architecture
- How to start off with good frontend architecture
- Good frontend architecture is based on a shared mental model
- How to set up frontend architecture at the project start
- How to maintain good frontend architecture long-term
- Having at least one architecture “spokesperson”
- Coding practices and tools for automated checks
- Future development trends and frontend architecture
- Get in touch with us
Goals and principles of a good frontend architecture
We all want good modern frontend applications. In our digital day-to-day life as users, we like to use user-friendly, fast, stable, and available applications. The providers of these apps like to own long-term profitable, performant, and secure apps. And as developers we like to develop well-functioning, maintainable, and extensible apps, achieved through a clear code structure and good architecture.
While “good” frontend architecture is key to building an application that serves the goals and satisfaction of users, businesses, and developers alike, it is in itself a broad and vague term. Its understanding is subjective and highly context-dependent in practice.
In order to get a deeper insight into what good frontend architecture constitutes for us at MaibornWolff and how we realize it in our software development projects together with our customers, I talked with five of my senior web engineering colleagues about how they personally conceptualize and apply frontend architecture in their agile development teams.
The insights that my colleagues shared comprise a higher-level overview of the topic – the goals we’d like to achieve with good frontend architecture, what aspects are crucial, how to set up an architecture in the beginning of a project, and how to sustain good architecture throughout later development stages. While the general principles should largely apply to any development platform as well as mobile development, the examples and insights discussed in this article series are mainly based on the personal experiences shared by my colleagues in the area of web development.
We will start off with looking at what frontend architecture is, which goals we want to achieve with it, and which key principles are crucial in the development process.
What does frontend architecture include?
Architecture is about the way one structures and divides software, in particular its code, into building blocks. A frontend architect focuses on the big picture of an application’s structure – which parts a software consists of, and how these different software parts interact with each other. The concern is thus on the overall structural arrangement of an application rather than on the specific implementation of the different parts. Ideally, the implementation of the building blocks of the software is interchangeable, meaning that single blocks are independent of each other. This also makes them reusable.
Looking at an application’s technical environment, architecture also involves the tech stack of the application and the structure around it, for example whether it is embedded in several frontend systems and how it plays together with one or more backend systems. On the client side, architecture may be influenced by performance requirements, browser constraints in bandwidth or memory as well as device dependencies. It also involves non-functional requirements such as HTML structure for search engine optimization and accessibility.
Setting up an architecture is like building a foundation on a green field, and we need to ensure that this foundation is stable and enables us to grow and extend the application going further. Taking decisions regarding the framework, libraries, and coding practices set helpful directions as well as possibly narrow constraints on the architecture. In consequence, future development is highly dependent on the initial setup.
Benefits of good frontend architecture
In project-based development, we aim to implement the technical functionality and requirements of the app with the best qualitative characteristics possible, given a certain timeframe and the team’s capabilities. With good architecture, we ensure that the application is:
- Functional: the app works as intended and is robust and performant
- Extensible: the app embraces growth, meaning new features can easily be integrated while staying stable and maintainable
- Integrated: the app fits well into the existing technical landscape
- Testable: the app can be tested easily and extensively
Certain architectural patterns may also help in achieving non-functional requirements like performance, as there are tools such as Lighthouse that take page speed into account during development if the architecture meets the tools’ requirements. In addition, a clean and clear architectural structure reduces the mental load on developers, fosters a common understanding in the team, and facilitates the onboarding for new developers, which helps business goals.
Key principles in frontend architecture
From the interviews with my colleagues, certain key areas emerged as those to which they pay most attention to in frontend architecture. These include framework selection, modularization, data flow, component segregation, and a balanced file structure.
- Selecting a framework
Since usually not all information is available at the beginning of the project, it’s important to collect as many insights as possible from your client, asking for example:
- How long-lived the project will be
- How many developers it will have
- How often the developers will change
- How big the project may become
- What business relevance it has
Answers to these questions may give you a good idea of the scope of the application and the product vision, where it is headed, and which business goals are driving its development. Consider whether the customer may already have a preference for certain technologies, and whether the team is already in place and what knowledge they have. Then start off with a best guess, taking into account the degree of interactivity of the application, and whether the considered framework allows you to start building a small basic prototype, so you can avoid facing too high hurdles or constraints in the beginning.
Furthermore, it’s important to evaluate how the process for design and UX will be handled in the project, as this needs to be integrated very early on and its importance for business goals is rising. You may also evaluate if meta-frameworks like Next.js, Remix or Nuxt.js, that specify certain architecture patterns, fit your use case. Finally, it is recommended to use appropriate libraries, as there are often many good solutions existing in the framework’s ecosystem, while making sure they play well together. It’s less recommended to build everything yourself, which also means having to explain it to everyone.
- Modularization
Modularization means drawing boundaries in the system to separate modules cleanly and keeping them as independent as possible. To find appropriate boundaries, think about which module belongs to which feature and consider how features may be grouped into business domains. Each module will need certain data and should ideally only get what it needs. To minimize overlap and keep modules separated, analyze where the dependencies are in your system. There should also be as few technical components as possible, except for global things such as logging and authentication, which should be stored in a central place.
However, since modularization may increase the complexity of apps despite its benefits, keep other effects such as potential decreased page speed in mind. It’s also beneficial to think about the impact that modularization of your features may have for team division in the future.
- Component segregation
Similar to modularization but on a more detailed level, in modern frontends, code blocks are usually contained in components, which is referred to as component-based architecture. Components should have one single responsibility, so they are self-contained elements that communicate with other elements via their interface. Their purpose is to be reusable across your application or even across different applications, making their use modular and flexible. The application will overall be assembled by these independent components, and smaller child components may be flexibly used in larger, more customized parent components.
It’s important to build small, reusable, and if possible, fine-grained components, which could be achieved through for example:
- Distinguishing representational components from logical ones by separating them neatly
- Encapsulating data into an abstraction to make data accessible through a single point in the application (e.g. in React, with the Context API)
- Creating uniformity among component implementation and considering introducing a shared component library
The benefits of building cohesive yet independent components are for one that their reusability allows for faster development. They can also be updated or expanded in one central location, making maintenance easier. For large projects, the independence of components might also enable teams to work in smaller teams, given that interfaces are kept stable when changing internal implementations.
- Data flow and interfaces
As every application has its component tree starting with the root, it’s important to introduce a data flow that is logical, consistent, and easy to understand. This will make it easier to grasp from where data originates and where it’s directed at, and thus make it easier to debug.
In the case of parent-child component structure, make sure that information flows only in one direction in components – only from parent to child components, and that the parent component controls the child component. However, keeping components decoupled is crucial, which other forms of making data available to components such as data injection make possible. Overall, a balance is needed between a clear and directed data flow and keeping components isolated. For this, a central state management is needed, which holds and updates the necessary data and provides components with only what they need.
State management is particular to frontend development, as the user’s data input in the browser, so in the frontend state, needs to be synchronized with the backend. As these tools are not that easy to build, there are many of them on the market. You will still face a certain amount of overhead in setting them up, but choosing an appropriate state management technology is crucial. This also boils down to an architectural decision depending on the specific project context, as it should satisfy your requirements as well as integrate well with the chosen framework and libraries. When using stores or libraries for state management, make sure that you use the dependency inversion pattern to avoid tightly binding your app to a specific store implementation or store technology.
When designing interfaces, keep the inversion principle in mind and decide in advance who determines the interface – the consumer or the producer. In general, it is very useful to align your code with the SOLID, DRY, and YAGNI principles, but sometimes it is worth questioning or breaking them, so look out for where they may hinder more than help. For example, one of my web architect colleagues has derived his own “minimal interface principle”, stating that an interface (or any other thing) should do only the most minimal thing necessary to achieve the given requirement. This will minimize the reasons for changing the interface and thereby make it more robust and stable. He tests interfaces and other code against these questions:
- What is the minimal thing I need to put in and get out?
- Is the minimal solution well related to the power of the solution? To speak, how powerful is the smallest solution? Ideally, find the smallest solution with the largest power.
Furthermore, avoid building something that is not accounted for in the framework you are using, but rather stick to its rules.
- Balanced file structure
On the one hand, having many files in one folder can make it hard to keep a good overview and appear chaotic. On the other hand, a too deep hierarchy with many nested folders can obscure its contents, making it difficult to know where to find what you are looking for. A balanced structure appropriate for your use case, for example several high-level folders grouping a small number of subfolders, may help mitigate both problems as the codebase grows. With a relatively flat hierarchy, so by using a number of small folders and files, each component or independent function will be located in its own file. Even though this doesn’t completely eliminate the complexity of having a large code base but shifts it horizontally, having many small items on a few levels usually allows for a better overview.
If your selected framework comes with a predetermined structure, sticking to it will make most sense. Within this, folders can for example be functionally ordered or logically according to domain. You could also base the folder structure on how the app will be used, for example by building a web app as if it had pages, even with a single-page application, since a browser is page-based. A web application that ends up in the browser could for example be structured in folders for each page, with its components next to it.
To summarize, keeping in mind these general principles and the goals of implementing good frontend architecture can help in finding a consistent approach to it that will enable the application to grow in the long run.
We will take a more detailed look at the process of developing an architecture for your frontend application at the starting point of a project. Stay tuned!
How to start off with good frontend architecture
There is not one perfect reference project that solves all architectural problems. Developing good architecture in your specific project is thus a question of setting up an architecture that is appropriate for the project, applied well, commonly known and shared as a commitment in the team.
Good frontend architecture is based on a shared mental model
Architecture is not only a technical skeleton. It is also something we as developers use to make sense of the system in an abstract way, like a mental model of how we think about the application. Sharing the same mental model among the team enables us to reach a common understanding and to communicate effectively with other developers.
So at what point is an architecture emerging? One could argue that one human alone may not need architecture at all, as long as this person knows their way around the project and does not lose their overview. However, even if a single person codes an application, the code will always have an implicit architecture, even if it may be inconsistent or hard to follow. Thus, especially in a team environment, it is about making architecture explicit on the one hand and consistent on the other. The more aware you are of your architecture and the big picture, the more deliberate you can construct it and make explicit rules known in your team. This common understanding will allow your team to be consistent in how problems are solved.
In addition, it is helpful for the whole team to get a good insight into the vision of the product owner about where they see the application in the future and what its purpose is. Communicating this inside the developer team will enable everyone to have real-life context and use cases to connect their more abstract, shared mental model to. In addition to the initial requirements analysis, if you know the framework conditions and what the application should ideally look like in two years from now, you can plan accordingly, even if your company may only be with the project for one year until handover to internal development.
How to set up frontend architecture at the project start
While the specifics of your implementation will depend on your project and its context, the interviews with my colleagues converged on a few points to keep in mind in the initial phase of the project for working towards a good, stable frontend architecture in the long run.
- Thinking small and embracing change
How do you set up your app in the beginning so that you prepare for it to go from a small application to a large one, encompassing many features and functionalities? Most of them argued that starting out small and iteratively helps you get going and focus on those problems that need solving right now. With each iteration, the complexity of the application will then align with the project stage, and you can avoid developing something that you aren’t going to need later (YAGNI).
You might want to follow a small waterfall model for this in the very beginning, so that you don’t forget any essentials and helpers such as linting and automated quality checks, but so that you also solve only what needs to be solved at that stage. Then, in an agile manner, add to it bit by bit and refactor it as you go. You could also start off with an initial example setup and see how it fares after sticking to it for a bit. After the first user stories are done, evaluate it, rewrite it to clean it up, and keep it like that from that point on. It could also be done as a proof-of-concept that you throw away after a short time, based on which you then start again with what you’ve learned from scratch.
Also consider the seniority of your team, as they need to work with the architecture on a day-to-day basis. Providing and communicating a more established structure in the beginning may give more security to junior developers.
While it is important to know the long-term product vision, thinking too big in the beginning can lead to over-engineering and introducing unnecessary complexity into the application. Keeping it simple (KISS) is key here; finding something that works for now and that can be refactored easily as the application grows. Try to anticipate potential roadblocks ahead of time, but also to stay at the level of complexity that is required at this point in time. Most crucial is a high flexibility and extensibility of the app, so that when a large team of developers might be working on it in the future, it is set up for growth.
- Using project-specific conventions for consistency
While starting small and working iteratively as described above, try to establish good practices as early as possible. Good architecture is supposed to always help and take work off your shoulders over the course of the project’s development. It can do this by providing a consistent and clear framework, backed by explicit rules, in which development takes place.
At the beginning of a project, the framework has probably been decided on quite quickly, and after evaluating which other tools to use, you’ll put an initial structure in place that serves as the starting point for everything that follows. Making initial decisions at the beginning of the project to ensure that the application has an easy-to-grasp structure and data flow is crucial at this project stage. Explicit conventions may be established around:
- Naming: logically consistent naming of variables, functions, classes and components, as well as case rules for different elements and files
- File organization: using consistent folder and file structures
- Component structure: organizing code into logical components, and how a component is built, keeps its state, or gets data from
- Imports: specifying allowed types of imports and which modules can import from which modules or libraries, i.e. how dependencies are managed between different parts of the codebase
- Best practices and design patterns: common use of design patterns for writing code that is efficient and scalable, and how to use third-party libraries effectively
- Commenting: consistent use of comments and a clear commenting style
- Error handling and logging: consistent practices for error and exception handling and guidelines for logging
- Testing: covering the codebase with consistent patterns in unit and integration tests
- Formatting: indentation, spacing and line breaks for readability
It’s important to stick to these conscious decisions once they have been set so that they remain clear and can be implemented throughout. An effort by everyone to adhere to the architecture as much as possible will also bring about useful coding examples that provide direction for new future developers in the team.
In the long-term, conventions help by reducing cognitive load and ensuring uniformity. It is easier to put new things into place when following the already established pattern, which also makes it easier to grow in later stages.
We’ve seen that once you have decided on an approach, it should be kept to ensure consistency in the future. But you also need to be open to changing and questioning things if necessary. Frontend applications are “living” structures that are changing and growing over time. Some things that you have built in the past might at some point be not suited anymore to satisfy current requirements and simply not do a good enough job, making refactoring necessary. Refactoring can open up new possibilities and may be embraced for that reason. After completing refactoring changes, it’s crucial to reestablish consistency. Solutions and new best practices need to be communicated well, and changes should be made explicit, so that new patterns can then again be followed consistently.
In summary, starting small and embracing change while sticking to explicitly established conventions will set up a good foundation for your frontend in the beginning. Consistency is key and will also allow a shared mental model to develop among your team, which enhances effective communication.
What remains is the question of how we can maintain this architectural foundation we have set up in the long-term, in order to keep our frontend scalable, performant and maintainable over the project lifecycle. We will therefore dive deeper into how a team can work together to keep good frontend architecture alive. We will also explore tools and techniques for how automated checks can support this.
How to maintain good frontend architecture long-term
Over the course of time, architecture depends on how developers interact with it and make changes to it. Frontend architecture tends to have few constraints and be flexible, especially in the case of component-based architectures based on libraries and frameworks such as React or Vue.js, more so than Angular applications, for example. While flexibility is good because it makes it easy to extend the application, it also makes it more prone to divergent structures or chaos. That’s why it’s crucial to ensure that new code fits into the existing architecture. This can be done by having someone in the team who is responsible for keeping an architectural overview, as well as using a range of appropriate automated checks and tools.
Having at least one architecture “spokesperson”
As discussed in the previous article, establishing an initial architectural structure in your frontend, including some conventions about how things work in the same way throughout your application, helps to foster the development of a shared mental model amongst your team. Going forward, it’s a matter of adjusting the initial structure in the right places when necessary and maintaining it as the application grows. At least one person should be responsible for this: maintaining an overview of architectural aspects throughout the project. Depending on the size and seniority of the team, and the needs of the project, the role of the architect may vary somewhat. Responsibility might mainly be taken on by an architecture lead, be shared among the entire team, or fall to a dedicated team in large projects.
- Lead architect in one team
In teams with few senior and many junior developers, it may be useful to have one to two dedicated people who are responsible for the application’s frontend architecture. They can be thought of as “spokespersons”. They take the subject seriously and have a deep knowledge and understanding of it. Ideally they have seen the beginning of the project, and most importantly they have a vision for the future as well as plans how to get there. They stand up for the architecture within the team and outside of it, for example regarding new requirements, whether that means making adjustments when necessary or maintaining consistency. They also take on responsibility for cross-sectional topics such as accessibility, internationalization, and performance.
Rather than solely providing top-down guidance, the spokesperson’s aim should be transferring competence to all team members. Ideally, everyone in the team should be able to uphold the architecture themselves – the role of the architect is then “to make themselves obsolete in theory”, as one of my colleagues put it. Spokespersons may actively involve other developers in coming up with improvements of new code. They may also make the guiding principles known and accessible to everyone, for example through written or visual documentation or communicating it well otherwise. Still, in addition to competent team members, at least one person should uphold the role of making sure new code adheres to the principles agreed upon and take on the responsibility for it.
In addition, having insight and foresight about where the application is headed is invaluable in the role of an architect, and “thinking is cheaper than developing”, as one of my colleagues said. When faced with new challenges, as there is usually no single solution, he for example tries to always come up with several solutions that solve the problem with the most minimal effort needed. A helpful question to compare potential solutions may be: When looking at the problem at hand, which is the solution that covers all current requirements and is robust to changes, while having less or comparable costs to other potential solutions?
Similar guiding questions may apply to refactoring. Since architecture is a living thing that can and must evolve, refactoring is part of the process. Evaluating which parts may benefit from refactoring as the application grows, and then implementing these changes at appropriate intervals, also leads to continuous engagement with the frontend architecture.
- Shared responsibility in one large team
In larger teams with a greater number of senior developers, multiple people may act as architecture spokespersons as described above and may share the responsibility for it. They may discuss issues and potential solutions and reach decisions together, which are then shared and communicated across the entire team.
- Architecture lead team for multiple large teams
In very large projects with multiple large independent teams working on one application, each team may have at least one team member that inhabits the role of architecture lead. These people then get together regularly. For example, in the team of one of my colleagues, all architecture leads meet once per week in a scheduled time slot to discuss current topics and decide on architectural design when necessary. The structure is not very hierarchical, so the developers discuss and reach decisions on architecture rules and guidelines together. The meetings are open for everyone on the team, so anyone taking an interest in the topic is free to join at any time and bring up issues that need attention. This frequent information exchange with multiple team members is especially necessary the larger the overall team is, in order to help teams implement coherent solutions across the entire codebase.
In more hierarchical teams, depending on the team culture, there may also be one overall lead architect whose role it is to make architectural decisions in a more top-down manner.
Overall, no matter the exact team setup, to maintain good frontend architecture it is crucial that the entire team stands behind the architectural approach and is actively supporting it. Frontend architecture may easily become neglected through other daily work tasks and deadlines. When this happens, it can quickly snowball into large inconsistencies in the codebase, development inefficiencies and performance issues, leading to major refactoring tasks. Thus, maintaining good frontend architecture needs to be prioritized by the team and be “lived” by everyone. It can then play an integral part in the application’s growth.
Coding practices and tools for automated checks
In addition to all team members actively taking responsibility for maintaining good frontend architecture, certain team practices as well as the use of automated tools may provide further support in upholding architectural quality.
- Code reviews and pull requests
Code reviews by fellow developers belong to the most useful practices to maintain uniformity in the codebase. Team members review each other’s pull requests before merging into the codebase and can thereby spot problems in the new code that one may have missed during development, such as deviances from best practices. This consistency can make it easier to maintain the architecture over time.
By reviewing someone else’s code with an eye to the bigger picture, team members may also be able to identify areas of the codebase that could generally benefit from refactoring. Reworking issues early is most effective; otherwise, they may later become a lot more difficult and time-consuming to fix.
Furthermore, code reviews facilitate communication and collaboration between team members, which can lead to better results and also help build a shared mental model of the architecture.
- Frontend architecture documentation
Having early architecture decisions as well as later refactoring decisions and their rationale documented in written form can provide a common ground for everyone in the team. It can also be a valuable resource in the onboarding of new team members. Graphics and diagrams may support this. For example, a big picture diagram of the frontend architecture may visually convey the overall structure and the relationships between its parts or components.
When employing specific architectural patterns or solutions that you have seen in other projects, it may be useful for others to be able to take a look at the sources themselves. Including links to references in a Readme file can thus also be helpful.
Keeping the documentation up to date is necessary to keep up with current changes and to avoid erroneous and confusing information.
- Example components and pre-generated templates
It may come in handy to have various “go-to” components or modules that serve as particularly good examples for this type of component or module, respectively. This can help developers to maintain consistent implementations and make development more efficient. You could also create pre-generated templates for different use cases, and package them in a component or module starter code. Anything that abstracts your best practices from specific implementations may be helpful.
- Linting
Technical tools can greatly support development by providing warnings and errors when new code does not conform to the defined coding standards and best practices.
Linting tools can be configured to enforce things such as consistent naming conventions, avoiding problematic code patterns or deprecated features, and code formatting. Most relevant to frontend architecture, however, are linting rules that focus on import and export statements. Restricting module imports helps to keep modules independent from each other by for example not allowing modules from one library to be imported into the other. Import and export linting should also be used to avoid the creation of circular dependencies, which otherwise may lead to bugs.
Having as many automated checks against architectural rules as possible is advisable when the rules are well defined, transparent and easy to understand. Everyone should be able to understand these rules and know how to apply them, otherwise the automated checks can turn out to be frustrating for developers. A good amount of well-defined, automatic checks can greatly reduce the effort of manual review tasks for all developers on your team.
- Code Quality Analysis Tools
Similar to linters but with higher functionality, automated code quality analysis tools can be used to measure the quality of the code base and detect coding standard violations and bad patterns. For instance, SonarQube is a widespread tool that can track and analyze specific code metrics that are relevant to good frontend architecture, such as maintainability, complexity, and reliability. Thresholds of these metrics can be used to uphold the quality of the codebase and to identify areas of the codebase where improvements can be made. For this, pre-defined rule sets are available, but custom rules can also be defined. SonarQube also provides a range of tools for detecting security vulnerabilities, performance bottlenecks, and other potential performance issues such as unnecessary re-rendering or expensive DOM operations.
Other architecture validation and dependency visualization tools, such as Dependency Cruiser or the Nx Dependency Graph tool in Nx monorepos, also provide a visual graph that shows the dependency flow between modules in your frontend. Visualizing the flow of data in this way can help to uncover unwanted dependencies.
Many linting and code quality analysis tools can be integrated into the development workflow, such as through pre-commit hooks or continuous integration (CI) pipelines, to ensure that the code is analyzed automatically on every commit or pull request.
In summary, through helpful team practices such as code reviews, up-to-date documentation and pre-generated templates, everyone on the development team can be kept in the loop and be enabled to implement features that follow the established architecture. In addition, by configuring code quality analysis tools to focus on architecture-related issues, new code is consistently checked against your defined rule set and issues can be addressed right away. This can lead to a more maintainable, scalable and robust codebase that is less prone to problems and bugs. It can also enhance the developer experiencing by providing clear architectural principles for your frontend application.
In this last part, we will look at current trends in frontend development and ways to keep up with the latest innovations in the field. Where will the future take us as frontend architects?
Future development trends and frontend architecture
This part will deal with relevant trends in frontend development that may affect our applications’ architecture in different ways. With new tools and frameworks emerging constantly, we’ll also mention several ways how you can keep yourself up to date with current developments.
- Blurring the line between client rendering and server rendering
The classic architecture of single page applications (SPAs) with a strict split between the backend and frontend is increasingly becoming replaced with server-side rendering (SSR) and static site generation (SSG). Meta frameworks such as Next.js, Nuxt.js, and Remix have in recent years paved the way for a return to the benefits of loading content statically: rendering a page on the server and serving the generated static HTML to the browser first, and then hydrating it with JavaScript. This return to static content dissolves the strict separation between frontend and backend. It enables faster loading times of UI elements on the page, thereby enhancing performance, the user experience, and the possibilities for better Search Engine Optimization.
As further examples, the newer framework Remix for instance provides even more efficient data delivery due to fetching data on the server instead of client-side on the user’s browser. This avoids client-side loading times by sending requests immediately. In a different way but to a similar effect, the introduction of React Server Components into React has made it possible to render components and fetch the needed data on the frontend server. This removes the need of sequential data fetching in component hierarchies from the user’s browser. React Server Components can also use third-party libraries, and as they are compiled on the server, the bundle size of what is shipped to the client can be significantly reduced. Modern meta-frameworks also offer other benefits such as built-in caching, thereby further decreasing response times. For example, Next.js in conjunction with React Server Components enhances website performance through built-in caching of fetch requests and routing. For routing, the server components of visited route segments and prefetched route segments are temporarily cached in what is called the Router Cache.
- Trend toward further frontend modularization
Microfrontends
We’ve already seen the importance of modularization in part one of this series. To obtain even more flexibility and the ability to build modular, scalable, independent apps, microfrontends as an architectural pattern has risen in popularity over the last few years. Microfrontend architecture consists of a collection of small, self-contained applications that together constitute the overall application. Each microfrontend is responsible for a specific feature or functionality and may be built using its own framework or technology stack.
Benefits include greater flexibility in development, as teams can work on individual features independently, without being constrained by the overall technology choices. Since microfrontends can be deployed independently from others, they may overall speed up and simplify CI/CD pipelines. They may also prevent the growth of single page applications into large monoliths. Monolithic applications are prone to becoming less maintainable over time, as the distribution of responsibilities can become undefined and bloated as the application grows. Microfrontends may provide an architectural alternative.
However, challenges can also arise with this pattern. Splitting an application into independent teams may introduce risks on the business side, because fragmented teams may have more difficulty in communicating effectively. This can pose more challenges for maintaining coherence and consistency in the codebase, especially regarding the architecture of the frontend. Other challenges are the increased complexity in communication between the different parts of microfrontends, which makes a well-defined architecture and API contract even more necessary. In light of these drawbacks, most of my colleagues argued that what you can do with existing frameworks is sufficient for most projects in practice and that microfrontend architecture needs a well-defined use case to function effectively.
An alternative to microfrontends could be to develop apps side by side in a mono repository, where you still preserve independence between applications, but also maintain coherence in terms of implementation and team organization. As with any architectural pattern, it is important to consider the benefits and tradeoffs of microfrontends regarding various aspects of your project, including team organization and architecture development before deciding to adopt the microfrontend pattern.
Web components
Furthermore, web components are becoming increasingly popular in frontend development. Many of my colleagues argued for their various benefits and would like to see them more established as an architectural pattern and even more of a default pattern for frontends. Web components are based on existing web standards and are reusable components with their functionality encapsulated away from their environment. They can operate across different frameworks and libraries, specifically any JavaScript library or framework that works with HTML, and can thus be reused across different projects and web apps. By creating custom HTML elements that are entirely separate but operable within your web application, web components are highly modular and can be used very flexibly. This can be especially beneficial for the architecture of large and complex web applications. When using web components, it is however important to keep in mind that they must share many interfaces with their external environment, so keep an eye out for performance.
- Modern, lightweight web frameworks
Newer web frameworks incorporate features that are getting increasingly popular and that can have significant impact on how an application is built architecturally. With newer frameworks such as Svelte, SolidJS, Qwik, or Astro, we are seeing new ways of improving the user experience as well as the developer experience. They all strive to increase performance, for example by finding more efficient ways of presenting data to the user or receiving user interactions. They are usually lightweight with small bundle sizes and have very fast runtimes, through which they can far outperform big frameworks like React or Angular in runtimes and speed. Astro for example is among the frameworks that provides very fast page load performance, especially for large amounts of statically generated or server-side rendered content.
As another example, Svelte delivers optimized, compiled JavaScript code to the browser without the need for the browser to parse and compile at runtime, resulting in faster load times. It also supports reactivity, which enables improved rendering performance without the need for a virtual DOM, something that other new frameworks such as SolidJS also do. In reactive frameworks such as SolidJS, components do not need to be re-rendered when data changes. This works based on signals that the framework observes and then only updates the data at its exact location in the DOM.
Qwik on the other hand takes an approach that is based on delivering the smallest bundle size possible to the browser by serializing JavaScript into the HTML document and sending very fine-grained JavaScript modules to the browser only when they are needed upon user interaction. Instead of typical SSR hydration, Qwik thus uses progressive hydration, meaning it delays the loading and execution of JavaScript for as long as possible. Qwik also implements resumability, which loads state based on where it left off in the server without needing to re-run components.
While most of these frameworks are still less widely adopted and may have some drawbacks, less community support, or may be too lightweight on their own, they can greatly complement existing technologies for specific use cases. Their growth in popularity may provide new directions for future advances in web development.
- Design systems
As the design and branding of applications is becoming increasingly important for organizations to achieve their business goals, frontend design systems are becoming crucial drivers of design consistency within the enterprise environment. A custom design system, which is to be translated into a custom frontend style and component library, is usually shared across different applications within a company. Establishing cohesive, consistent design systems for frontend applications is thus not only a design concern, but also an important concern in frontend architecture.
While its organization and design lie in the domain of designers, it is an architectural decision how to build it up in code so that it can be efficiently shared across applications. Style and component libraries need to be well integrable into frontend applications, whether existing or planned, and should work well with the technologies used in them. It’s thus crucial to consider their architecture and interfaces as early as possible. Early communication with stakeholders is key. The libraries also need to be developed at the right time, so that the different frontend applications can use them ideally from the beginning. Overall, when integrating a design system into your frontend, it pays off to keep it easy to use and maintain for all people working with it, whether in development, design, or business.
The frontend world is fast paced. New technologies are emerging, and there is a lot of discussion going on online. I will leave you with a few ways to keep up to date with new trends in frontend development:
- Taking a look at the JavaScript Risings Stars project, which provides an overview of the trending projects on GitHub in the JavaScript eco-system over the last 12 months
- Following frontend experts and discussions on social media
- Going to conferences
- Reading the results of annual developer surveys such as State of JavaScript and State of CSS
- Reading regular blogs on sites such as Medium or feeds such as daily.dev
- Following new projects on GitHub
- Listening to web and frontend podcasts
- Following channels on Reddit such as the JavaScript channel or framework-specific ones
- Going offline and looking at your colleagues’ screens, asking them what great things they are building J
Whether it’s exploring the power of component-based architecture or adopting alternative frontend architecture patterns and technologies such as microfrontends and web components, staying informed about the latest trends and best practices is vital for frontend architects. At the end of this article series, I hope to have highlighted that frontend architecture plays a crucial role in the success of any web application. By adopting a well-thought-out and maintainable architecture, we can build scalable, efficient, and robust applications that delight users and meet business goals. The frontend is the face of your application, and investing in a thoughtful architecture will pay off greatly, setting your project up for long-term success. Thank you for reading!