Skip to content
All projectsPlatformAug 2024 to Jun 2025

Pandooin

A three-app travel platform on a Turborepo monorepo. Booking, payments, partner tooling and search, built with a team and shipped to two environments.

Interface

  • Representative interface still for Pandooin
  • Representative interface still for Pandooin

Representative interface stills.

Role
Frontend Developer, platform and delivery
Organisation
PT Teknologi Pandu Wisata
Period
Aug 2024 to Jun 2025
Stack
  • Next.js
  • TypeScript
  • Turborepo
  • Bun
  • Tailwind
  • MUI
  • NextAuth
  • TanStack Query
  • Algolia
  • PostHog
  • Bugsnag
  • AWS S3
  • Cloudflare
  • GitHub Actions
  • VPS
  • Figma
  • Jira

Short explanation

Pandooin is a travel platform. When I joined it ran on a monolithic Laravel and Vue codebase, and moving off that was the entry point rather than the job. What the business needed was a platform three separate products could be built on, so that is what we built, with a team, over ten months.

One constraint shaped everything: Pandooin's customers arrive through organic search. Anything that cost rankings cost more than it saved.

  • A Turborepo monorepo on Bun carrying three applications and a shared component library.
  • 35 routes in the customer marketplace: search, checkout, orders, articles, campaigns.
  • A partner console with an availability calendar, dashboards, listing editor and media upload.
  • Multilingual routing, so language is part of the URL and every page stays indexable.
  • Two environments on VPS, deployed by GitHub Actions, behind Cloudflare, media on AWS S3.

Why a monorepo

Three products share most of a domain. A customer books an experience, a partner lists and manages it, and Zamrood serves its own audience on the same catalogue. Kept in three repositories they drift: three button components, three date formatters, three opinions about what a price looks like.

So the shared surface lives in internal packages, and each application consumes them. Not just a component library, but the ESLint, Tailwind and TypeScript configurations too. Consistency is enforced by the build rather than by review.

Each application is wired in as a git submodule, so a team can cut a release for one product without dragging the other two along. That is the part people usually get wrong about monorepos: sharing code should not mean shipping in lockstep.

Three applications, one foundation

Customer marketplace, partner console, Zamrood.

What I built in the customer app

This is the public, search-facing product and the one carrying revenue. It grew to 35 routes.

Search

Algolia InstantSearch with search insights.

Travel catalogues are only as good as the search over them. Insights matter as much as the index: knowing what people searched for and did not book is a product signal, not an analytics nicety.

Two checkout flows

Activities and experiences price differently, so they book differently.

Both end at orders, payment method selection and a confirmation, but the package selection ahead of that is not shared. Forcing one flow to serve both would have made each worse.

Authentication

Google OAuth, credentials, phone OTP, email verification, password reset.

Login and register are parallel routes intercepted as modals, so signing in does not throw away the page you were looking at. On a marketplace, the moment you interrupt someone browsing is the moment you lose the booking.

Multilingual routing

A locale segment on every route.

Language sits in the URL rather than in a cookie, so each translation is independently crawlable. That is the SEO constraint expressed as an architectural decision instead of an afterthought.

What I built in the partner console

The operator's half. Partners list experiences, manage availability and watch how they are performing, and none of that is the same problem as browsing a catalogue.

Availability calendar

The screen partners live in.

Dashboards and listing tools

Charts, data tables, a rich text editor and drag-and-drop media upload.

Delivery and infrastructure

GitHub Actions runs a build gate on every pull request, then deploys over SSH to the server, with beta and production on separate branches and separate machines. Cloudflare sits in front; media is served from AWS S3 in the Singapore region.

Nothing here is managed-platform convenience. Knowing why each of those steps exists is the difference between deploying and understanding a deployment.

Production visibility is three tools doing three jobs: PostHog for what people do, Bugsnag for what breaks and how slowly it renders, GTM and Meta Pixel for where people came from.

Two environments, branch per environment

Beta first, always. Production is what survived it.

The part that was actually hard

Migrating something that cannot stop. The business ran on the old platform the entire time, so the new stack had to reach parity on a page before that page could switch. Parity includes everything nobody wrote down: a query parameter some campaign depends on, a redirect that has been there for years.

Working across a team was the other half. Backend, UI/UX and frontend each held part of the picture, coordinated through GitHub, Jira, Postman and Figma. The genuinely expensive bugs came from a shape mismatch between what the API returned and what the design assumed, and the fix for that is conversation, not code.

Three products on one monorepo adds a failure mode of its own: a change to a shared package is a change to three applications at once. The configuration packages exist partly so the type checker catches that before a reviewer has to.