WhereNext.to

A travel discovery and booking platform where visitors browse destination guides and volunteer experiences, then book them through a bespoke server-side cart and checkout. Built on Next.js over a shared Strapi backend that powers two products.

WhereNext homepage featuring a hero image of people white-water rafting in Bali, with navigation menu, search bar, and sidebar showing travel experiences including Adventure week in Bali, Protecting Sea Turtles in Tanzania, and Culture Week in Uganda
Client

WhereNext.to

Status

Live

Introduction

A travel content and booking platform where people browse destination guides, articles, and volunteer or adventure experiences, then book them through a full cart and checkout. It's one product built from two codebases — a public-facing website and a custom content management backend that also powers a sister product, WhereNext.ai. I was the principal developer from 2021 through 2026, writing the substantial majority of both codebases (roughly 3,500 of ~4,400 frontend commits and ~1,970 of ~2,300 backend commits) alongside a product owner, designers, and occasional contract developers.

WhereNext.to experiences page displaying a search interface with region and category filters on the left, and a grid of travel experiences including volunteering in Chile, adventure activities in Nepal, and teaching programs

The Challenge

Build a travel discovery and booking platform that combines editorial destination guides, volunteer and adventure experiences, and third-party tour inventory with a full cart and checkout, while maintaining content across two separate products from a single shared CMS backend.

The Outcome

A complete, live travel commerce platform with a bespoke server-side cart solving a genuinely tricky cross-system authentication merge

Tech Stack

Frontend

  • Next.js
  • React
  • Apollo Client
  • React InstantSearch
  • Mapbox
  • Mapbox GL
  • SCSS
  • React Hook Form
  • SWR
  • React Map GL
  • Mapbox Search JS
  • next-cloudinary
  • @hookform/resolvers
  • markdown-to-jsx
  • isomorphic-dompurify
  • localStorage

Language

  • JavaScript
  • TypeScript
  • GraphQL

Backend

  • Strapi
  • REST
  • Algolia
  • Viator
  • Google Places
  • Clerk
  • HubSpot
  • Cloudinary
  • Google OAuth
  • Viator API
  • Google Places API
  • OpenWeatherMap API

DevOps

  • Sentry
  • Datadog
  • Vercel
  • Dokploy
  • Docker

Tooling

  • Yup
  • Webpack
  • Google Tag Manager
  • @next/bundle-analyzer
  • Next.js 16 (pages router) with React 19 on the frontend — roughly 46,000 lines of JavaScript and TypeScript across destination, experience, guide, story, cart, and checkout routes.
  • Strapi as a headless CMS and application backend, spanning ~40 API domains (destinations, experiences, travel guides, articles, cities, countries, regions, continents, partnerships, affiliates, orders and more) with heavily customised controllers and routes rather than generated CRUD. The same instance serves both WhereNext.to and WhereNext.ai, so it's shared infrastructure across two products.
  • Data is read through both GraphQL via Apollo Client and REST, with Next.js API routes acting as a server-side proxy layer that keeps Strapi and third-party API keys off the client.
  • Algolia powers site search and faceted browsing, with React InstantSearch on the frontend and a dedicated bulk-index endpoint on the backend.
  • Viator supplies bookable tour and experience products, including free-text product search; Mapbox GL (via react-map-gl and Mapbox Search JS) and Google Places handle maps, geocoding, and location autocomplete.
  • Clerk for authentication, HubSpot for CRM and embedded forms, Cloudinary (through next-cloudinary) for image delivery.
  • SCSS Modules for component-scoped styling — every component has a co-located .module.scss — with a shared styles include path for tokens.
  • Forms built on React Hook Form with Yup schemas via @hookform/resolvers; SWR for client-side data fetching and revalidation.
  • Observability through Sentry (with source map upload and an ad-blocker-resistant tunnel route) and Datadog RUM plus browser logs.
  • Frontend deployed on Vercel with hardened response headers (CSP, nosniff, frame options) defined in vercel.json; the CMS runs on self-managed infrastructure orchestrated with Dokploy.
WhereNext.to map search interface displaying an interactive world map with regional navigation buttons for Africa, Asia, Caribbean, Central America, Europe, Middle East, Oceania, North America, and South America

Key Implementation Details

