Skip to content

Stack

Concrete technology choices for the Alhuda Travel ERP. Cross-reference package.json for exact versions; the table below captures the load-bearing dependencies and the role each one plays.

Runtime surface

Concern Choice Why
UI framework React 18 (react, react-dom) Standard for the team; Suspense + concurrent features used for lazy route loading in src/App.tsx.
Build / dev server Vite 5 with @vitejs/plugin-react-swc Fast HMR, ESBuild-style transforms, PWA via vite-plugin-pwa, gzip + brotli compression plugins. See vite.config.ts.
Language TypeScript 5.8 (strict compile via npx tsc --noEmit) Enforced in CI; tsc --noEmit must be clean before PR review (CLAUDE.md §Testing).
Styling Tailwind CSS 3.4 + tailwind-merge, tailwindcss-animate, class-variance-authority, clsx Utility-first; cva used inside src/components/ui/* shadcn-style primitives.
Headless UI primitives Radix UI (@radix-ui/react-*, ~28 packages) Accessibility-first unstyled primitives, wrapped by the project's components/ui layer.
Icons lucide-react Consistent icon set used across the app.
Theming next-themes Light/dark toggle wired in src/components/theme-provider.tsx.
Routing React Router 6 (react-router-dom) See src/App.tsx for the full route table and Frontend routing.
Data fetching / cache TanStack React Query 5 Default staleTime 5m, gcTime 10m, retry: 1, refetchOnWindowFocus: false — tuned for an internal ERP where data doesn't change under the user's nose (src/App.tsx:84-98).
Virtualisation @tanstack/react-virtual Long list perf (journal lines, booking passengers, etc.).
Forms React Hook Form 7 + @hookform/resolvers Paired with Zod for schema-driven validation.
Validation Zod 3 Shared type-safe validation between UI and API parameter parsing.
Backend SDK @supabase/supabase-js 2 Used everywhere via src/integrations/supabase/client.ts. No custom Node API server.
Date handling date-fns 3 No Moment.js; locale formatting done per-call. src/lib/date.ts wraps getDateStamp helpers.
Charts recharts Dashboard, Finance reports.
Excel I/O xlsx + xlsx-js-stylelazy-loaded Bundle-budget discipline — xlsx is only pulled in by the few screens that import/export spreadsheets; see scripts/check-bundle-budgets.mjs.
Toasts / notifications sonner + Radix Toast (@radix-ui/react-toast) Two notification surfaces for different use cases.
Command palette cmdk Quick actions.
Carousel / input masks embla-carousel-react, input-otp, react-day-picker, vaul, react-resizable-panels Component-specific primitives.
Observability Sentry (@sentry/react) Initialised in src/lib/observability.ts; error boundary in src/components/common/ErrorBoundary.tsx.
Web vitals web-vitals Performance telemetry streamed through the observability layer.

PWA and service worker

vite-plugin-pwa is configured with strategies: 'injectManifest' and points at src/sw.ts — we hand-write the service worker logic rather than relying on auto-generated Workbox config. Update prompts surface via @/components/common/PWAUpdateNotifier (src/App.tsx:13). See vite.config.ts:43 onward.

Test stack

Concern Choice Where
Unit / integration Vitest 2 + happy-dom vitest.config.ts, 570+ tests required to pass before commit (CLAUDE.md §Testing).
React tests @testing-library/react + @testing-library/jest-dom + @testing-library/user-event Co-located *.test.tsx files.
Mock service worker msw HTTP mocking where Supabase can't be spun up in-process.
E2E Playwright (@playwright/test) playwright.config.ts, scripts in e2e/.
Coverage @vitest/coverage-v8 npm run test:coverage.

Build and release tooling

Concern Choice Notes
Linter ESLint 9 with eslint-plugin-react-hooks, eslint-plugin-react-refresh, typescript-eslint eslint.config.js.
Bundle analysis rollup-plugin-visualizer ANALYZE=true npm run build emits dist/bundle-stats.html.
Bundle budgets scripts/check-bundle-budgets.mjs Hard ceiling on per-chunk sizes; fails the build on regression.
Compression vite-plugin-compression (gzip + brotli) Built assets are pre-compressed for Cloudflare Pages.
Release automation semantic-release + @semantic-release/changelog + @semantic-release/git Conventional commits drive CHANGELOG.md and version bumps.
Supabase CLI supabase (dev dependency) Local DB + Edge Functions workflow.

Libraries that deliberately don't appear

  • No Redux / Zustand — server state lives in React Query, local state in useState.
  • No Axios — everything goes through supabase-js or a thin apiFetch wrapper over it.
  • No Moment.js — date-fns only.
  • No Lodash — small targeted helpers live in src/lib/*.

Why React Query + Supabase, not Redux

React Query's staleness model maps cleanly onto "data lives in Postgres and is fetched on demand". Adding Redux would mean two caches (client-owned and server-owned) and a whole class of reconciliation bugs; the team has chosen to skip it.

Further reading