Building A Solid Frontend Application Stack That Works For You
There are countless ways to design a new frontend application. You have to consider the product requirements, non-functional requirements, and assumptions based on your previous experience. You also need to dig deep and ask tough questions so you would not be surprised later down the road. In this post, I share the result of the process we did, and the decisions we took for the product we are currently developing, an interactive and extendible video editor.
I'm currently working on creating an interactive video editor application and needed to design the project based on the following main product requirements:
- Provide a single page application (a.k.a. SPA) that contains hundreds of components.
- Use an existing server that is owned by another team and exposes Restful Web Services (REST).
- Write the user interface components library (UI-Kit) separately so we can use it in other products as well.
The following diagram shows the connection between the product sources, hosted in GitHub, and the different applications resulted from them, as various stakeholders use them. It might help you next time you are designing your product.
The UI-Kit repository
We are using Lerna to manage the repository; Lerna is a toolchain that handles the complexity of managing multiple libraries and later deploying them to NPM. Every time we commit changes to the master branch, GitHub Actions, our Continuous Integration and Deployment service (CI/CD), automatically execute "Lerna publish flow" to generate and release a new version into NPM. During the process, it uses the Conventional Commits methodology to decide on the next version and also to update the changelog document with the new version content grouped by bugs, features, and breaking changes.
During development, we are using the Storybook framework to create a real-time development workspace. That workspace is a controlled environment that allows creating the components in isolation with mock data and simulates all use-cases and edge cases! As a bonus, Storybook provides fantastic support for generating the documentation site based on the code already written during the development. No extra code is needed if you are following some Storybook basic guidelines. A documentation site available on the cloud means that the developer on your team has a place to explore the available components, their APIs, and integration code samples.
The documentation site and the real-time workspace are based on mock data, which makes them ideal for the QA personnel to run their automated (with Cypress) and manual tests. A documentation site also helps the product manager and the designer since it acts as a showcase of the components used in the application.
We use a single development workspace (Storybook application) and a single automation application (Cypress application) for all the libraries of this repository since they are all part the same UI-kit.
The Application Repository
The Monorepo repository application is also written in React. Although not deployed to NPM, it is still written as a group of libraries used internally by the application. Splitting the code into libraries is great for code reuse and maintenance. For that, we are using the NRWL Nx tool, an open-source library, as the repository toolchain.
As in the UI-kit repository, we are using Storybook to have a real-time development workspace where the developer creates the components before the she/he integrates them in the application. We follow the "Component first approach" as it plays nicely with React philosophy and modern development concepts. Having a real-time development workspace with mock data helps create reliable components with mature API that are easy to compose and reuse across the application. Once we are satisfied with the behavior of the component in the workspace, we integrate them into the application. A QA personnel prioritize testing in the workshop as it simulate all use-cases including edge-cases using mock data. She/he performs tests in the application only for use-cases that cannot be tested in the workspace or tests that are not available in the workspace like integration with the server API or between component views.
We use GitHub Actions (our CI/CD service) to release new versions automatically once we commit changes to the master branch. The release process uses Conventional Commits together with Standard Version to resolve the next version, create a release in Github and add to the changelog based on the commit messages.
We have another separate CI/CD service named Vercel (previously Zeit) that builds a standalone version of all our products for every Pull Request (PR) and commit we have. This is a powerful feature that enables us to see the changes of the PR during code review on a pre-deployed cloud version. When we commit changes to A PR or to the master branch, Vercel creates and publicly hosts three different sites for every version: a documentation site, a real-time workspace, and our application. With paid subscriptions, we also have access to unlimited past deployment, which means that we can access any released version even months after it was first deployed. Since our application assumes clients are hosting it in an iframe HTML element that provides configuration using window.postMessage(), we deploy a fourth application that is a React application that simulates user login and hosts our real application inside an iframe.
We use Reviewable, whose slogan is "GitHub Code Reviews Done Right". I feel this post is not complete if I don't mention this service. I will dedicate a separate post discussing concrete reasons why all (well almost all) the developers must use Reviewable if they are serious about code reviews.
We use a single development workspace (Storybook application) and a single automation application (Cypress application) for all libraries in the repository as-well. I mention it again because this is in contrast to the philosophy and best practice of the NRWL Nx toolchain. We do that because we are not going to develop applications of different products in the same repository, something that is supported by NRWL workspace. In our implementation, all the codebase in the repository belongs to the same product. So since we have only applications that belong to the same product, we should also have a single development workspace and single cypress test application.
We created a dedicated standalone playground application that lets us run the editor application in isolation. It empowers the development phase with essential integrations and use-cases usually not available in the production application. A few features are sending a shareable link with predefined configuration to another teammate (let it be a developer, QA personal, product manager), integration with other independent products, and execution of power user actions usually done manually. This standalone application is being deployed alongside the editor using our CI/CD into a staging environment.
The Chosen Frontend Technology Stack
The list below is a short list with links to the chosen technology stack. I will review the reasons for some of the chosen libraries in future posts.
- Frontend framework: React
- State management: Redux (with Redux-Toolkit, Redux-Observable, Redux-Views)
- Component styling: CSS in JS
- UI-Kit: Ant Design
- Drag & drop: React-DND
- Animation: Framer-Motion
- Automation: Cypress
- Asynchronous Flows: RxJS
- Complex user interaction area: SVG Elements (
In this post, I reviewed the decisions we did when we designed one of the projects at my workplace. The sources are not publicly available, so I cannot provide a link to review it, but I will provide code samples and demo repository in future posts. In the meantime, feel free to ask questions.