Hexa Shop
A modern e-commerce platform with a clean UI, product filtering, cart management, and seamless checkout experience.


Project Overview
Hexa Shop is a modern, full-stack e-commerce platform built with the latest web technologies. The application features a customer-facing storefront and a comprehensive admin dashboard for managing products, categories, orders, and store settings.
The platform was designed with scalability, developer experience, and modern UX patterns in mind, leveraging React Server Components for optimal performance and Supabase for a complete backend-as-a-service solution.
Key Features
- š Customer Storefront ā Browse products, categories, and complete purchases
- š Admin Dashboard ā Full CRUD operations for products, categories, inventory, and orders
- š Role-Based Access Control ā Admin, demo admin (limited write access), and customer roles
- š¼ļø Image Management ā Multi-image uploads with Supabase Storage integration
- šØ Dark/Light Theme ā System-aware theme switching with persistent preferences
- š± Responsive Design ā Mobile-first approach with collapsible sidebar navigation
Technologies Used
Frontend
| Technology | Purpose |
|---|---|
| Next.js 16 | App Router, React Server Components, SSR |
| React 19 | Latest React features including use hooks |
| Tailwind CSS v4 | Utility-first styling with CSS variables |
| shadcn/ui | Accessible component library (new-york style) |
| TanStack Query | Server state management and caching |
| TanStack Table | Headless table for data grids |
| React Hook Form + Zod | Type-safe form handling and validation |
| TipTap | Rich text editor for product descriptions |
| Lucide React | Icon library |
Backend & Infrastructure
| Technology | Purpose |
|---|---|
| Supabase | PostgreSQL database, Auth, Storage |
| Supabase Auth | OAuth (Google), email/password authentication |
| Supabase Storage | Image uploads with 2MB file size limits |
| Next.js Server Actions | Type-safe server mutations with revalidation |
| Middleware | Route protection for admin paths |
Development Tools
| Tool | Purpose |
|---|---|
| TypeScript | End-to-end type safety |
| ESLint v9 | Code quality and consistency |
| pnpm | Fast, disk-efficient package manager |
Challenges Faced
1. Supabase Client Caching in Serverless Environment
Problem: Next.js 16's Fluid compute architecture caused issues when caching Supabase clients at module level, leading to stale authentication states and cookie synchronization problems.
Impact: Users experienced intermittent auth failures and session inconsistencies across server components and server actions.
2. Complex Role-Based Permissions
Problem: The application required granular permissions with three distinct roles:
- Admin ā Full access to all operations
- Demo Admin ā Read access + create/update (no delete) for portfolio demonstrations
- Customer ā Public storefront access only
Impact: Needed a flexible authorization system that could be checked both in middleware (route protection) and server actions (operation-level protection).
3. Multi-Image Product Management
Problem: Products required multiple images with primary image designation, reordering capability, and proper cleanup when images are deleted or replaced.
Impact: Required coordination between React state, form handling, Supabase Storage uploads, and database transactions.
4. Optimistic UI with Server State
Problem: Balancing immediate UI feedback with server-side data consistency when performing CRUD operations through server actions.
Impact: Users expected instant feedback while the system needed to maintain data integrity and handle potential failures gracefully.
Solutions Implemented
1. Fresh Supabase Client Pattern
Implemented a strict "create fresh per function" pattern for server-side Supabase clients:
// lib/supabase/server.ts
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
},
},
}
);
}Every server action and API route creates a new client instance, ensuring proper cookie handling and authentication state.
2. Layered Authorization System
Implemented a multi-layer approach:
Layer 1 ā Middleware Route Protection:
// proxy.ts
export const config = {
matcher: ["/admin/:path*"],
};Layer 2 ā Role Helper Functions:
// lib/auth/roles.ts
export function isAdmin(user: User | null): boolean {
return user?.app_metadata?.role === "admin";
}
export function hasAdminAccess(user: User | null): boolean {
return isAdmin(user) || isDemoAdmin(user);
}Layer 3 ā Server Action Guards:
export async function deleteCategory(id: string) {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
// Only full admin can delete
if (!isAdmin(user)) {
return { ok: false, error: "Only admin users can delete records" };
}
// ... proceed with deletion
}3. TanStack Query Integration with Server Actions
Combined TanStack Query's caching capabilities with Next.js Server Actions:
// hooks/use-categories.ts
export function useAddCategory() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (formData: FormData) => {
const result = await addCategory(formData);
if (!result.ok) throw new Error(result.error);
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: CATEGORIES_QUERY_KEY });
toast.success("Category created!");
},
onError: (error: Error) => toast.error(error.message),
});
}This pattern provides:
- Automatic cache invalidation on mutations
- Loading and error states
- Optimistic updates when needed
- Toast notifications for user feedback
4. Composable Form Architecture
Standardized form pattern using React Hook Form with Zod validation:
// Separate schema file
export const categoryFormSchema = z.object({
name: z.string().min(1, "Required").max(100),
is_active: z.boolean(),
});
// Form component with Controller pattern
<Controller
control={control}
name="name"
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input {...field} />
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>;Architecture Highlights
Directory Structure
app/
āāā (root)/ # Public storefront (route group)
āāā admin/ # Protected admin dashboard
āāā auth/ # Authentication flows
āāā actions/ # Shared server actions
lib/
āāā services/ # Server actions (categories.ts, products.ts)
āāā supabase/ # Client factories (server.ts, client.ts)
āāā auth/ # Role helpers
components/
āāā ui/ # shadcn/ui primitives
āāā admin/ # Admin-specific components
āāā auth/ # Auth forms
hooks/ # TanStack Query hooks
providers/ # React context providers
Data Flow
User Action ā React Hook Form ā Server Action ā Supabase
ā
TanStack Query Mutation ā Cache Invalidation ā UI Update
ā
Toast Notification (Sonner)
Results Achieved
Performance Metrics
- ā” First Contentful Paint: < 1.5s with React Server Components
- š¦ Bundle Size: Optimized with tree-shaking and dynamic imports
- š Data Fetching: Efficient with TanStack Query deduplication and caching
Developer Experience
- ā 100% TypeScript: End-to-end type safety from database to UI
- š§© Modular Architecture: Clear separation of concerns
- š Consistent Patterns: Standardized forms, hooks, and server actions
User Experience
- šÆ Instant Feedback: Optimistic updates with graceful error handling
- š Theme Support: Seamless dark/light mode switching
- š± Responsive: Full functionality on mobile devices
Security
- š Multi-layer Auth: Middleware + server-side role checks
- š”ļø Demo Mode: Safe demonstration without data loss risk
- š Secure Uploads: Validated file types and size limits
Key Learnings
-
Serverless Architecture Requires Stateless Patterns: Module-level caching can cause subtle bugs in serverless environments. Always create fresh instances for request-scoped resources.
-
Type Safety Pays Off: The upfront investment in TypeScript and Zod schemas prevented countless runtime errors and improved refactoring confidence.
-
Separation of Server/Client Concerns: Clear boundaries between server and client code (Server Actions vs TanStack Query hooks) made the codebase more maintainable.
-
Component Composition Over Configuration: Building with shadcn/ui's composable primitives allowed for consistent yet flexible UI patterns.
Links
- š Live Demo ā Try the demo admin experience
- š GitHub Repository ā View the source code
- š§ Contact ā Discuss this project
Built with Next.js 16, React 19, Supabase, and modern web technologies.