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 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.
The three Pandooin applications sharing one component library
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.
Search results across the Pandooin activity and experience catalogue
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.
Checkout flow with package selection and payment method
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.
Login presented as a modal over the current page
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.
Partner availability calendar
Dashboards and listing tools
Charts, data tables, a rich text editor and drag-and-drop media upload.
Partner dashboard with performance charts
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.
GitHub Actions build and deploy runs across beta and production
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.

