Frontend Architecture¶
The frontend is built with Next.js 16.2.5, React 19.2.7, and Tailwind CSS 4.
Project Structure¶
frontend/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout with providers
│ ├── page.tsx # Homepage
│ ├── account/ # User account area (see subtree below)
│ ├── admin/ # Admin dashboard (React)
│ ├── api/ # Route handlers (health check)
│ ├── auth/ # Authentication pages
│ ├── cart/ # Shopping cart
│ ├── checkout/ # Checkout flow
│ ├── contact/ # Contact form
│ ├── designer/ # Product designer (Fabric.js)
│ ├── designs/ # Saved designs (anonymous-friendly)
│ ├── login/ # Login page
│ ├── offerte/ # Quote request pages ([id] detail)
│ ├── producten/ # Product routes (listing + [slug] detail) — canonical
│ ├── team-order/ # Team/bulk ordering
│ ├── faq/ # FAQ page
│ ├── over-ons/ # About page
│ ├── privacy/ # Privacy policy
│ ├── retouren/ # Return policy
│ └── voorwaarden/ # Terms & conditions
├── components/
│ ├── Account/ # Account area components
│ ├── admin/ # Admin dashboard components
│ ├── Cart/ # Cart components
│ ├── checkout/ # Checkout components
│ ├── Designer/ # Designer chrome/controls
│ ├── ProductDesigner/ # Fabric.js canvas designer
│ ├── designs/ # Saved-design components
│ ├── header/ # Header components
│ ├── homepage/ # Homepage sections
│ ├── journey/ # Guided journey / onboarding UI
│ ├── orders/ # Order components
│ ├── products/ # Product listing
│ ├── product-detail/ # Product detail page
│ ├── quotes/ # Quote request components
│ ├── TeamOrder/ # Team order components
│ ├── ui/ # Shared UI primitives
│ └── providers/ # Context providers
├── contexts/ # React contexts
│ ├── AuthContext.tsx
│ ├── CartContext.tsx
│ ├── TeamOrderContext.tsx
│ └── ThemeContext.tsx
├── hooks/ # Custom hooks
│ ├── api/ # API query hooks
│ └── admin/ # Admin hooks
├── lib/ # Utilities
│ ├── api/client.ts # API client (Axios)
│ ├── posthog.ts # PostHog analytics init
│ └── queryClient.ts # TanStack Query
├── types/ # TypeScript types
└── public/ # Static assets
fabric vs @types/fabric version mismatch
Runtime uses fabric@^7.4.0, but the type stubs are pinned to
@types/fabric@^5.3.10. The v5 types don't fully describe the v7 API, so
some Fabric.js call sites rely on local typing shims or any.
Routes¶
Canonical customer routes are Dutch. /producten (+ /producten/[slug]) is the
canonical product surface; older English paths exist only as permanent redirects
(configured in next.config.ts).
| Route | Purpose | Notes / redirect |
|---|---|---|
/ |
Homepage | |
/producten |
Product listing | Canonical |
/producten/[slug] |
Product detail | Canonical |
/products |
— | 308 → /producten |
/products/[slug] |
— | 308 → /producten/[slug] |
/catalog |
— | 308 → /producten |
/designer |
Product designer (Fabric.js) | |
/designs |
Saved designs | Works anonymously |
/cart |
Shopping cart | |
/checkout |
Checkout flow | |
/team-order |
Team / bulk ordering | |
/offerte/[id] |
Quote request detail | |
/orders |
— | 308 → /account/orders |
/account/orders |
Order history | |
/account/quotes |
Quote requests | |
/account/designs |
Saved designs | |
/account/addresses |
Address book | |
/account/profile |
Profile settings | |
/account/wishlist |
Wishlist | |
/admin |
Admin dashboard | Staff, MFA-enforced |
/auth, /login |
Authentication | |
/contact, /faq, /over-ons, /privacy, /retouren, /voorwaarden |
Static / info pages |
Component Architecture¶
Component Organization¶
Components are organized by feature:
components/
├── [feature]/
│ ├── FeatureComponent.tsx
│ ├── FeatureComponent.stories.tsx # Storybook
│ └── FeatureComponent.test.tsx # Jest tests
Component Patterns¶
Presentational Components
interface ButtonProps {
variant: 'primary' | 'secondary';
children: React.ReactNode;
}
export function Button({ variant, children }: ButtonProps) {
return <button className={styles[variant]}>{children}</button>;
}
Container Components
export function ProductList() {
const { data: products } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
});
return <ProductGrid products={products} />;
}
State Management¶
Server State (TanStack Query)¶
const { data, isLoading, error } = useQuery({
queryKey: ['products', categoryId],
queryFn: () => api.products.list({ category: categoryId }),
});
Client State (React Context)¶
The primary client state is managed via React Context:
AuthContext- Authentication state and user sessionCartContext- Shopping cart items and operationsTeamOrderContext- Team order builder stateThemeContext- Light/dark theme preferences
Persistent State (Zustand)¶
Zustand is used for localStorage-persisted state that must survive SSR. The
admin onboarding checklist (hooks/admin/useOnboardingProgress.ts) is the
canonical example:
// Admin onboarding checklist progress (persisted under 'admin-onboarding')
export const useOnboardingStore = create<OnboardingState>()(
persist(
(set) => ({
completedSteps: [],
markComplete: (step) => set((state) => ({
completedSteps: [...state.completedSteps, step],
})),
}),
{ name: 'admin-onboarding' }
)
);
Observability¶
- Sentry (
@sentry/nextjs) wraps the app viaSentryProviderinapp/layout.tsx. Session Replay is lazy-loaded as a separate chunk (sentry.client.config.ts) to keep it out of the initial bundle. - PostHog is instrumented:
instrumentation-client.tsinitializes the SDK,lib/posthog.tsexposesidentify()andtrackEvent()helpers (plus typed product/cart/checkout events), andPostHogProvidercaptures$pageviewon route changes. The provider is dynamically imported and only rendered on non-admin routes, so admin sessions do not pollute product analytics and posthog-js stays out of the admin bundle. Event delivery depends onNEXT_PUBLIC_POSTHOG_KEYbeing set for the environment.
Styling¶
Tailwind CSS 4 with custom design tokens in app/globals.css:
:root {
--primary: #31a8b6;
--primary-dark: #2a919d;
}
@theme inline {
--color-primary: var(--primary);
}
Visual Identity System¶
The "Freeze" brand visual language uses frost/ice effects:
Color Tokens:
- --frost-N - Frost blue shades (100-900)
- --ice-N - Ice accent colors
- --coral-N - Warm accent for CTAs
Utility Classes:
- glass-frost - Glassmorphism with backdrop-blur
- frost-overlay - Decorative ice pattern overlay
- link-underline - Animated underline on hover/focus
Animation Patterns (Motion):
// Entrance animations with stagger
<motion.div variants={containerVariants} initial="hidden" animate="visible">
<motion.div variants={itemVariants}>...</motion.div>
</motion.div>
// Tactile feedback
<motion.button whileTap={{ scale: 0.95 }} transition={{ duration: 0.1 }}>
// Frost glow hover
className="hover:shadow-[0_0_15px_rgba(125,211,252,0.25)]"
Product Designer¶
The Fabric.js canvas editor supports:
- Text with custom fonts
- Image uploads
- Shape tools
- Multi-view support (front/back/left/right)
- Undo/redo
- Print area boundaries