Frontend code can get really complex really quickly. This comes from how we naturally tend to build webpages and how we tend to write CSS. Web Pages tend to be built from the top down where we define and style the page, then sections we want to work on, then the individual pieces of that section and so on. It’s easy to forgo good engineering practices and then end up building highly coupled components.
How can we deal with this in our frontend code and keep it more resilient? We can make strong architectural decisions early on in the project that benefit us as the project evolves.
- Benefits of thinking about Architecture early
- Tools and methodologies I found beneficial
- Loose Coupling
- Component Driven Design
- Atomic Design
- Tailwind CSS
Benefits of thinking about Architecture early
In most codebases the primary reason for the codebase becoming difficult to work with is the haphazard nature of writing frontend code. You start a project with the intention of solving a problem or getting a design on the page. You spend most of your time focusing on what renders in the browser that it’s very easy to forget to manage the codebase as well. I’ve seen greenfield projects become difficult to work on within a matter of a few weeks.
CSS also has the ability to add to significant coupling of our components by unintentionally applying CSS rules to components through poor selectors. Naming and selecting components is extremely important when writing good CSS. It’s very easy to write poor selectors that work for your particular use case and then when needing to extend your frontend code and its CSS, you find you have to code around your previous selectors. This quickly devolves into CSS spaghetti and you now spend more time trying to properly select your components in CSS than writing code that adds value. Even with component-based frameworks like React and Vue, this is how frontend codebases tend to evolve without proper care.
These types of problems can be avoided by making architectural decisions about your frontend code early in the project and sticking to them throughout its lifespan. Here’s an example of a project that made architectural decisions early on:
Quite early on a recent project I worked on, we started having discussions of the architecture of the frontend codebase. We decided to go with Atomic Design for how we created components and eventually we’d start to practice Component-Driven Development when writing components, building components in isolation using Storybook before putting them on the page. Within a month after setting up all the necessary infrastructure and getting used to our development process, development moved extremely quickly as we were leveraging the previously built components in our design system to move forward.
We eventually took on more work than we could really handle and we needed to bring on someone for help. This is generally a no-no in project management as it usually takes new people a long time to get ramped up and be productive on a project. In this circumstance though, the ramp up time for bringing on the new person who extremely small for two reasons:
- The new person needed to build new frontend components but we built components in isolation before putting them on the page so they didn’t need to know the codebase well to add to it.
- Because of Atomic Design and building components using Storybook, the components remained relatively isolated even in production and so even when putting them on the page, you didn’t need to interact with the rest of the codebase outside of the work you needed to do.
A few other people had contributed to the codebase as well with similar results, some of which were much junior in their coding experience but were able to contribute because of how the architecture made the codebase very approachable.
Tools and methodologies that I found beneficial
So I’d like to give you some concrete tools and ideas you can use to accomplish this. Here are some of the things that allowed the codebase in my story to be as resilient as it was:
A major architectural concept in software engineering. Loose coupling is a concept where the dependencies between different components are low. The benefit of this is if there is a change that needs to be made in a component, few other components would need to be changed as well. This helps isolate your components from each other, allowing changes to components as they need to be expanded upon to be much more frictionless. This also allows you to work on them in parallel without worrying about the implementation details of another developer’s work.
Every other concept or tool that I talk about will contribute to this and is the main philosophy why they were so effective.
Component Driven Design
An important frontend development concept. Frontend development tends to be top-down where we define the page first and then the elements within it. Component-Driven Design turns that idea on its head by focusing on working on the components (starting with the smallest) first and then putting the page together as the final step. This works particularly well because the smallest component such as buttons and text tends to get repeated when building top down. Building those first allows you to use them throughout the application without redefining them.
Where loose coupling is a rule we follow in architecture to keep our codebase resilient, Component-Driven Design helps enforce that rule as developing this way naturally leads to less coupled components.
Atomic Design is a component design pattern by Brad Frost defined in the early 2010s. The concept is each component falls into the categories of atoms, the smallest components, molecules, made up of multiple atoms, organisms, made up of atoms and molecules and pages which are made up of the 3 previously described categories.
Where Component-Driven Design tells us how we construct pages, Atomic Design tells us how components relate to one another.
This and Component-Driven Design are what we will mainly use to keep our frontend codebase loosely coupled,making it extremely resilient.
Storybook is a tool used to document Design Systems, each instance of a frontend component and the varying states it can be in being called stories. Storybook focuses mainly on the visual aspect of frontend components, allowing you to see how components are rendered and see their different states and what actions they have.
The three previous concepts we talk about, the frontend code itself and other tools necessary to run that code will come together to form a Design System which can be documented in Storybook. While documenting the Design System is important and should be done, the power of Storybook for our purposes is that you can develop components in isolation of the rest of the system. This helps enforce the loose coupling throughout the codebase by forcing Developers to put together components that do not need to rely on the rest of the system to run. The second benefit is you are automatically documenting, at minimum, a new component in the Design System as you develop new components.
TypeScript does a great job of eliminating that particular issue by enforcing what a component expects when properly typed. Secondly, TypeScript also doubles as the first layer of documentation for your components. When leveraging your component, TypeScript will tell your IDE what your component expects which makes using components significantly easier.
Tailwind CSS is a library that allows you to add prebuilt CSS rules by adding Tailwind classes to your markup. With this you no longer have to write CSS yourself, you just use a combination of Tailwind classes to get the styling you want.
Tailwind is likely the most controversial topic here but allow me to explain. CSS, when written poorly, tends to lead to more tightly coupled components in unexpected ways. It is also quite easy to write CSS poorly and to have that coupling make itself known when you least expect it. This is mainly through the selection of HTML elements to add styling rules to. Tailwind effectively eliminates that by forcing a Developer to leverage predetermined styling rules which are applied by adding classes. Because of this, there is rarely any need to attempt to select HTML elements to apply styling and thus you don’t write poorly written CSS selectors.
Additionally, this now keeps all your display logic in a single file, leading to high cohesion, where related items are packaged together which in this case would be the display logic.
*Note: If you rather not use Tailwind there are two alternatives that come to mind in terms of this idea. The first is leveraging Styled Components. You get to write CSS rules but you still avoid writing CSS Selectors. This I feel is an acceptable middleground. The second is simply learning to write better CSS and in turn writing better CSS Selectors.
One other thing I want to briefly touch on is keeping your display logic and business logic separate. My experience is mainly with React but how this manifested in my work was putting as much of the business logic in React Hooks and separate utils as possible. I’d also try to integrate hooks into the highest level component that needed the data. Such as if the highest level component was an organism, then the hook would output the data there and the organism would be responsible for distributing the data to the relevant components. While important, I feel this deserves its own article to expand on this further.
I have to openly admit that I stumbled across this entirely by accident. All I knew was the pain I suffered throughout previous codebases I worked in and these ideas I came across throughout my Front End Engineering career. Even then, I accidentally implemented them in one project and was self-aware enough to notice these benefits, only really defining how they worked together in writing this article. While all the tools described above will lead to a better codebase and a better developer experience, the main thing I want Engineers to take away from this is the concept of loosely coupled components and directly enforcing that through the choice of tools and development practices you decide to implement. I’m sure there are other ways to achieve this with tools and techniques I haven’t come across.
This article is inspired from the experience I wrote about here: Built a full feature in 6 hours: My experience on how the right tooling can improve your development velocity
Two talks I watched really help inform me on some of the concepts I speak about here:
- Frontend Architecture: Front-End Architecture 101 - Nir Kaufman @ ReactNYC
- Learn to build resilient Front-End Architecture: Learn how to build resilient frontend architecture with Monica Lent - GOTO: Development talks
- If you found this article interesting, please feel free to heart this article!
- If you’re interested in learning more about Front-End Engineering, follow me here on Dev.to and Twitter.
- If you’re looking for jobs, I’d highly recommend checking out @TechIsHiring on Twitter or LinkedIn for posted jobs and other resources!
Top comments (1)
Great article, Chad! My only comment would be if you have an accessible design system that covers everything (components, etc.) then you are setting yourself up for success the rest of the way.