Frontend Technology Selection: Tradeoffs Over Best Tools
Choosing a tech stack for a new product is one of the most consequential decisions an engineering team makes. Not because the tools matter that much individually — but because the combination determines how fast the team can move for the next two to three years.
When we started building our travel booking app, we had a small team, an ambitious timeline, and one clear priority: ship fast without creating debt that would slow us down later.
Selection Philosophy
Before picking any tool, we defined what mattered most. The criteria were simple:
- Team knowledge — can the team be productive from day one?
- Development speed — does the tool reduce friction or add it?
- Maintainability — will this be easy to change in six months?
- Ecosystem maturity — are there enough resources, plugins, and community support?
- Bundle size — for a consumer app, performance and SEO are not optional.
The most important criterion was team knowledge. A technically superior tool that nobody knows how to use is worse than a familiar tool with known limitations. Familiarity beats novelty when speed matters.
React + Next.js + TypeScript
The decision to use React with TypeScript was driven by the team’s existing experience. Most of our engineers already knew React. TypeScript added type safety without slowing anyone down — the investment pays back immediately in fewer runtime bugs and better editor support.
We chose Next.js as the framework. At the time, it was the most mature React framework with built-in SSR, routing, and API routes. Staying close to framework defaults was important. The less custom infrastructure, the faster the team moves.
Our backend also uses TypeScript. Having one language across the entire stack — frontend, backend, and shared packages — reduces context switching. Engineers can contribute to any part of the system. Shared types between frontend and backend prevent contract mismatches. A unified language stack is an underrated productivity multiplier.
GraphQL + React-Query
We use GraphQL for our public API. For a consumer app with many different views, GraphQL is a natural fit. Each page fetches exactly the data it needs — no over-fetching, no under-fetching. The schema serves as documentation and a contract between frontend and backend.
Combined with TypeScript and graphql-code-generator, we get type-safe queries and mutations for free. The generated types flow from the GraphQL schema to the frontend components. Schema changes break the build, not the user experience.
For data fetching on the frontend, we chose react-query + graphql-request instead of Apollo Client. Apollo is powerful but heavy. It brings its own normalized cache, state management, and opinions about how data should flow. For our use case, that was too much.
React-Query is simpler. It handles caching, background refetching, and stale-while-revalidate patterns — but it stays out of the way. Combined with graphql-request as a lightweight GraphQL client, the setup is minimal. Pick the tool that solves your actual problem, not the one that solves every possible problem.
Styling: Choosing the Safest Option
Styling was the hardest decision. We evaluated six options: CSS Modules, Tailwind CSS, CSS Modules + Tailwind, Styled-Components, Chakra-UI, and Vanilla-Extract.
We built prototypes with each option and scored them on five criteria: maintainability, development speed, readability, future-proofing, and bundle size.
Three finalists made the cut:
Tailwind CSS had the best development speed and a large community. But readability suffered in complex components. Long utility class strings are fast to write but hard to review.
Vanilla-Extract was the most elegant — type-safe CSS with zero runtime cost. But in 2021, the community was small and few production projects used it. Betting on an unproven library felt risky for a product we needed to ship quickly.
CSS Modules was the safest choice. It is just CSS. It works out of the box with Next.js. No runtime cost. No new syntax to learn. The downside: it requires internal conventions to keep styles consistent. Without rules, the flexibility becomes a maintenance problem.
We chose CSS Modules. Not because it was the best option in absolute terms — but because it had the lowest risk and the team could be productive immediately.
What This Taught Us
Technology selection for a new product is not about finding the best tools. It is about finding the right tradeoffs for the team, the timeline, and the product.
The best practice I have found is to optimize for team productivity first and technical excellence second. A great tool that the team struggles with is worse than a good tool that everyone knows. Start with what reduces friction. Swap later when the tradeoffs shift.
Every choice we made followed this principle: React because the team knew it, Next.js because it reduced infrastructure decisions, GraphQL because it solved a real frontend problem, React-Query because it was simpler than the alternative, CSS Modules because it was safe.
None of these were the most exciting options. All of them let us ship a product on time.