Md Foysal Ahmed
E-Commerce2025

Hexa Shop

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

Next.jsTypeScriptReactPostgreSQLSupabaseTailwind CSSShadcn UITanStack QueryReact Hook FormZodTipTap
Hexa Shop screenshot 1
Hexa Shop screenshot 2

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


Technologies Used

Frontend

TechnologyPurpose
Next.js 16App Router, React Server Components, SSR
React 19Latest React features including use hooks
Tailwind CSS v4Utility-first styling with CSS variables
shadcn/uiAccessible component library (new-york style)
TanStack QueryServer state management and caching
TanStack TableHeadless table for data grids
React Hook Form + ZodType-safe form handling and validation
TipTapRich text editor for product descriptions
Lucide ReactIcon library

Backend & Infrastructure

TechnologyPurpose
SupabasePostgreSQL database, Auth, Storage
Supabase AuthOAuth (Google), email/password authentication
Supabase StorageImage uploads with 2MB file size limits
Next.js Server ActionsType-safe server mutations with revalidation
MiddlewareRoute protection for admin paths

Development Tools

ToolPurpose
TypeScriptEnd-to-end type safety
ESLint v9Code quality and consistency
pnpmFast, 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:

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:

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

Developer Experience

User Experience

Security


Key Learnings

  1. Serverless Architecture Requires Stateless Patterns: Module-level caching can cause subtle bugs in serverless environments. Always create fresh instances for request-scoped resources.

  2. Type Safety Pays Off: The upfront investment in TypeScript and Zod schemas prevented countless runtime errors and improved refactoring confidence.

  3. Separation of Server/Client Concerns: Clear boundaries between server and client code (Server Actions vs TanStack Query hooks) made the codebase more maintainable.

  4. Component Composition Over Configuration: Building with shadcn/ui's composable primitives allowed for consistent yet flexible UI patterns.


Links


Built with Next.js 16, React 19, Supabase, and modern web technologies.