Over the past few years, while working at a $1.7B GenAI startup, I’ve come across various front-end tools and techniques.
As the company grew, my team had to make various decisions on how keep a organized front-end repository where engineers across the entire globe contribute.
In this article, I’ll share my experiences with these tools and give them a rating: either “❌ Consider” or “✅ Endorse.”
❌ Consider: I’d recommend considering alternatives for your projects.
✅ Endorse: I’d reuse this in a project without hesitation.
Code Quality and Tooling
ESLint
🔍 Consider
Description: A static code analysis tool that identifies and fixes problems in JavaScript code to enforce consistent coding standards.
Positive:
ESLint helps find bugs and ensures coding standards are consistent across a distributed teams. It is extensible with various rules specifically for React.
Negative:
ESLint can be slow. In my experience, it significantly adds to the build pipeline time, even with techniques like caching and running it only on changed files.
In my daily workflow, I run ESLint as a Git hook before each commit. Even when I change just a few files, ESLint typically takes 5–15 seconds, which can be frustrating, especially since I prefer making frequent, small commits.
A potential replacement for ESLint might be biome, which is significantly faster. However, biome currently lacks support for all ESLint rules.
Git hooks
✅ Endorse
Description:
Git hooks, managed by tools like Husky or pre-commit, ensure that everyone working on the codebase executes the same actions before committing changes.
Positive:
Pre-commit Git hooks help reduce the number of broken builds when opening pull requests by enforcing consistent practices across the team. Here are some tasks you might include in your pre-commit hooks:
Format changed files
Run ESLint on changed files
Check for unimported files
Check for cyclic dependencies
Format commit messages with a prefix, such as a ticket number, so a bot can automatically link your Jira/Linear ticket.
TypeScript
✅ Endorse
Description:
TypeScript has seen a significant rise in popularity over recent years, though some, like Rich Harris (creator of Svelte), argue that it’s not always necessary.

Positive:
If you’re working on a large, distributed codebase with engineers around the globe, TypeScript is definitely worth considering. It runs faster than ESLint, provides strong confidence in the code you ship, and makes refactoring easier.
Negative:
TypeScript can add some additional build time to your CI pipeline since you need to run tsc
.
It can also be tedious, particularly when dealing with packages that have complex types, like react-hook-form. In such cases, I’ve often found myself debugging TypeScript-specific issues that wouldn’t exist in a JavaScript-only project.
Unimported / knip
✅ Endorse
Description:
Tools like Unimported (no longer maintained) and knip help identify unused files in your codebase.
Positive:
When cleaning up your code, it’s easy to overlook unused files. These tools help you detect files that can be safely deleted, keeping your codebase clean and efficient.
While writing this article, I noticed that Unimported is no longer maintained. Although I haven’t personally worked with knip, it seems worth the effort to migrate from Unimported to knip and explore its additional capabilities.
Build and Package Management
Vite
✅ Endorse
Description:
A next-generation front-end build tool that provides fast development and optimized production builds.
Positive:
Switching from Babel to Vite can dramatically boost your productivity. In my experience, build times decreased from 5 minutes to just 40 seconds.
Additionally, Vite’s ‘Instant Server Start’ feature launches your local development server immediately, saving valuable time every day
Lerna
✅ Endorse
Description:
Lerna simplifies the management of JavaScript monorepos.
Positive:
I’ve had a smooth experience with Lerna so far. It’s a solid, reliable tool for managing JavaScript monorepos without any major issues.
Protocol buffers
✅ Endorse
Description: A language-neutral, platform-neutral, extensible mechanism for serializing structured data, used to define and exchange data between distributed systems efficiently.
Positive:
Protocol Buffers are an excellent tool for aligning distributed teams on API standards, especially in large codebases with dozens of engineers. They simplify the process of building complex applications by providing a consistent and efficient data format.
Negative:
However, Protocol Buffers can be complex to set up and come with a learning curve that might be challenging for those unfamiliar with the system.
Side Note:
Have you ever been curious about how code generation tools like Protocol Buffers actually work? I had the chance to explore this in-depth during a Google Summer of Code project, where I contributed to a Google tooling framework that’s now approaching 300 stars on GitHub.
Testing
Unit Tests with Jest
❌ Consider
Description: A JavaScript testing framework.
Positive:
Unit tests that focus on the business logic of your application are great for catching bugs. For instance, transforming data from the backend or constructing complex API requests from various objects are scenarios where unit tests shine.
We significantly reduced Jest’s unit test run time from 4 minutes to about 1 minute by using SWC.
Alternatively, using Vitest could also be a good option for improving test speed.
Negative:
I’m not a big fan of writing unit tests for React components, especially for simple interactions like clicking a button to open a popover. These tests often require significant time to implement and maintain, yet they provide minimal value in return.
Playwright
✅ Endorse
Description:
Playwrights popularity over the last years increased a lot which gives me confidence that Playwright is the correct choice for E2E.

