Frontend Overview
The Volare frontend is built with Astro 5 and Tailwind CSS v4, providing optimal performance and SEO-friendly travel product pages.
Technology Stack
Section titled “Technology Stack”| Component | Technology | Version |
|---|---|---|
| Framework | Astro | 5.x |
| Styling | Tailwind CSS | 4.x |
| Runtime | Node.js | 18+ |
| Package Manager | pnpm | 8+ |
| Deployment | Cloudflare Pages | - |
Architecture
Section titled “Architecture”Frontend├── Pages (File-based routing)│ ├── Static pages (Astro components)│ ├── Dynamic routes ([market], [slug])│ └── API endpoints (.ts files)├── Layouts (Shared structure)│ └── MainLayout.astro├── Components (Reusable UI)│ └── Feature-specific components└── Shared (Utilities & config)Key Features
Section titled “Key Features”- Zero JavaScript by default - Astro components ship no JS unless needed
- File-based routing - Pages map directly to URLs
- Dynamic routes - Market and product slug parameters
- SSR/SSG flexibility - Server or static rendering per page
- Tailwind CSS v4 - Utility-first styling with CSS variables
Project Structure
Section titled “Project Structure”frontend/├── src/│ ├── components/ # Reusable Astro components│ ├── features/ # Feature-specific components│ ├── layouts/ # Page layouts│ │ └── MainLayout.astro│ ├── pages/ # File-based routing│ │ ├── index.astro│ │ ├── health.ts│ │ ├── [market]/ # Dynamic market routes│ │ └── products/│ ├── shared/ # Shared utilities│ └── styles/ # Global styles├── public/ # Static assets├── astro.config.mjs # Astro configuration└── package.jsonRouting
Section titled “Routing”Static Pages
Section titled “Static Pages”src/pages/index.astro -> /src/pages/about.astro -> /aboutsrc/pages/contact.astro -> /contactDynamic Routes
Section titled “Dynamic Routes”src/pages/[market]/index.astro -> /{market}src/pages/[market]/[...path].astro -> /{market}/{tourPath}/{slug} -> /{market}/{lang}/{tourPath}/{slug}src/pages/[market]/about-us.astro -> /{market}/about-ussrc/pages/[market]/destinations/[country].astro -> /{market}/destinations/{country}src/pages/[market]/booking/[reference].astro -> /{market}/booking/{reference}Product Detail Routes
Section titled “Product Detail Routes”Product pages use localized tour path slugs from market configuration:
| Pattern | Example | Description |
|---|---|---|
/{market}/{tourPath}/{slug} | /es/circuito/to-maldivas | Default language |
/{market}/{lang}/{tourPath}/{slug} | /es/ca/circuit/to-maldivas | Specific language |
The tourPath segment is configured per-market and language (e.g., “circuito” for Spanish, “circuit” for Catalan).
Preview Mode: Product pages accept ?preview=<token> to display draft products using signed API URLs.
Source: frontend/src/pages/[market]/[...path].astro
API Endpoints
Section titled “API Endpoints”src/pages/health.ts -> /health (JSON response)Astro Components
Section titled “Astro Components”Basic Component
Section titled “Basic Component”---// Component script (runs at build time)interface Props { title: string; description?: string;}
const { title, description } = Astro.props;---
<article class="product-card"> <h2>{title}</h2> {description && <p>{description}</p>}</article>
<style> .product-card { @apply p-4 rounded-lg shadow-md; }</style>Layout Usage
Section titled “Layout Usage”---import MainLayout from '../layouts/MainLayout.astro';---
<MainLayout title="Page Title"> <main> <h1>Content here</h1> </main></MainLayout>Styling with Tailwind CSS v4
Section titled “Styling with Tailwind CSS v4”Configuration
Section titled “Configuration”Tailwind v4 uses CSS-first configuration:
@import "tailwindcss";
@theme { --color-primary: #3b82f6; --color-secondary: #64748b;}Usage in Components
Section titled “Usage in Components”<div class="bg-primary text-white p-4 rounded-lg"> <h1 class="text-2xl font-bold">Title</h1> <p class="text-secondary">Description</p></div>Development
Section titled “Development”Commands
Section titled “Commands”# Start development serverpnpm dev
# Build for productionpnpm build
# Preview production buildpnpm previewDevelopment Server
Section titled “Development Server”The frontend runs at http://localhost:4321 by default.
API Integration
Section titled “API Integration”Fetching from Backend
Section titled “Fetching from Backend”---const market = Astro.params.market;const lang = 'en';
const response = await fetch( `${import.meta.env.API_URL}/api/${market}/${lang}/products`);const { data: products } = await response.json();---
<ul> {products.map((product) => ( <li> <a href={`/${market}/products/${product.url_slug}`}> {product.title} </a> </li> ))}</ul>Image Optimization
Section titled “Image Optimization”Use Astro’s <Image /> component for optimized images:
---import { Image } from 'astro:assets';import heroImage from '../assets/hero.jpg';---
<Image src={heroImage} alt="Hero image" width={1200} height={600}/>Meta Tags
Section titled “Meta Tags”---// In layout or pageconst { title, description } = Astro.props;---
<head> <title>{title} | Volare Travel</title> <meta name="description" content={description} /> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /></head>Structured Data
Section titled “Structured Data”<script type="application/ld+json"> {JSON.stringify({ "@context": "https://schema.org", "@type": "Product", "name": product.title, "description": product.description })}</script>Product Pages
Section titled “Product Pages”Product detail pages use a magazine-style editorial layout with an interactive trip configurator in a slide-out drawer.
Page Sections
Section titled “Page Sections”| Section | Component | Description |
|---|---|---|
| Hero | viaje/Hero | Full-bleed hero with breadcrumbs, country label, duration, price, and Reserve CTA |
| TextItinerary | viaje/TextItinerary | Day-by-day itinerary with background image |
| InfoSection | viaje/InfoSection | Accordion with highlights, meals, accommodation details, not-included |
| AccommodationCarousel | viaje/AccommodationCarousel | Horizontally scrollable accommodation cards |
| ImageSection | viaje/ImageSection | Gallery grid (single or triple layout) |
| Microrelato | destination/MicrorelatoSection | Guest story section (from product translation, shared with product pages) |
| QandASection | viaje/QandASection | Tabbed FAQ categories with accordion |
| FloatingActions | ProductFloatingActions | Sticky Reserve/Details buttons + TravelDetailsModal + ConfiguratorDrawer |
Product Page Architecture
Section titled “Product Page Architecture”src/features/product/├── types/│ ├── product.ts # Product API response interfaces│ └── configurator.ts # TripConfigurator types├── mappers/│ └── productToViajeProps.ts # Maps API data to Viaje component props├── components/│ ├── ProductPage.astro # Main page assembler (uses Viaje components)│ ├── ProductFloatingActions.tsx # Composes FloatingButtons + TravelDetailsModal + ConfiguratorDrawer│ ├── ConfiguratorDrawer/ # Slide-out configurator wrapper│ │ └── ConfiguratorDrawer.tsx│ ├── TripConfigurator/ # Trip configuration form│ │ └── TripConfigurator.tsx│ └── ReserveButton.tsx # Opens configurator drawer└── index.ts
src/components/viaje/ # Shared Viaje design components├── Hero/ # Hero with breadcrumbs, label, CTA├── TextItinerary/ # Day-by-day itinerary├── InfoSection/ # Accordion-based info section├── AccommodationCarousel/ # Accommodation cards carousel├── ImageSection/ # Single or triple image gallery├── QandASection/ # Tabbed FAQ accordion├── FloatingButtons/ # Sticky bottom action buttons└── ExperienceModal/ # Experience detail modalState Management
Section titled “State Management”ProductFloatingActions is a single React island that composes all interactive product page elements: FloatingButtons, TravelDetailsModal, and ConfiguratorDrawer. It manages open/close state locally via React state.
- Hero CTA and FloatingButtons “Reserve” button open the configurator drawer
- FloatingButtons “Details” button opens the TravelDetailsModal
ConfiguratorDrawerrendersTripConfiguratorinside theDrawercomponentDrawercomponent handles escape key, backdrop click, and body scroll lock
Shared UI Components
Section titled “Shared UI Components”src/shared/ui/└── Drawer/ └── Drawer.tsx # Reusable slide-out panel with motion animationsLayout
Section titled “Layout”Product pages use PublicLayout.astro — the standard public layout with header and footer. The product page route ([...path].astro) fetches both the product data and leading price, then passes them to ProductPage.astro. A mapper (productToViajeProps.ts) transforms the API response into props for each Viaje design component.
Data flow: [...path].astro fetches product + leading price -> ProductPage.astro maps via mapProductToViajeProps() -> renders Viaje components (Hero, TextItinerary, InfoSection, etc.) + ProductFloatingActions (React island with configurator drawer).
Source: frontend/src/features/product/
Landing Page (Homepage)
Section titled “Landing Page (Homepage)”The homepage landing page provides an immersive brand introduction with animated carousels and editorial content.
Landing Route Pattern
Section titled “Landing Route Pattern”/{market}/homeExample: /es/homeLanding Page Sections
Section titled “Landing Page Sections”| Section | Description |
|---|---|
| LandingNavbar | Split navigation with centered logo |
| LandingHero | Full-screen hero carousel with animated text |
| AboutSection | Brand mission statement |
| RegionsCarousel | Interactive region/country selector with carousel |
| PhilosophySection | Animated accordion with brand philosophy |
| StoryCard | Guest story highlight |
| VideoSection | Video player with poster image |
| ExperienceSection | Experience showcase with image carousel |
| CTASection | Call-to-action with animated text |
| LandingFooter | Newsletter signup and footer links |
Landing Architecture
Section titled “Landing Architecture”src/features/landing/├── types/│ └── landing.ts # NavItem, Region, LandingLabels, etc.├── data/│ └── landingContent.ts # Market-specific label content├── components/│ ├── LandingPage.astro # Main page assembler│ ├── LandingNavbar.astro # Split navigation│ ├── LandingHero.tsx # Hero carousel (React + Motion)│ ├── AboutSection.astro # Mission text│ ├── RegionsCarousel.tsx # Region/country carousel (React + Motion)│ ├── PhilosophySection.tsx # Animated accordion (React + Motion)│ ├── StoryCard.astro # Guest story component│ ├── VideoSection.tsx # Video player (React)│ ├── ExperienceSection.astro # Experience showcase│ ├── ExperienceImageCarousel.tsx # Image carousel (React + Motion)│ ├── CTASection.astro # Call-to-action│ └── LandingFooter.astro # Footer with newsletter└── index.ts # Feature exportsTechnical Notes
Section titled “Technical Notes”- React + Framer Motion: Carousels and animations use React with the
motionlibrary - Market-based content: Labels and content load from
getLandingLabels(market) - Zero JS sections: Static sections use Astro components (AboutSection, StoryCard, etc.)
- Interactive sections: Carousels and accordions use React islands
Source: frontend/src/features/landing/
Destination Pages
Section titled “Destination Pages”Country/destination landing pages showcase editorial experiences for a specific country, using PublicLayout and the PaisExperienceSection components.
Destination Route Pattern
Section titled “Destination Route Pattern”/{market}/destinations/{country}Example: /es/destinations/argentinaDestination Page Sections
Section titled “Destination Page Sections”| Section | Description |
|---|---|
| Title + Description | Country name and editorial description |
| Experience Cards (Desktop) | PaisExperienceSection — sticky 3-column gallery with scroll animations |
| Experience Cards (Mobile) | PaisExperienceMobileSection — full-height snap-scroll cards |
| Trip Modal | ExperienceBottomModal — lists linked trips with itinerary and pricing |
| Navbar + Footer | Via PublicLayout |
Destination Architecture
Section titled “Destination Architecture”src/components/pais/├── PaisExperienceSection/│ ├── PaisExperienceSection.astro # Astro wrapper (desktop)│ ├── PaisExperienceSection.react.tsx # React component│ ├── PaisExperienceSection.types.ts # Type definitions│ └── PaisExperienceSection.module.css # Styles└── PaisExperienceMobileSection/ ├── PaisExperienceMobileSection.astro # Astro wrapper (mobile) ├── PaisExperienceMobileSection.react.tsx # React component └── PaisExperienceMobileSection.module.css # StylesData Flow
Section titled “Data Flow”Country data is fetched server-side from the CMS API. Editorial experience content (title, tag, description, image) is stored per-locale in cms_country_translations.experiences JSON. Each experience references a product_template_id which is resolved to the matching ProductByMarket per requested market.
Source: frontend/src/pages/[market]/destinations/[country].astro
Checkout Flow
Section titled “Checkout Flow”The checkout flow guides users through customizing their trip after selecting an offer.
Flow Steps
Section titled “Flow Steps”TripConfigurator → Loading Screen → Flights → Hotels → Activities → Transfers → Contact → Travelers → Summary & Payment → Confirmation| Step | Route | Description |
|---|---|---|
| Loading | /{market}/checkout/{offerId}/loading | Transition screen with progress animation |
| Flights | /{market}/checkout/{offerId}/flights | Select flight options |
| Hotels | /{market}/checkout/{offerId}/hotels | Select hotel accommodations |
| Activities | /{market}/checkout/{offerId}/activities | Select optional activity upgrades per day |
| Transfers | /{market}/checkout/{offerId}/transfers | Select optional transfer upgrades per day |
| Contact | /{market}/checkout/{offerId}/contact | Enter booking contact person details |
| Travelers | /{market}/checkout/{offerId}/travelers | Enter traveler information with inline name validation (ASCII-only) |
| Summary | /{market}/checkout/{offerId}/summary | Review booking and complete payment |
| Confirmation | /{market}/confirmation/{reference} | Booking confirmed, trip details and next steps |
Loading Screen
Section titled “Loading Screen”The loading screen provides a branded transition between the trip configurator and checkout:
- Full-screen background with destination imagery
- Loading card with animated progress bar
- Rotating tips displaying travel-related messages
- 3-second minimum display ensures smooth UX
- Auto-redirect to flights page when ready
The TripConfigurator redirects to the loading page after offer creation, which then forwards to the checkout flow.
Source: frontend/src/features/checkout-loading/
Checkout Architecture
Section titled “Checkout Architecture”Checkout pages share a common architecture:
- Astro page - Server-rendered wrapper passing props to React
- React page component - Handles state, API calls, navigation
- Selector components - Day-by-day selection UI
- Shared components -
StickyHeader(with summary drawer),NavigationFooter,HelpWidget
src/features/checkout-loading/├── types/│ └── loading.ts # Loading screen types├── data/│ └── loadingContent.ts # Rotating tips content├── components/│ ├── CheckoutLoadingScreen.tsx # Main loading screen│ ├── LoadingCard.tsx # Card with progress and tips│ ├── ProgressBar.tsx # Animated progress bar│ └── RotatingTips.tsx # Cycling tip messages└── index.ts
src/features/checkout/├── types/ # TypeScript interfaces│ ├── checkout.ts # Shared types (OfferSummary, Currency, Labels)│ ├── hotel.ts # Hotel selection types (3-tier selector)│ ├── activity.ts # Activity selection types│ ├── transfer.ts # Transfer selection types│ ├── traveler.ts # Traveler data types│ └── summary.ts # Summary page types├── components/│ ├── HotelSelectionPage.tsx│ ├── ActivitySelectionPage.tsx│ ├── TransferSelectionPage.tsx│ ├── TravelerDataPage.tsx│ ├── SummaryPaymentPage.tsx│ ├── HotelSelector/ # Tabbed 3-tier hotel cards│ ├── ActivitySelector/ # Day sections, cards│ ├── TransferSelector/ # Day sections, cards│ ├── TravelerForm/ # Traveler input forms│ ├── Summary/ # Summary page components│ ├── StickyHeader.tsx # Trip info header with summary button│ ├── CheckoutSummaryDrawer.tsx # Slide-out summary with price breakdown│ ├── NavigationFooter.tsx # Back/Continue buttons│ └── HelpWidget.tsx # Support contact└── api/ └── checkoutApi.ts # Session persistence
src/features/confirmation/├── components/│ ├── ConfirmationPage.tsx # Main page component│ ├── HeroColumn.tsx # Left column with destination image│ ├── DetailsColumn.tsx # Right column with booking details│ ├── ChecklistItem.tsx # Individual checklist items│ └── ActionButtons.tsx # Download/share actions└── types/ └── confirmation.ts # Confirmation page typesCheckout State Management
Section titled “Checkout State Management”Each selection page uses the reducer pattern:
Map<number, string>tracks one selection per day (dayNumber → optionId)- Selections persist to backend session via checkout API
- Previous selections restore on page load
Session Persistence
Section titled “Session Persistence”Selections save to the checkout session (backend API):
// Activity selections{ activity_id: number, day_number: number, price: number }[]
// Transfer selections{ transfer_id: number, day_number: number, price: number }[]Source: frontend/src/features/checkout/
Deployment
Section titled “Deployment”The frontend deploys to Cloudflare Pages:
# Deploy to stagingpnpm deploy-stagingBuild output is in dist/ directory.