Every mutation in the kit uses Next.js Server Actions. They run server-side, use the Supabase admin client for writes, and call revalidatePath() to refresh page data.
Architecture
Client Component → Server Action → Supabase Admin Client → Database
→ revalidatePath() → Page Refresh
All Server Actions are in lib/actions/ and marked with "use server" at the top of each file.
Product Actions (lib/actions/products.ts)
| Action |
Parameters |
Description |
createProduct |
FormData |
Insert product with category, tags, specs |
updateProduct |
id, FormData |
Update product details |
deleteProduct |
id |
Delete product and associated images/variants |
uploadProductImage |
productId, file |
Upload to Supabase Storage |
Order Actions (lib/actions/orders.ts)
| Action |
Parameters |
Description |
updateOrderStatus |
orderId, status |
Change order status |
addOrderNote |
orderId, message |
Add a note to the order |
updateTrackingNumber |
orderId, trackingNumber |
Set shipping tracking |
Review Actions (lib/actions/reviews.ts)
| Action |
Parameters |
Description |
respondToReview |
reviewId, response |
Add admin response |
moderateReview |
reviewId, status |
Approve, reject, or flag |
Address Actions (lib/actions/addresses.ts)
| Action |
Parameters |
Description |
createAddress |
FormData |
Add new address |
updateAddress |
id, FormData |
Edit existing address |
deleteAddress |
id |
Remove address |
setDefaultAddress |
id |
Mark as default |
Discount Actions (lib/actions/discounts.ts)
| Action |
Parameters |
Description |
createDiscount |
FormData |
Create new discount code |
updateDiscount |
id, FormData |
Edit discount details |
disableDiscount |
id |
Set status to disabled |
Return Actions (lib/actions/returns.ts)
| Action |
Parameters |
Description |
createReturnRequest |
orderId, items, reason |
Submit return request |
updateReturnStatus |
returnId, status |
Process return |
Customer Actions (lib/actions/customers.ts)
| Action |
Parameters |
Description |
getAllCustomers |
- |
Fetch all users with order stats (admin) |
Error Handling Pattern
All Server Actions follow this pattern:
"use server"
import { createAdminClient } from "@/lib/supabase/admin"
import { revalidatePath } from "next/cache"
export async function updateOrderStatus(orderId: string, status: string) {
try {
const supabase = createAdminClient()
const { error } = await supabase
.from("orders")
.update({ status })
.eq("id", orderId)
if (error) throw error
revalidatePath(`/admin/orders/${orderId}`)
revalidatePath("/admin/orders")
return { success: true }
} catch (err) {
console.error("Failed to update order status:", err)
return { success: false, error: "Failed to update order status" }
}
}
Using Server Actions in Components
Server Actions are called from client components using form actions or event handlers:
"use client"
import { updateOrderStatus } from "@/lib/actions/orders"
function OrderActions({ orderId }: { orderId: string }) {
const [isPending, startTransition] = useTransition()
function handleStatusChange(status: string) {
startTransition(async () => {
const result = await updateOrderStatus(orderId, status)
if (result.success) {
toast.success("Order status updated")
} else {
toast.error(result.error)
}
})
}
return (
<Select onValueChange={handleStatusChange} disabled={isPending}>
{/* ... */}
</Select>
)
}
Supabase Client Selection
| Context |
Client |
RLS |
| Public reads (storefront) |
Server client |
Yes (anon key) |
| User reads (account) |
Server client |
Yes (user's session) |
| Admin reads |
Server client or Admin |
Admin client bypasses RLS |
| All writes |
Admin client |
Bypassed (service role) |
| Webhook handler |
Admin client |
Bypassed (service role) |
The admin client (lib/supabase/admin.ts) uses SUPABASE_SERVICE_ROLE_KEY and should never be exposed to the client.