Skip to content

Performance

Audit performed without running npm run build (no production artifacts at audit time). Numbers below are projections from the dependency list and source structure.

Initial bundle (projected)

  • React 18 + react-dom: ~140 KB gzipped
  • react-router-dom: ~20 KB
  • @tanstack/react-query: ~14 KB
  • ~27 @radix-ui primitives: ~80 KB combined (only the ones actually imported)
  • lucide-react: tree-shakeable; per-icon import cost
  • recharts: ~90 KB (heavy)
  • date-fns: ~5 KB if imported per-function
  • shadcn/ui + Tailwind: minimal runtime (CSS is dominant; ~30 KB after Tailwind purge)
  • App code: ~30 KB

Projected total: ~300-400 KB gzipped for the main chunk. For an internal tool with 6 routes, acceptable. Optimisation pressure is low until route count exceeds ~15 or a heavy dep like puppeteer-core lands.

Tree shaking

Vite + ESM = tree shaking out of the box. As long as imports are named (import { Button } from "@/components/ui/button"), unused code is dropped. Watch for import * as X from ... patterns — none observed in spot-checks.

Code splitting

Not configured. All routes are statically imported in src/App.tsx. Every page is loaded on initial visit, even if the user only ever visits one. With 6 routes this is fine; at ~15 routes consider switching to:

const Accounts = React.lazy(() => import('./pages/Accounts'));
// ...
<Route path="/accounts" element={<React.Suspense fallback={<Skeleton />}><ProtectedRoute>...</ProtectedRoute></React.Suspense>} />

Image optimisation

  • No <picture> / srcSet usage observed
  • No WebP/AVIF pipeline
  • public/dashboard.jpg and public/someli-admin-og.png are served as-is

For a 6-page admin tool, this is fine. If image-heavy pages get added, add Vite's vite-imagetools or a CDN with on-the-fly conversion.

Font loading

SF Pro Display is a system font on Apple platforms — no network round-trip. On non-Apple platforms, the fallback chain (-apple-system, BlinkMacSystemFont, system-ui, sans-serif) renders immediately with no FOIT/FOUT. No <link rel="preload"> is needed.

Core Web Vitals

Not measured. No RUM (Real User Monitoring) is configured. For an internal tool with ~10–50 daily users, this is acceptable; the team can rely on manual UX testing.

To add: web-vitals package (~1 KB) + report to a backend log endpoint, or wire to Sentry's Performance feature.

Render performance

  • No React.memo / useMemo usage observed at scale (some specific places use it)
  • shadcn's <Table> is plain HTML — for large lists, virtualisation (@tanstack/react-virtual, already implicit via @tanstack/react-query) would be needed when rows exceed ~500
  • The Accounts page likely has the heaviest table — verify it has pagination (BE returns paginated results, so probably OK)

Caching strategy

  • No service worker
  • No Cache-Control config in repo (depends on hosting layer)
  • React Query cache TTL is library default (staleTime: 0)

For an internal tool: fine. For a customer-facing app at scale: would need to be tuned.

Recommendations

In priority order:

  1. Add bundle-size reporting in CI (e.g., rollup-plugin-visualizer + a CI check)
  2. Add Core Web Vitals reporting via web-vitals package (5 lines of code)
  3. Code split when route count exceeds ~15
  4. Virtualise large tables when needed
  5. Tune React Query staleTime per query — most admin data is fine with staleTime: 30_000