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>/srcSetusage observed - No WebP/AVIF pipeline
public/dashboard.jpgandpublic/someli-admin-og.pngare 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/useMemousage 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-Controlconfig 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:
- Add bundle-size reporting in CI (e.g.,
rollup-plugin-visualizer+ a CI check) - Add Core Web Vitals reporting via
web-vitalspackage (5 lines of code) - Code split when route count exceeds ~15
- Virtualise large tables when needed
- Tune React Query
staleTimeper query — most admin data is fine withstaleTime: 30_000