Moving a Multi-Vendor Storefront to Next.js Without Pausing Vendor Onboarding
Bangla Bazar's vendor count was climbing past a few dozen when the original server-rendered storefront started showing cracks. Search and category pages were timing out under load during evening traffic peaks, and vendors — the people whose trust the whole marketplace depends on — were chasing us for payout visibility that was stuck behind a batch job running once a day. The obvious fix, at least the one that gets proposed first in almost every "our platform is struggling" conversation, was a full rewrite. That wasn't acceptable here, because it would have meant freezing vendor onboarding for months, at exactly the point growth was accelerating.
Splitting the Problem Instead of Rewriting Everything
Instead of rewriting the whole platform in one large, high-risk effort, we split it along a line that matched where the actual pain was. The customer-facing storefront — the part that needs to be fast and needs to rank well in search, because that's where buyers actually land — moved to Next.js with server-side rendering and incremental static regeneration for product and category pages. The Laravel admin panel and vendor portal, which vendors were already trained on and comfortable with, stayed exactly as they were. The two systems now communicate over a REST API rather than sharing a single monolithic codebase.
This decision deserves more explanation than "split it in two," because the naive version of that idea — just draw a line somewhere and call it microservices — tends to create as many problems as it solves. The line we drew wasn't arbitrary: it followed the actual difference in requirements between the two audiences. Buyers need speed and SEO. Vendors need a stable, familiar tool for managing inventory and orders. Those are different problems with different acceptable trade-offs, and forcing them into one rewrite would have optimized for neither.
The rewrite-vs-don't-rewrite question is usually a false binary. The real question is which parts of the system actually share a bottleneck, and which parts just happen to share a codebase.
Why the Catalog Moved to MongoDB
Product types across 500-plus vendors don't share a schema in any meaningful sense. A clothing vendor's meaningful attributes — size, color, material, fit — look nothing like an electronics vendor's — wattage, compatibility, warranty terms. Forcing that variability into a rigid, normalized MySQL schema was a real part of what made the old search and category pages slow: either you maintain an enormous sparse table full of mostly-null columns, or you maintain an ever-growing set of joined attribute tables, and either way, every search query pays for that complexity.
MongoDB's flexible, document-oriented schema fit the actual shape of the catalog data far better than trying to force a relational model onto inherently variable product attributes. Meanwhile, MySQL stayed the system of record for orders, payments, and payouts — data that genuinely benefits from strong consistency guarantees and doesn't have the same schema-variability problem the catalog does.
Why Not Move Everything to MongoDB?
Because orders, payments, and payouts are exactly the kind of data where you want transactional guarantees and a rigid, well-understood schema — the opposite of what made MongoDB attractive for the catalog. Using one database for everything, in either direction, would have meant compromising on the data model that actually fit at least one half of the problem.
The Payout Problem
Vendors didn't just want a faster storefront — a page-load metric doesn't mean much to someone running a small business through the platform. What they actually wanted was confidence that their money was moving, visible in something close to real time, rather than a screen that says "processing" with no further detail.
Because payouts already sat in the same transactional MySQL store used for orders — the system of record we deliberately kept unchanged in the split — we could expose a near-real-time payout dashboard for vendors without introducing a second source of truth that would then need to be kept in sync with the first. That's a direct benefit of not rewriting the transactional core: the data vendors cared about most was already reliable, and the work was surfacing it, not re-architecting where it lived.
// Simplified payout status query — reads directly from
// the existing transactional store, no separate sync job
SELECT
v.id AS vendor_id,
SUM(CASE WHEN p.status = 'completed' THEN p.amount ELSE 0 END) AS paid_out,
SUM(CASE WHEN p.status = 'pending' THEN p.amount ELSE 0 END) AS pending
FROM payouts p
JOIN vendors v ON v.id = p.vendor_id
WHERE v.id = ?
GROUP BY v.id;
Migration Without a Freeze
The hardest operational constraint was doing this migration while vendor onboarding kept running. A few specific practices made that possible:
- The Next.js storefront and the Laravel admin/vendor portal were deployed as entirely independent services from day one of the migration, so onboarding a new vendor through the (unchanged) Laravel portal never depended on the storefront migration's progress.
- Product catalog data was dual-written to both MySQL and MongoDB during a transition window, with the storefront gradually cut over category by category, rather than a single big-bang cutover.
- A feature flag controlled which storefront (old server-rendered pages or new Next.js pages) served a given category, so we could roll forward — or back — per category without an all-or-nothing deployment.
Best Practices
- Split systems along the line where requirements actually diverge, not an arbitrary technical boundary.
- Pick a database per data shape, not per project — catalog and transactional data had genuinely different needs here, and pretending otherwise would have compromised both.
- Keep a stable system unchanged unless it's actually part of the problem you're solving. The Laravel admin portal wasn't broken, so it didn't need reinventing.
- Migrate incrementally, with a rollback path per unit of migration (per category, in this case), rather than in one irreversible cutover.
Common Mistakes
- Rewriting an entire platform because one part of it — usually the customer-facing surface — is underperforming, when the rest of the system is working fine.
- Forcing inherently variable data (like a multi-category product catalog) into a rigid relational schema out of habit rather than fit.
- Introducing a second data store for a subset of functionality without a clear plan for which store is authoritative for what, which invites subtle sync bugs.
- Doing a big-bang cutover on a live marketplace with active vendors and transactions, instead of an incremental, reversible migration.
Performance and SEO Notes
| Aspect | Before | After |
|---|---|---|
| Category page rendering | Server-rendered, DB-heavy per request | ISR, mostly served from cache |
| Search performance under load | Frequent timeouts at peak | Stable, no reported timeouts |
| Payout visibility for vendors | Daily batch job | Near real-time dashboard |
FAQ
Q: Why keep two databases instead of standardizing on one?
Because the catalog and the transactional core have genuinely different consistency and schema-flexibility needs. Standardizing on one database would have meant accepting a worse fit for at least one half of the system, purely for the sake of operational simplicity.
Q: How did you avoid the REST API between the two services becoming a new bottleneck?
The storefront reads catalog data primarily from its own MongoDB copy rather than calling the Laravel API on every page render — the API is used for the parts that need to stay authoritative and current, like checkout and payout status, not for high-volume read paths.
Vendor Communication During the Migration
A migration like this is invisible to buyers if done well, but it's not invisible to vendors — many of whom rely on the platform for a meaningful share of their income and are understandably anxious about any change to how their storefront presence or payouts work. We ran a lightweight, ongoing communication track alongside the technical migration: a changelog vendors could check, and advance notice before any category was cut over to the new storefront, even though the cutover itself was designed to be functionally invisible from a buyer's perspective.
This turned out to matter more than the purely technical work in terms of vendor trust. A vendor who notices their product pages look slightly different, with no advance explanation, tends to assume something is broken — even if the new version is objectively faster and ranks better in search. Proactive, if brief, communication headed off a meaningful number of support tickets that would otherwise have been "is my store okay?" rather than a genuine bug report.
Search Ranking Continuity
Moving the storefront to a new rendering approach carries real SEO risk if URLs, metadata, or structured data change even slightly during the migration — search engines don't forgive a wholesale URL restructuring gracefully, and a marketplace's organic search traffic is often a majority of its buyer acquisition. We treated URL and structured-data continuity as a hard constraint on the migration, not a nice-to-have: every migrated category page kept its existing URL structure, canonical tags, and JSON-LD product markup exactly as they were before, so the only thing that changed from a search engine's perspective was rendering speed, not addressable content.
What We Verified Before Each Category Cutover
- URL structure matched exactly, with no unannounced redirects introduced.
- Structured data (product schema, pricing, availability) validated identically against Google's structured-data testing tools before and after.
- Core Web Vitals measured for the new rendering path before it went live for that category, not just after.
Conclusion
The marketplace scaled to 500-plus vendors and 10,000-plus products without a single pause in onboarding, storefront load times improved materially under the new ISR-based rendering, and vendor payout visibility went from a delayed daily batch to something close to real time — all while payment processing stayed stable in production throughout the entire migration. None of that required a rewrite of the whole platform; it required correctly identifying which part of the system actually needed to change.