Case Study · Next.js · Booking System
Case Study: How I Built a Taxi Booking Website with Next.js
Full technical case study of building a taxi and chauffeur booking website — real-time availability, WhatsApp integration, Google Maps, and local SEO strategy.
Jatinder Sandhu
Client
Chauffeur & Taxi Firm
Category
Local Transport
Stack
Next.js · Google Maps
Timeline
4 weeks
The Brief
This client runs a local taxi and chauffeur service — airport transfers, corporate accounts, and pre-booked long-distance rides. Before the rebuild, they were taking every booking over the phone or through Facebook Messenger, which meant missed calls after hours, double-bookings on busy weekends, and zero presence on Google for the searches that actually convert, like “airport taxi near me” and “chauffeur service [city]”.
The ask was straightforward on paper: a website where customers could check vehicle availability, get an instant fare estimate, and book a ride without picking up the phone. The harder part was doing this without a call centre budget and without the client wanting to manage a complex admin panel.
Why Not a Ready-Made Booking Plugin
There are WordPress taxi-booking plugins on the market, and I evaluated a few before ruling them out. Three things pushed me toward a custom Next.js build instead:
- Off-the-shelf plugins render fare calculators and booking forms client-side with heavy JS bundles — page speed tanked in every demo I tested, and speed directly affects local map-pack rankings.
- The client needed fare logic specific to their fleet (per-mile rate, airport surcharge, waiting time) — plugin fare engines are generic and hard to customise without touching PHP.
- They wanted the booking to end in a WhatsApp confirmation, not an email they might not check between jobs — most plugins are built around email-only workflows.
Architecture Decisions
Google Maps Places Autocomplete for pickup/drop-off
Customers type a pickup and destination and the Places Autocomplete API resolves it to a real address with lat/lng. This removes the classic booking-form problem of vague or misspelled addresses that cause driver confusion later.
Server-side fare calculation via Route API
Once pickup and drop-off are set, a server action calls the Google Routes API to get distance and estimated drive time, then runs it through the client's rate table (base fare + per-mile + airport surcharge + time-of-day multiplier). The fare estimate is calculated server-side so it can't be tampered with client-side.
Real-time-style availability without a live dispatch system
A full live-GPS dispatch system was out of budget and out of scope. Instead, the booking form checks a simple time-slot table — each vehicle class has blocked slots pulled from confirmed bookings — so customers only see genuinely available windows instead of a permanently-open calendar.
WhatsApp as the confirmation channel
On submit, the booking is saved and a pre-filled WhatsApp message is generated with pickup, drop-off, date, time, and vehicle class. The customer taps through to send it, and the driver dispatcher replies to confirm — fast, familiar, and no missed-email problem.
Tech Stack
Framework
Next.js 14 (App Router)
Styling
Tailwind CSS
Maps & Routing
Google Maps Platform
Database
PostgreSQL (bookings, slots)
Booking Logic
Server Actions
Confirmation
WhatsApp wa.me links
Hosting
Vercel (Edge Network)
SEO
Next.js Metadata API
Analytics
Google Analytics 4
Inside the Fare Calculation
The fare engine had to match what the client's dispatchers already quote over the phone, or customers would distrust the online price the moment they called to double-check. Here is a simplified version of the server-side calculation:
// Server action: calculate fare estimate
async function calculateFare(pickup: LatLng, dropoff: LatLng, vehicleClass: string) {
const route = await getRoute(pickup, dropoff) // Google Routes API
const rate = RATE_TABLE[vehicleClass]
let fare = rate.base + route.miles * rate.perMile
if (isAirport(pickup) || isAirport(dropoff)) fare += rate.airportSurcharge
if (isPeakTime(route.departureTime)) fare *= rate.peakMultiplier
return { fare, etaMinutes: route.durationMinutes }
}
Running this server-side matters for two reasons: the rate table stays private (competitors can't scrape it from the client bundle), and the number the customer sees can't be manipulated by editing form values in devtools.
Local SEO Strategy
For a taxi business, almost all commercial intent is local and near-immediate — “airport taxi [city]”, “chauffeur service near me”, “book a cab to [airport name]”. The SEO plan was built entirely around that intent:
- Dedicated landing pages per route and service type — airport transfers, corporate accounts, long-distance — each targeting a specific keyword cluster rather than one generic homepage.
- LocalBusiness JSON-LD schema with service area, phone number, and operating hours so Google can surface rich results and map-pack eligibility.
- Google Business Profile fully optimised and linked, with the website driving reviews back through a post-ride WhatsApp follow-up.
- Fast Core Web Vitals from static rendering on non-booking pages — critical because Google explicitly weights page experience for local service queries.
- Structured FAQ content on each service page answering real questions customers ask on the phone, doubling as FAQ schema for rich snippets.
Results
96/100
Lighthouse Score
4 wks
Build to Launch
40%
Bookings via Website (3 months)
Top 3
Map Pack Rankings (5 months)
Within three months, about 40% of new bookings were arriving through the website instead of phone calls — most of them outside normal office hours, which were bookings the business would previously have lost entirely. Within five months, the site was ranking in the local map pack for the client's two highest-value keywords, and phone call volume for “just checking availability” dropped noticeably since customers could now see it themselves.
Key Lessons
“Real-time” does not always mean live GPS tracking
The client asked for “real-time availability”, but what they actually needed was accurate slot availability, not a live fleet-tracking system. Understanding the real requirement behind the request saved weeks of unnecessary engineering.
Keep the confirmation channel where the customer already lives
Email confirmations for a taxi booking are the wrong channel — people book a ride and expect a fast reply, not an inbox check. Routing confirmation through WhatsApp matched how the client's customers already communicate.
Server-side pricing logic is a trust and security decision, not just a technical one
Calculating fares server-side protected the rate table from competitors and prevented price tampering — a detail that matters more for a booking site handling real transactions than it would for a content site.
Conclusion
This project reinforced something I see across almost every local service business I work with: the biggest wins rarely come from the most complex feature on the list. They come from removing friction from the one action that actually makes money — in this case, turning a phone call into a self-service booking that works at 11pm on a Sunday.
If you run a taxi, chauffeur, or local transport business and are still taking every booking by phone, there is a real case for a lean, fast booking site built around how your customers already communicate.
Want a booking system like this for your business?
I build fast, conversion-focused booking and lead-gen websites for local service businesses. Get in touch to discuss your project.
Get in Touch →