Skip to main content

Application Type

VSM Store is a React 18 Single-Page Application (SPA) bundled by Vite and deployed to Cloudflare Pages CDN. There is no server-side rendering. All routing happens client-side via React Router DOM v6. The SPA contains two distinct experiences under one deployment:
  • Storefront — public-facing catalog, cart, checkout, and authenticated user pages (profile, orders, loyalty, etc.)
  • Admin Panel (/admin/*) — completely separate layout with its own sidebar, guarded by AdminGuard, lazy-loaded into a separate admin-panel chunk

Provider Tree

src/main.tsx composes the full provider hierarchy before rendering <App />:
React.StrictMode
  └─ ErrorBoundary
       └─ BrowserRouter
            └─ ThemeProvider        (dark/light theme, localStorage 'vsm-theme')
                 └─ AuthProvider    (Supabase auth + customer_profiles)
                      └─ QueryClientProvider   (React Query, staleTime: 5 min)
                           └─ HelmetProvider   (SEO meta tags)
                                └─ SafetyProvider
                                     └─ App
ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
        <ErrorBoundary>
            <BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
                <ThemeProvider>
                    <AuthProvider>
                        <QueryClientProvider client={queryClient}>
                            <HelmetProvider>
                                <SafetyProvider>
                                    <App />
                                </SafetyProvider>
                            </HelmetProvider>
                        </QueryClientProvider>
                    </AuthProvider>
                </ThemeProvider>
            </BrowserRouter>
        </ErrorBoundary>
    </React.StrictMode>,
);
react-hot-toast’s <Toaster> is mounted inside App rather than in the provider tree so that admin and storefront can each configure their own toast position and styles.

Data Flow

Supabase PostgreSQL (with RLS policies)

Services  (src/services/*.service.ts)
    — async functions wrapping supabase client calls

Hooks  (src/hooks/use*.ts)
    — React Query useQuery / useMutation wrappers
    — define queryKey, queryFn, and staleTime

Components + Pages  (src/components/ + src/pages/)
    — consume hooks, render data, dispatch mutations

UI rendered to the user
Zustand stores (cart, notifications) sit alongside this flow for purely client-side state that does not require server synchronisation.

Lazy Loading and Code Splitting

Every page component is wrapped in React.lazy() so it is not included in the initial bundle:
// Storefront pages
const Home = lazy(() => import('@/pages/Home').then(m => ({ default: m.Home })));
const SearchResults = lazy(() => import('@/pages/SearchResults').then(m => ({ default: m.SearchResults })));

// Admin pages — load only when /admin/* is visited
const AdminDashboard = lazy(() => import('@/pages/admin/AdminDashboard').then(m => ({ default: m.AdminDashboard })));
const AdminGuard = lazy(() => import('@/components/admin/AdminGuard').then(m => ({ default: m.AdminGuard })));
All lazy components are wrapped in <Suspense fallback={<PageLoader />}>. The PageLoader is a minimal spinning indicator that does not depend on any provider.

Vite Manual Chunks

The vite.config.ts defines a manualChunks strategy (currently toggled off but documented) that splits the bundle into separately cacheable chunks:
Chunk nameContents
vendor-reactreact, react-dom, scheduler
vendor-sentry@sentry/*
vendor-supabase@supabase/*
vendor-query@tanstack/*
vendor-routerreact-router*
vendor-framerframer-motion
vendor-zodzod
admin-panelEverything under src/pages/admin/
legal-pagesLegal pages
This means a product catalog update only invalidates the application chunk — vendor chunks remain cached.

Storefront vs Admin Separation

App.tsx branches on pathname.startsWith('/admin') before rendering:
if (isAdmin) {
    return (
        <Suspense fallback={<PageLoader />}>
            <AdminGuard>
                <AdminLayout>
                    <AdminErrorBoundary>
                        <Routes>{/* admin routes only */}</Routes>
                    </AdminErrorBoundary>
                </AdminLayout>
            </AdminGuard>
        </Suspense>
    );
}
Admin panel characteristics:
  • No storefront <Header>, <Footer>, <CartSidebar>, or <WhatsAppFloat>
  • AdminGuard queries the admin_users Supabase table; non-admins are redirected
  • AdminLayout renders its own sidebar navigation
  • All admin pages are in a separate lazy chunk so they are never downloaded by storefront visitors

PWA Configuration

VSM Store is an installable Progressive Web App:
AssetLocationPurpose
manifest.json/public/manifest.jsonApp name, icons, display mode
Service Worker/public/sw.jsOffline support and caching
Icons/public/icons/96×96 and 192×192 PNG icons
The service worker is registered in main.tsx:
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker
            .register('/sw.js')
            .catch((err) => {
                if (import.meta.env.DEV) console.error('[PWA] SW error:', err);
            });
    });
}
index.html includes the standard PWA meta tags:
  • apple-mobile-web-app-capable: yes
  • theme-color: #0f172a (slate-900, the dark mode background)
main.tsx includes a cache-busting routine (VSM_VERSION = 'W143-RECOVERY-A') that unregisters stale service workers and clears caches on version mismatch. This was added to recover from a cache poisoning incident and runs on every fresh load.

Error Handling

LayerMechanism
Root<ErrorBoundary> wraps the entire tree in main.tsx
Storefront root<ErrorBoundary componentName="StorefrontRoot"> in App.tsx
Individual routesAdditional <ErrorBoundary> inside <Suspense>
Admin<AdminErrorBoundary> wraps all admin routes
Servicestry/catch with console.error + re-throw; Sentry captures in production
React QueryGlobal QueryCache.onError and MutationCache.onError log to Sentry and add a notification

Build docs developers (and LLMs) love