DEV Community

Dwarves Foundation
Dwarves Foundation

Posted on

Why We Chose Our Tech Stack: Accelerating Development with a Robust Frontend Solution

At Dwarves Foundation, we pride ourselves on being a team of seasoned technology experts, passionate about crafting innovative solutions for our clients. With years of experience in the technology industry, we understand the importance of selecting the right tools and technologies to deliver exceptional results. In today's fast-paced world, staying ahead of the competition requires building and deploying features quickly without sacrificing quality or stability. When selecting a technology for our frontend tech stack, the foremost question we ask is: "Has this technology achieved a certain level of stability and maturity?" Quick wins are important, but truly transformative products, teams, and infrastructures require years of sustained effort. In this article, we'll discuss the key components of our tech stack and explain how they contribute to our ability to develop high-quality, scalable applications at lightning-fast speeds.

Basic building blocks

React

With a decade of evolution under its belt, React has proven itself as a stable, high-performance, and user-friendly frontend framework. It strikes the perfect balance of stability, performance, and usability, earning its place as our go-to choice for the frontend foundation. Although React has a steeper learning curve compared to some other frontend frameworks, its vast ecosystem, extensive documentation, and strong community support make it a worthwhile investment for long-term projects.

NextJS

When it comes to scaling production-ready React applications, Next.js is our top pick. This well-rounded framework delivers an unbeatable developer experience, complete with essential features like hybrid static and server rendering, TypeScript support, intelligent bundling, and route pre-fetching. One potential limitation of Next.js is the added complexity it introduces compared to a standard React application. However, we find that the benefits of improved performance, scalability, and developer experience more than justify this trade-off, and the Next.js community provides excellent support and resources to help developers overcome any challenges.

React Context

We don't advocate for any specific state management library. React Context API simplifies data transfer through the component tree without resorting to manual prop drilling. Most applications don't require complex global state management, and React Context is more than sufficient for tackling simpler problems. Our philosophy for React state management is to keep state as local as possible and utilize React Context when prop drilling becomes unwieldy. Some developers might consider React Context less powerful than other state management libraries like Redux or MobX, but for many projects, the simplicity and native integration of React Context prove to be advantageous. By avoiding unnecessary complexity, we can focus on delivering efficient and maintainable applications.

TypeScript

Integrating TypeScript into our codebase offers numerous benefits for developing medium to large-scale applications. By using TypeScript, we can identify bugs at compile-time, code with confidence through features such as auto-completion, definition jumping, and source documentation, and synchronize API interfaces between backend and frontend using Swagger JSON documentation. Furthermore, TypeScript streamlines the refactoring and renaming processes, ultimately enhancing our development workflow and the quality of the applications we deliver.

We recognize that TypeScript might initially seem daunting for developers who are more familiar with JavaScript. However, we've found that by providing comprehensive onboarding materials and ongoing support, our team can quickly adapt to TypeScript and leverage its benefits to ensure a more robust and maintainable codebase. Additionally, TypeScript's compatibility with JavaScript means that we can gradually migrate portions of our codebase, reducing the risk and impact of transitioning to a new language.

SWR

SWR functions as our backend data caching layer, ensuring a responsive and dynamic UI. By presenting cached data first (stale), revalidating with a fetch request, and ultimately updating with current data, our UI remains lively and up-to-date. One potential drawback of SWR is the possibility of over-fetching or under-fetching data in some scenarios, which may lead to performance issues.

To address these concerns, we fine-tune SWR configurations to optimize data fetching strategies based on the specific requirements of each application. This approach ensures optimal performance and data freshness while minimizing any potential drawbacks of SWR integration.

React Hook Forms

We endorse React Hook Forms for form management, thanks to its exceptional balance of performance and developer experience. While React Hook Forms may not be as feature-rich as some other form libraries like Formik, its focus on simplicity and performance ensures that we can create efficient and maintainable forms for most use cases. When additional functionality is needed, React Hook Forms can be easily extended with custom components or third-party libraries, providing a flexible and adaptable solution for form management.

TailwindCSS

TailwindCSS resolves common CSS frustrations and accelerates development for developers of all skill levels. Key benefits include:

  • Consistency: utility classes adhere to system constraints, preventing arbitrary values
  • Simplified naming: no need for complex namespacing techniques like BEM
  • Lean production build: automatically removes unused CSS for optimized bundles
  • Mobile-first: apply utilities easily at specific breakpoints
  • Customization: JIT and tailwind.config.js allow extensive personalization

Although TailwindCSS may initially appear verbose and lead to larger HTML files, the framework's automatic removal of unused CSS in production builds ensures that the final bundle remains lean and performant. Additionally, the utility-first approach quickly becomes intuitive, leading to faster development and easier maintenance.

Architecture

Our frontend applications rely on a thoughtfully designed, multi-layered architecture that guarantees production-readiness and exceptional results. Each layer plays a critical role in the overall performance and scalability of the application.

architecture

Service Connector: Fetch API

The service connector layer is responsible for handling communication between the frontend and backend services. We utilize the Fetch API for all our API calls. By integrating the Fetch API with SWR, we can efficiently manage API requests, caching, and error handling. This combination ensures a fast and reactive user experience while maintaining a clean and organized codebase.

State Management: React Context and SWR