Hand-rolled server-side cart and checkout — The hardest and most interesting part of the project, designed and built from scratch before AI assistance was available.

  • Cart state lives server-side in Strapi rather than in browser storage, so a cart survives device changes and can be reconciled against live product data at checkout.
  • Composed of a deep component tree — cart items with per-item option and traveller inputs, modals, promo code chips and validation, summary line items, and add-to-cart popups surfaced directly from experience pages.
  • Checkout is schema-validated end to end (React Hook Form + Yup) before orders are written to the backend's order domain.

Deferred cart merging across authentication — Solving an eventual-consistency problem between two systems.

  • When a logged-out visitor with a populated cart signs up, the authenticated user may not exist in Strapi yet, because identity is owned by Clerk and propagation isn't instant.
  • Rather than fail the merge or silently drop the cart, the backend stamps a merge_with_auth_id flag onto the anonymous cart and completes the merge later, once the user record materialises.
  • A small edge case with an outsized commercial cost if handled naively — exactly the kind of race that quietly loses customers.

A large third-party integration surface — A significant share of total effort went into normalising external data into one coherent content model.

  • Viator products are fetched either live via a bulk-product proxy route or from an Algolia mirror index, with a source switch so the same UI works from either path and falls back gracefully.
  • A custom Algolia search client suppresses empty queries (returning synthetic empty result sets) and honours an explicit empty-search-trigger sentinel, avoiding wasted search operations on first paint.
  • Google Places integration supports both the legacy and new Places APIs behind one function signature, with a field mask tuned to only the place attributes the product actually renders.

Map-driven discovery — Search and browsing are as much spatial as textual.

  • Mapbox-based map search with a synchronised sidebar, custom markers, and distinct popup components for countries, destinations, in-house experiences, and Viator products.
  • A spiderfier component fans out overlapping markers so dense clusters of experiences remain individually clickable.
  • Maps are dynamically imported so the heavy GL bundle never blocks non-map routes.

Content composition and rendering pipeline — Editors assemble pages without developer involvement.

  • A block-based renderer maps Strapi dynamic-zone components onto React blocks (two-column text/image, icon-box rows, card rows, flexible rows), so page layouts are content-driven.
  • Rich text is rendered with markdown-to-jsx and sanitised through isomorphic-dompurify as a documented, enforced convention.
  • Image delivery is tuned with explicit deviceSizes/imageSizes breakpoints, a 90-day minimum cache TTL, and a remote-pattern allowlist covering Cloudinary, Mapbox, and partner CDNs.
  • Sitemaps are generated at build time from a webpack server hook, and redirects live in a single versioned data module rather than scattered config.

Infrastructure migration to Dokploy — Recent work moving the CMS off managed hosting onto self-orchestrated infrastructure.

  • Container-based deployment of the Strapi backend managed through Dokploy, giving direct control over environments and the database.
  • Because the canonical repository sits on the product owner's account and couldn't be connected to Dokploy directly, deployment runs from a maintained fork kept in sync — a deployment artifact rather than a separate codebase.

What I Contributed

  • Designed and built a server-side cart and checkout system from scratch, including a deferred merge mechanism that resolves a race condition between Clerk authentication and Strapi user provisioning to prevent cart loss at signup
  • Architected and implemented a custom Strapi backend spanning roughly 40 API domains with heavily customised controllers and routes, shared as infrastructure across two products
  • Integrated a large third-party data surface: Viator for bookable tours with dual-path fetching, Algolia for search with a custom client, Google Places and Mapbox for location and maps, plus Clerk, HubSpot, Cloudinary, Sentry, and Datadog
  • Migrated CMS infrastructure to self-managed Dokploy orchestration, maintaining a deployment fork to work around repository access constraints

Outcome

A complete, live travel commerce platform: a bespoke server-side cart and checkout with a genuinely tricky cross-system authentication merge solved properly, a ~40-domain custom Strapi backend that now serves two products, and a broad integration surface tying Viator inventory, Algolia search, and Mapbox/Google Places geodata into one coherent content model. Five years of continuous ownership across both codebases meant the architecture, the observability tooling, and eventually the deployment infrastructure were all mine to design — the product never found an audience commercially, but the engineering behind it stands on its own.