-
Framework: Next.js vs Vite vs Remix vs TanStack Start
- Next.js: choose for SSR/SSG, SEO-critical apps, or when you want a batteries-included full-stack setup with App Router and React Server Components.
- Vite: choose for pure SPAs, internal tools, or dashboards where fast dev HMR matters more than SSR.
- Remix: choose when you want web-fundamentals-first nested routing, loaders/actions, and progressive enhancement baked in.
- TanStack Start: choose when you want type-safe routing with SSR but more flexibility than Next.js.
-
Rendering Strategy: SSR vs SSG vs CSR vs ISR
- SSR: public-facing pages with per-request personalization or frequently changing data.
- SSG: marketing sites, docs, or blogs where content rarely changes and you want cheap CDN hosting.
- CSR: authenticated app shells behind a login where SEO doesn't matter.
- ISR: hybrid sites with mostly static content but occasional background revalidation (e.g., ecommerce catalogs).
-
Language: TypeScript vs JavaScript
- TypeScript: default for any team or non-trivial app; catches bugs at compile time and makes refactors safer.
- JavaScript: only for tiny prototypes, throwaway demos, or when contributors strictly lack TS familiarity.
-
Package Manager: pnpm vs npm vs yarn vs bun
- pnpm: monorepos and projects sensitive to disk space and install speed, thanks to its content-addressable store.
- npm: zero-config, ships with Node, maximum compatibility with tutorials and CI examples.
- yarn: existing yarn berry (PnP) setups or teams already bought into yarn workspaces.
- bun: greenfield projects prioritizing raw install/run speed and willing to accept a younger ecosystem.
-
Repo Structure: Monorepo (Turborepo/Nx) vs Polyrepo
- Monorepo: multiple apps sharing UI components, types, or utils (web + mobile + admin dashboard).
- Polyrepo: single app, small team, or strict ownership boundaries between services.
-
Routing: File-based vs React Router vs TanStack Router
- File-based (Next/TanStack): convention over configuration; routes map to filesystem, great for standardization.
- React Router: SPAs where you want full programmatic control and a mature, familiar API.
- TanStack Router: you need fully type-safe routes with search param validation and loader-based data fetching.
-
Client State Management: Zustand vs Redux Toolkit vs Jotai vs Context API
- Zustand: minimal boilerplate, good default for most apps that need shared state.
- Redux Toolkit: large apps with complex state machines, time-travel debugging, or existing Redux muscle memory.
- Jotai: state that's naturally atomic and derived - good for complex forms or graph-like state.
- Context API: truly global, rarely-changing values like theme or auth user; avoid for anything that updates often.
-
Server State / Data Fetching: TanStack Query vs SWR vs RTK Query vs Apollo
- TanStack Query: default for REST - caching, mutations, devtools, framework-agnostic.
- SWR: simpler API, lightweight, tightly integrated if you're in the Vercel ecosystem.
- RTK Query: already using Redux Toolkit and want data fetching in the same store.
- Apollo: GraphQL with normalized caching, optimistic updates, and subscriptions.
-
Styling Approach: Tailwind vs CSS Modules vs styled-components vs vanilla-extract
- Tailwind: fast iteration, consistent design tokens, pairs great with shadcn/ui.
- CSS Modules: you want scoped plain CSS with zero runtime and no new DSL to learn.
- styled-components: dynamic theming and co-located CSS-in-JS, accepting the runtime cost.
- vanilla-extract: type-safe, zero-runtime CSS-in-TS for design system authors.
-
Component Library: shadcn/ui vs MUI vs Chakra vs Radix + custom
- shadcn/ui: you want copy-pasted, fully owned components built on Radix + Tailwind.
- MUI: enterprise apps needing comprehensive, Material-style components out of the box.
- Chakra: accessible, themeable components with a friendly prop-based API.
- Radix + custom: you want headless, accessible primitives and a fully bespoke visual identity.
-
Forms: React Hook Form vs TanStack Form vs Formik
- React Hook Form: default choice - performant, uncontrolled, pairs well with Zod resolvers.
- TanStack Form: deeply type-safe with first-class async validation and field arrays.
- Formik: only for legacy projects already using it; generally not recommended for new work.
-
Schema Validation: Zod vs Valibot vs Yup
- Zod: de facto standard; great TS inference, broad ecosystem integration (RHF, tRPC, etc.).
- Valibot: bundle-size-sensitive apps - modular and tree-shakable with Zod-like ergonomics.
- Yup: existing Formik projects or if you prefer its async-first validation style.
-
API Layer: REST vs GraphQL vs tRPC
- REST: public APIs, maximum client compatibility, or simple CRUD needs.
- GraphQL: multiple clients with divergent data needs and complex nested queries.
- tRPC: full-stack TypeScript project where the API is only consumed by your own clients.
-
Authentication: Auth.js vs Clerk vs Auth0 vs custom
- Auth.js (NextAuth): open-source, self-hosted, many OAuth providers out of the box for Next.js.
- Clerk: drop-in UI, user management dashboards, and you're happy to pay for speed.
- Auth0: enterprise SSO, SAML, and compliance requirements.
- Custom: unusual auth flows or strict data residency requirements that preclude third parties.
-
Unit/Integration Testing: Vitest vs Jest
- Vitest: Vite-based projects and anyone wanting a faster, ESM-native runner with a Jest-compatible API.
- Jest: large existing test suites, Next.js without Vite, or tooling that specifically assumes Jest.
-
E2E Testing: Playwright vs Cypress
- Playwright: multi-browser coverage (WebKit/Firefox), parallelism, and modern DX - default recommendation.
- Cypress: heavy time-travel debugging and a richer UI for flaky test diagnosis.
-
Linting & Formatting: ESLint + Prettier vs Biome
- ESLint + Prettier: mature ecosystem with plugins for every framework and rule imaginable.
- Biome: greenfield projects wanting a single, fast Rust-based tool for both linting and formatting.
-
Folder Structure: Feature-based vs Type-based vs Atomic Design
- Feature-based: co-locate all code per domain feature - scales best as apps grow.
- Type-based (components/, hooks/, etc.): fine for small apps but painful past ~20 features.
- Atomic Design: design-system-heavy apps where reusable UI primitives are the primary concern.
-
Code Splitting Strategy: Route-level vs Component-level lazy loading
- Route-level: default; split at page boundaries for the best TTI gains with minimal effort.
- Component-level: specific heavy widgets (charts, rich editors, maps) that are conditionally rendered.
-
Error Monitoring: Sentry vs LogRocket vs Datadog
- Sentry: best-in-class error tracking with source maps, releases, and performance monitoring.
- LogRocket: you need session replay alongside errors to debug UX issues.
- Datadog: you're already standardized on it for backend observability and want a single pane.
-
Analytics: PostHog vs Google Analytics vs Plausible vs Mixpanel
- PostHog: product analytics with feature flags, session replay, and a self-hosting option.
- Google Analytics: free, ubiquitous, and sufficient for marketing/traffic metrics.
- Plausible: lightweight, privacy-first, cookie-less - good for EU compliance.
- Mixpanel: funnel and cohort analysis for product teams that live in event data.
-
CI/CD: GitHub Actions vs GitLab CI vs CircleCI
- GitHub Actions: default if your repo is on GitHub - deep integration and a huge marketplace.
- GitLab CI: your source is on GitLab; tight Auto DevOps integration.
- CircleCI: teams wanting powerful parallelism primitives and mature caching out of the box.
-
Hosting / Deployment: Vercel vs Netlify vs AWS vs Cloudflare Pages
- Vercel: Next.js apps - first-party support, edge runtime, preview deploys.
- Netlify: Jamstack sites, serverless functions, and a generous free tier.
- AWS: you need full control, custom VPCs, or already live in AWS.
- Cloudflare Pages: edge-first apps, aggressive pricing, and tight Workers integration.
-
Environment Variable Management: .env files vs Doppler vs AWS Secrets Manager
- .env files: simple projects with few secrets and trusted devs.
- Doppler: teams needing shared, audited secret sync across envs without AWS complexity.
- AWS Secrets Manager: AWS-native stacks with rotation, IAM policies, and compliance needs.
-
Internationalization (i18n): next-intl vs react-i18next vs Lingui
- next-intl: Next.js App Router with first-class RSC support.
- react-i18next: mature, framework-agnostic, huge plugin ecosystem.
- Lingui: compile-time message extraction and a smaller runtime, ICU-based.
-
Animation: Framer Motion vs React Spring vs CSS/Tailwind
- Framer Motion: declarative animations, gestures, and layout animations with minimal code.
- React Spring: physics-based animations and fine-grained control.
- CSS/Tailwind: simple transitions and hover effects - no JS runtime cost.
-
Icon System: Lucide vs Heroicons vs custom SVG sprites
- Lucide: largest modern icon set with tree-shakable React components.
- Heroicons: clean, Tailwind-aligned, pairs well with shadcn/ui.
- Custom SVG sprites: brand-specific iconography or strict bundle-size budgets.
-
Date/Time Handling: date-fns vs Day.js vs Luxon vs Temporal
- date-fns: tree-shakable functional utilities; default choice for most apps.
- Day.js: Moment-like API in a tiny bundle - good for quick migrations off Moment.
- Luxon: heavy timezone and calendaring needs, built on Intl.
- Temporal (polyfill): you want the future standard today and are okay shipping the polyfill.
-
Component Documentation: Storybook vs Ladle vs none
- Storybook: design system or shared component library consumed by multiple teams.
- Ladle: Vite-based, faster alternative for simpler component catalogs.
- None: early-stage products where components change daily and docs rot faster than they help.
-
Image Optimization: next/image vs Cloudinary vs custom (Sharp + CDN)
- next/image: Next.js projects - automatic resizing, lazy loading, and format negotiation.
- Cloudinary: you need on-the-fly transformations, CDN delivery, and work in non-Next frameworks.
- Custom (Sharp + CDN): full control, cost-sensitive at scale, or highly specific image pipelines.