· 1 min read · Zain Akram
Server-render the marketplace, hydrate what users touch
How Tour27 went from an invisible single-page app to a travel marketplace where every tour page ranks, without giving up an interactive UI.
- Next.js
- SEO
- NestJS
- Architecture
Tour27 is a travel discovery marketplace. Its growth depended on search traffic for queries like "two day desert tour from Dubai". The original build was a client-rendered single-page app: fast once loaded, and almost entirely invisible to crawlers.
The rebuild, in three decisions
Static generation for tour pages. Every tour has a stable URL and content that changes rarely. Those pages are generated at build time and revalidated when an operator edits them. They are plain HTML with full metadata, structured data for the product and breadcrumbs, and images with real alt text. Crawlers get everything in the first response.
Server rendering for search and listings. Search results depend on the query, so those render on the server per request with sensible caching. Filters update the URL, which means a filtered search is itself a shareable, indexable page.
Hydrate only the interactive bits. The itinerary builder, the booking enquiry form and the map are client components. Everything else ships as server components with no JavaScript cost. Core Web Vitals went from failing to passing without a performance sprint.
The backend had to change too
The NestJS API is split by domain: tours, itineraries, leads, content. The CMS writes to content and triggers revalidation of the affected pages, so operators publish without a deploy. Lead capture writes to its own module with rate limiting and spam checks, because a public marketplace gets hammered by bots the moment it ranks.
Structured data earned its keep
Adding product, breadcrumb and FAQ structured data gave tour pages rich results within weeks. It is the cheapest SEO work there is, and most marketplaces skip it.
The outcome
Every tour page is indexable and fast, operators self-serve content changes, and organic search became the primary acquisition channel. The pattern generalises: render what search engines and first-time visitors need on the server, and spend JavaScript only where a human interacts.