Car rental vertical built parallel to a sales marketplace
Six months after the sales marketplace launched, the client wanted to add car rentals as a parallel vertical. The region's rental market was growing fast — tourism, business travel, expat demand — but the existing rental platforms were little more than phone-number directories. The plan was to give rental companies the same level of tooling that car dealers already had: a dedicated portal with leads, subscriptions, billing, and their own listings. On the consumer side, rental listings would appear alongside sales listings in search, but with their own filters and their own logic.
parallel models, not one schema
The temptation was to reuse the dealer infrastructure. Both verticals have listings, leads, subscriptions, and invoices — structurally, they look almost identical. But rental and sales have fundamentally different data models. A car for sale has a fixed price and a set of specs. A rental listing has pricing tiers — hourly, daily, weekly, monthly — each with its own rate. It has availability windows instead of a sold/not-sold binary. And rental leads behave differently: a buyer lead is "I want to buy this car," while a rental lead is "I need a car from Tuesday to Friday in this city." Forcing both into the same schema would have meant compromises everywhere, so we built separate Prisma models: RentalAdvertisement, RentalLead, RentalLeadOpen, each mirroring the sales-side structure but with rental-specific fields.
geo and proximity search
Geo-awareness matters more for rentals than for sales. A buyer will travel across the country to get the right deal on a car purchase. A renter wants something near the airport or their hotel. Every rental listing carries geo fields — coordinates and a human-readable address — populated via Google Maps API during listing creation. The consumer search supports location-based filtering: "rentals within 15 km of the city center," sorted by distance. This required a different querying approach than the sales catalog — we use PostGIS-style calculations on the PostgreSQL side to filter and sort by proximity without pulling the entire dataset into application memory.
rental portal and billing
The rental portal itself mirrors the dealer portal in its structure — a dedicated subdomain routed by middleware, JWT-based auth scoped to the RENTAL role, and a dashboard with lead management, analytics, billing, and API keys. But the business logic underneath is different. Rental subscription plans are priced differently than dealer plans. Rental lead classification uses different signals (booking request vs. availability check vs. phone reveal). And the invoicing cycle accounts for the fact that rental companies often operate seasonally — a plan that makes sense in summer tourist season doesn't make sense in January.
unified search, separate queries
The consumer-side search experience needed to feel unified even though the data sources are separate. When a user searches the marketplace, they can toggle between "Buy" and "Rent" modes. The filters adapt — in buy mode, you see mileage, year, and price range; in rent mode, you see rental duration, daily rate range, and location radius. Under the hood, these hit completely different query paths, but the UI transition is seamless. We built a shared filter component shell with pluggable filter sets, which kept the codebase DRY without forcing the rental and sales data into artificial alignment.
mvp scope and takeaway
The hardest scoping decision was the availability calendar. Rental companies wanted it badly — a visual calendar showing which dates each car is booked and which are free. Consumers wanted it too, so they could see at a glance whether a car was available for their dates. We scoped it out of the MVP. The engineering complexity of a real-time availability system — with concurrent booking protection, timezone handling, cancellation policies, and partial-day logic — would have doubled the timeline. Instead, we shipped with a simpler model: rental companies manually mark listings as available or unavailable, and booking requests go through the lead system for manual confirmation. It works, but it's the most-requested feature in the feedback backlog.
The takeaway from the rental vertical: parallel doesn't mean identical. Sharing UI patterns saved time. Sharing data models would have cost more time than it saved. The discipline of building separate Prisma models for rental entities — even when they looked 80% similar to the sales-side models — meant that every rental-specific feature (pricing tiers, geo search, seasonal billing) could be implemented cleanly instead of hacked around a shared schema. The availability calendar remains unfinished business, and it's a reminder that sometimes the right MVP decision is to ship without the feature everyone wants.
Stack
Frontend: Next.js 15, React 19, Tailwind CSS, TanStack Query
Auth & routing: jose (JWT), Next.js middleware (RBAC, subdomain routing)
Backend: Next.js Route Handlers, Prisma 6, PostgreSQL
Geo: Google Maps API (geocoding, distance calculations)
