live in productionB2B Commerce Platform· 2025—present

Bikalpo

End-to-end B2B commerce platform for a Bangladeshi distributor — product catalog, role-based pricing, and a checkout flow that respects how real wholesale orders actually get placed. Currently in production on its own domain; mid-flight rewrite into a Turborepo monorepo with web, mobile, and a Hono backend.

Next.js 16React 19Drizzle ORMNeon PostgresBetter-AuthTanStack QueryLexicalCloudinary

The brief

A Dhaka-based distributor wanted a B2B storefront that didn't feel like a downsized B2C shop. Their wholesalers buy in tiers, see different prices, expect their own catalogue, and place orders that span dozens of SKUs. The off-the-shelf Shopify-style platforms collapse under all four.

I shipped the first version at bikalpo.com — a Next.js app backed by Postgres on Neon, with Better-Auth, Drizzle, and a Lexical-powered rich content layer for product copy and policy pages. That version is now in production, and the rewrite into a Turborepo monorepo (web + native + Hono server) is mid-flight.

The problem

The status quo had four pain points:

  • Per-customer pricing — every wholesale account negotiates their own tiered price, and the storefront has to respect it without leaking other customers' tiers.
  • Catalogue scale — thousands of SKUs across dozens of brands, each with its own copy, imagery, and inventory state.
  • Rich content alongside commerce — landing pages, brand stories, and policy pages live next to the catalogue and need first-class editing, not a CMS bolted on the side.
  • Bangladesh-specific payments — local payment rails, COD, and partial-payment flows that most platforms ignore.

The approach

The system is one Next.js 16 app at the edge, talking to a single Postgres database via Drizzle. Auth, sessions, and role gating run through Better-Auth — including the per-customer pricing keyed off the authenticated identity. React Compiler is enabled, so the catalogue pages stay fast even with dense, cell-heavy renders.

Rich content (product copy, blocks on landing pages, policy pages) is authored in Lexical and stored as serialised JSON in the same Postgres tables as the rest of the catalogue. That decision matters: it means a product page and a policy page render through the same primitives, with no separate CMS lifecycle to keep in sync.

browser · b2b storefrontnext.js 16 · edgereact compiler · server actionspostgres / neonbetter-authcloudinary · lexical
fig. — system shape — one Next.js app, one Postgres, Lexical content alongside catalogue rows

The hard parts

Per-tier pricing without per-tier fetches

Each authenticated buyer sees their own price column on every product. The naive shape — fetch product, then fetch overrides — adds a round-trip on every catalogue page. I push the tier resolution into the Drizzle query at the edge with a single LEFT JOIN ... ON pricing.tier_id = $session.tier, so the listing page returns 60 products with the right number on each, in one query. The same hook covers cart totals, so the price the buyer sees is the price they're charged — there's no second source of truth.

Lexical alongside SKU data

Treating product copy and policy pages as the same content primitive was the call that paid off most. Both render through a single set of Lexical nodes (paragraph, heading, image, callout, table). New page types are a database row, not a new template.

The point of a B2B platform isn't to look like Shopify. It's to make the wholesaler feel like the catalogue was built for their account.

when reviewing the rewrite plan

The rewrite into a monorepo

The current production site is a single Next.js app. The successor — bikalpo-project — splits it into a Turborepo with apps/web (Next.js storefront), apps/native (the buyer's mobile app), and apps/server (Hono API). Shared packages/db, packages/auth, packages/ui keep the schema and design tokens centralised. The rewrite is mid-flight; the production app is unaffected.

What's running today

v1live in production
60+vendor brands · placeholder
1postgres · one source of truth
3apps · web / native / server

The production site at bikalpo.com ships the storefront, account portal, and admin tools. The monorepo rewrite is on track to replace it without downtime — same database, new shape.

What I'd do differently

Lexical was the right call for content; it would have been the right call sooner. Earlier versions used a hand-rolled markdown layer that I had to keep patching. Once the rich-text model is treated as part of the schema, every other decision gets easier.