Positive:
In my role, I was responsible for building our E2E testing infrastructure, and I chose Playwright as the framework. My experience has been overwhelmingly positive. I recommend focusing on a few comprehensive E2E tests that cover the most critical features and follows the path of the user, rather than implementing small unit tests for each component.
We’ve set up a system where relevant E2E tests are triggered on a pull request, and in case of failure, the engineer is linked directly to the Trace Viewer to analyze the issue.
Negative:
Maintaining E2E tests can be a bit tedious. Initially, we had developers write E2E tests alongside new features, but we’ve realized the need for a dedicated QA team to maintain existing tests and create new ones as the codebase evolves.
UI Frameworks and Libraries
Mantine
✅ Endorse
Description:
Mantine is a fully open-source React component library.
Positive:
Mantine offers an exceptional set of components that are easy to understand, adapt, and integrate into projects. The library works flawlessly, and the lead developer is very active on Discord, providing strong community support. I haven’t encountered any issues with Mantine in my experience.
I can highly recommend it.
Highcharts
❌ Consider
Description:
A comprehensive charting library that allows you to create a wide range of interactive visualizations.
Positive:
Highcharts has a well-documented API that allows you to build complex charts covering a wide range of use cases.
Negative:
However, Highcharts is a paid library, and in many cases, free alternatives like Chart.js are sufficient for most use cases.
Another drawback is that their TypeScript types and interfaces are consolidated into one massive file, which can slow down your IDE significantly. As a result, I often had to rely on their online documentation instead.
Additionally, for one specific use case, Highcharts didn’t meet my needs, and I had to resort to using D3 to build the required chart.
D3
❌ Consider
Description: A flexible JavaScript library for creating complex, custom data visualizations.
Positive:
D3 is invaluable when building complex hierarchical charts that can’t be handled by simpler libraries. It’s highly flexible and has extensive documentation with numerous examples to guide you.
Negative:
However, D3 comes with a very steep learning curve. I only recommend using it if other high-level chart libraries can’t achieve what you need.
You’ll often find yourself relying heavily on tutorials and examples from the documentation, as the library’s complexity is very high.
dnd kit
✅ Endorse
Description:
dnd kit is a library for implementing drag-and-drop functionality.
Positive:
I’ve found dnd kit to be great for building complex, nested forms where users can drag and reposition form elements. It has served me well in these scenarios.
State Management
TanStack Query
✅ Endorse
Description:
TanStack Query is a powerful library for managing API calls and server-state in React applications.
Positive:
I’ve had nothing but positive experiences with TanStack Query. It simplifies data fetching, caching, and synchronization, making it much easier than implementing your own fetching logic. This is, without a doubt, one of the most important libraries every front-end project should include.
TanStack Table
✅ Endorse
Description:
TanStack Table is a headless UI library for building powerful and customizable tables.
Positive:
My experience with TanStack Table has been entirely positive. It provides the flexibility to create robust table components tailored to specific needs.
Zustand
✅ Endorse
Description:
Zustand is a simple and scalable state management library for React applications.
Positive:
Zustand is easier to use and scale compared to React Context, making it an good choice for managing state in React projects.
React hook form
❌ Consider
Description:
React Hook Form is a popular library for building and managing forms in React applications.
Positive:
The library works well and has a large GitHub community, offering the necessary features to build complex forms.
Negative:
I’ve encountered issues when using it with TypeScript, particularly with maintaining type safety in complex forms. Dealing with recursive forms and their respective types has been tricky too.
Alternatives like TanStack Form or Mantine Form might offer fewer TypeScript-related issues.
Utility Libraries
Lodash
✅ Endorse
Description:
Lodash makes code cleaner and easier to read with its handy functions.
Positive:
Lodash functions help make your code more concise and readable. Some of my go-to functions include groupBy
, cloneDeep
, and a few others.
When I feel my code could be cleaner, I often ask GPT to refactor it using Lodash functions, and the results are usually an improvement.
Madge
✅ Endorse
Description:
Madge is a tool for detecting and preventing cyclic dependencies in your codebase.
Positive:
Unit tests written in Jest can be fragile, especially when cyclic dependencies exist in your repository, causing unexpected failures. By integrating Madge, you can avoid these issues. Madge is also fast, so it doesn’t add significant time to your workflow.
Negative:
Avoiding cyclic dependencies may require refactoring many files to different locations. When starting a new feature, it’s important to be mindful of where you place your files and how they’re referenced. If you don’t consider this upfront, you might end up reorganizing your entire file structure later.
Less
✅ Endorse
Description:
Less is a CSS preprocessor that helps make styling files more maintainable and easier to understand.
Positive:
Less can make your styling files easier to read and manage, especially when working with complex styles.
Negative:
Not all Less syntax is fully supported by IDEs, meaning that actions like “Go to declaration” (e.g., CMD + B) don’t always work.
I’ve found it more efficient to style DOM elements directly within JSX using a library like Mantine, rather than switching between files and dealing with CSS class names. This approach allows for quicker comprehension of the layout and reduces the need for external styling files.
For example, I prefer this approach:
// Using a flexbox with padding, margin-bottom and background in Mantine
const BoxExample = () => {
return (
<Flex p="1rem" mb="1rem" bg="white" justify="center">
Styled with Mantine Flex
</Box>
);
};
Over this:
// styles.less
.styled-div {
padding: 1rem
background-color: white
margin-bottom: 1rem
display: flex
justify-conent: center
}
const LessExample = () => {
return <div className="styled-div">Styled with LESS</div>;
};
So, my usual approach is to style as much as possible with the provided attributes from Mantine (e.g., p
, mb
, etc.) and fall back to CSS or Less only when necessary.
Learn more: