Archipelago International — Corporate Website

Project Overview
Archipelago International is one of Southeast Asia's largest hotel management companies, operating 200+ properties across 13+ hotel brands — including Aston, Harper, Quest, Neo, and favehotel. This is their primary corporate web presence: the single platform that communicates the group's brand identity, showcases its hotel portfolio, drives direct bookings, and attracts talent.
The site needed to perform at enterprise scale — fast global load times, airtight SEO, seamless booking flows, and a maintainable codebase that multiple developers could extend across many brands and services without stepping on each other.
Business Outcomes
- Unified digital presence for 13+ hotel brands under a single, consistent experience
- Reduced time-to-market for new brand and service pages through a scalable component architecture
- Improved organic search visibility via structured Schema.org data (12+ types) and pre-rendered pages with full meta coverage
- Enabled direct bookings and appointment scheduling through the Integrated Booking Engine, reducing dependency on third-party OTAs
- Streamlined talent acquisition with a self-contained career portal — job search, detail pages, and multi-file applications — all without a third-party ATS frontend
- Faster page delivery globally through AWS CloudFront + Cloudflare CDN with immutable cache headers on all static assets
What I Built
Multi-Brand Discovery System A unified brand hub presenting 13+ hotel brands, each with dedicated landing pages, hotel listings, and location-based search — all powered by a single typed API service layer.
Integrated Booking Engine (IBE) A custom multi-step booking flow covering room search, multi-guest configuration, pricing tiers, appointment scheduling, and payment — built as an isolated component system (components/integrated-booking-engine/) that can be surfaced anywhere on the site.
Career Portal Full job search with filters, individual job detail pages, and a multi-file application form (CV + supporting documents) with FormData serialization and server-side submission — no third-party job board embed required.
News & Content Hub A paginated news listing and article detail system with PDF export capability, Schema.org NewsArticle structured data, and dynamic Open Graph metadata per article.
Adaptive Header & Navigation A dual-mode header (desktop full-nav / mobile drawer) with scroll-aware transparency — transparent with a vignette on the homepage hero, opaque everywhere else. Manages dialogs (Book Now, Contact Us, Team Profiles) through a centralised dialog registry in the app store.
HLS Video Streaming Cloudflare Stream-hosted video with a custom player built on Hls.js + Vidstack, supporting adaptive bitrate streaming and fallback handling.
SEO & Structured Data Layer A useSEO() composable wrapping all OpenGraph, Twitter Card, and canonical tag logic. Paired with 12+ Schema.org structured data types (Organization, Hotel, Article, JobPosting, BreadcrumbList, Service, VideoObject, Event, and more) via nuxt-schema-org. Auto-generated sitemap with per-route priorities.
Form & Security Layer All public-facing forms (inquiry, career application, appointment, brochure download) are protected by Cloudflare Turnstile bot detection and managed through a Pinia form store for consistent validation and state handling.
Stack
- Nuxt 3.17 (SSG)
- Vue 3.5, TypeScript 5.5
- Vuetify 3.7
- Pinia 3
- Vite 6
- SCSS, Vuetify theming
- @nuxt/image — WebP, responsive density variants, multi-CDN
- Video.js, Vidstack, Hls.js
- Splide Vue, Swiper
- dayjs, @vuepic/vue-datepicker
- SEO: nuxt-schema-org, @nuxtjs/sitemap, @nuxtjs/robots
- Google Tag Manager, Google Analytics 4
- Cloudflare Turnstile
- Cloudflare Stream
- AWS CodeBuild → S3 → CloudFront
Rendering Strategy
The site is fully Static Site Generated (SSG) — all 14+ routes are pre-rendered at build time and served as static HTML from CloudFront. This delivers sub-100ms TTFB globally with zero cold starts.
At the component level, the project enforces a lazy component loading pattern: every page file (pages/*.vue) contains only SEO metadata and a resolveComponent() call. All logic, state, and templates live in a paired component file (components/pages/[name]/Content.vue). Vite treats each of these as a separate chunk, so the browser only downloads the code it needs for the current route.
pages/news.vue ← SEO metadata + resolveComponent() only
components/pages/news/Content.vue ← All logic, state, and template
Additional build-time optimisations:
- Manual Rollup chunk splitting per
node_modulespackage (video, swiper, etc. in separate chunks) - CSS code splitting enabled
- Nitro asset compression
- Immutable cache headers on
/_nuxt/**(1 month) and/images/**(1 week) - Resource hints with visibility-based prefetch
Key Engineering Challenges Solved
1. Code splitting at page and feature boundaries With 13+ brands and 14+ pages sharing a large UI component library, naive bundling would have produced a monolithic JS payload. The lazy component pattern combined with manual Rollup chunk configuration ensures each route loads only its own code, with shared vendor packages cached separately in long-lived immutable chunks.
2. Type-safe API consumption across a multi-endpoint backend The backend exposes two separate API bases (content API and brand/hotel API). Rather than scattering raw fetch calls, all endpoints are centralised in services/archipelago-service.ts using a generic fetchJSON<T>() wrapper. Every function is fully typed, error handling is consistent, and adding a new endpoint is a one-line change.
3. Scroll-aware, dialog-aware header state without layout shift The header needs to be transparent on the homepage hero, opaque when scrolled, and opaque whenever any dialog is open — across all breakpoints. This was solved by tracking scroll position, dialog registry state, and current route in the Pinia app store and deriving the header style from a single computed property, eliminating competing state sources and layout shift.
4. HLS video in a statically generated Vue app Cloudflare Stream delivers video as HLS, which native <video> elements don't support universally. Hls.js was integrated with a Vidstack wrapper to handle adaptive bitrate streaming, codec detection, and graceful fallback — all lazily loaded so the HLS parser is never downloaded on pages without video.
5. Multi-file career application submissions via static frontend Career applications require CV + supporting document uploads from a static site with no backend server. FormData serialisation is handled client-side, multipart POST requests go directly to the API with Bearer token auth, and Pinia form store manages upload progress, validation state, and success/error feedback — no server middleware required.
6. Schema.org coverage at scale without repetition Each page type (article, job posting, hotel, service) needs different structured data. A set of composables (useBreadcrumbSchema(), useContactPageSchema(), etc.) encapsulate the Schema.org generation logic per type. Page files call only the composables they need, keeping structured data co-located with SEO metadata and out of component logic.