In the state management layer, React Context is used for managing local state, while SWR handles global state and caching. This combination allows us to manage state effectively at different levels of the application, ensuring that components have access to the data they need without unnecessary prop drilling. The use of React Context and SWR enables smooth data flow and seamless updates across components, resulting in a highly reactive and efficient user interface.

Logic: React Hooks

The logic layer encapsulates the application's business logic and separates it from UI components. By using React Hooks, we can create reusable and composable logic that can be easily integrated into our components. Custom hooks help abstract complex logic and manage side effects, promoting code reusability and maintainability while ensuring a clear separation of concerns. This approach allows developers to focus on specific parts of the application logic, making it easier to understand, test, and debug.

UI: TailwindCSS and HeadlessUI

In the UI layer, we combine TailwindCSS and HeadlessUI to create visually appealing and accessible user interfaces. TailwindCSS streamlines the design process with utility classes, responsive design, and customization options, while HeadlessUI provides fully accessible, unstyled UI components that integrate seamlessly with TailwindCSS.

It's worth noting that while popular libraries like Ant Design and Material-UI offer comprehensive, ready-to-use UI components, they may impose constraints on design flexibility and sometimes require additional customization efforts to match the desired look and feel of an application. In contrast, TailwindCSS and HeadlessUI provide a more flexible and lightweight approach to styling and building UI components.

One potential drawback of using TailwindCSS and HeadlessUI is the need to assemble and style components from scratch, which may seem time-consuming initially. However, by investing in creating reusable and customizable components tailored to our design requirements, we ensure a more consistent and maintainable codebase. Additionally, this approach allows us to retain full control over the appearance and behavior of our components, avoiding the need to override default styles provided by pre-built component libraries.

Prioritizing Testing

A critical aspect of developing robust and reliable applications is implementing thorough testing. We understand the importance of a comprehensive testing strategy that covers every aspect of our frontend applications, from unit tests to integration tests and end-to-end tests.

Jest

Jest is our preferred testing framework for writing and running JavaScript tests. This feature-rich framework offers a straightforward setup, clear and concise error messages, and fast execution. However, Jest's performance can sometimes degrade when testing large applications with a high number of tests. To mitigate this issue, we make use of Jest's built-in support for test parallelization and selective test runs, which allow us to execute tests more efficiently and reduce overall test execution time.

React Testing Library

When testing React components, we utilize the React Testing Library. This library encourages best practices by focusing on testing components based on how they are used by the end-user, rather than testing implementation details. With React Testing Library, our tests are more resilient to changes in the codebase, reducing the maintenance burden and ensuring that our applications function as intended from a user's perspective.

Cypress

For end-to-end testing, we rely on Cypress, a powerful and user-friendly testing framework designed specifically for modern web applications. While Cypress excels in many areas, it currently supports only the Chromium-based browsers for end-to-end testing. However, given that the majority of users utilize Chromium-based browsers, and since Cypress tests closely simulate real-world user interactions, we believe that this limitation does not significantly impact our ability to deliver high-quality applications. Cypress enables us to write reliable, easy-to-debug tests that run directly in the browser, closely simulating real-world user interactions. This allows us to identify and resolve issues that might not be caught by unit and integration tests, ensuring a seamless and bug-free user experience.

Conclusion

Our meticulously selected frontend tech stack enables Dwarves Foundation to keep pace with the technology industry's rapid advancements and the growing demand for cutting-edge frontend solutions. By harnessing the strengths of React, Next.js, React Context, TypeScript, SWR, React Hook Forms, TailwindCSS, and HeadlessUI, we can develop high-quality, scalable applications with remarkable speed. These technologies, combined with our multi-layered architecture and commitment to comprehensive testing with Jest, React Testing Library, and Cypress, allow us to build and deploy features quickly without sacrificing quality or stability.

Ultimately, our robust tech stack, architecture, and testing practices contribute to our ability to stay ahead of the competition and deliver exceptional user experiences that meet the dynamic and challenging demands of today's technology landscape.

If you're seeking a technology partner with expertise in creating scalable, high-performance frontend solutions, we invite you to reach out to the Dwarves Foundation team for a consultation. We're eager to learn about your unique challenges and explore how we can help you succeed. To get in touch and learn more about our services, please visit our contact page at https://dwarves.foundation/contact/. Let's work together to bring your vision to life!

Follow us on

Website: https://dwarves.foundation
Discord: https://discord.gg/dwarvesv
Fanpage: https://www.facebook.com/dwarvesf
LinkedIn: https://www.linkedin.com/company/dwarvesf
Substack: https://note.d.foundation/

Top comments (2)

Collapse
 
dshung1997 profile image
Sy Hung Doan

I was skeptical about your usage of React Context. Most of the projects I worked on are web-apps. They usually have a big chunk of local states to work with, so we just opt for Redux. First of all, it's easy to use. One single provider, consume everywhere. And I am freed from redudant re-renders if states change.

React Context to me is quite limited, which made me think your apps aren't complicated if you're happy with React Context. I have a feeling that you're probably websites with some states. I don't know exactly... Please correct me if I'm wrong

Collapse
 
nosnart profile image
Son Tran • Edited

I agree about the usage of React Context. Currently, my preference would be to utilize composition components first, and only consider using Context If necessary. As of now If I need to use Context API I would utilizing the 'use-tracked' or 'use-context-selector' package to prevent unnecessary cascading rendering.