# SEO Optimizations — CahooTravel (`client-cahoo/`)

_Implemented: August 2026_

---

## Overview

This document covers all SEO improvements applied to the `client-cahoo/` Next.js 14 app. Changes are grouped by category with file references.

---

## 1. Root Metadata & `metadataBase`

**File:** `app/[locale]/layout.tsx`

### What was done
- Replaced `export const metadata` with `export async function generateMetadata({ params })` to enable dynamic, locale-aware metadata generation.
- Set `metadataBase` to `process.env.NEXT_PUBLIC_APP_URL ?? 'https://cahootravel.com'`. Without this, Next.js cannot generate absolute URLs for OG images, canonical links, or hreflang tags.
- Added a `title.template` of `'%s | CahooTravel'` so all page titles follow a consistent format (e.g. _"Pricing — Transparent Plans | CahooTravel"_).
- Improved the root description from a headline fragment to a keyword-rich, crawlable sentence (~155 characters).

### Before
```ts
export const metadata: Metadata = {
  title: 'CahooTravel — Find Hidden Travel Deals',
  description: 'The same hotel room, different price depending on where you search from...',
  icons: { icon: '/favicon.svg' },
}
```

### After
```ts
export async function generateMetadata({ params }): Promise<Metadata> {
  return {
    metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL ?? 'https://cahootravel.com'),
    title: {
      template: '%s | CahooTravel',
      default: 'CahooTravel — Find Hidden Hotel Price Gaps & Save',
    },
    description:
      'Compare hotel prices from 100+ countries. Reveal geo-pricing gaps on Booking.com, Airbnb, Expedia and more — and book at the cheapest rate. Free to start.',
    ...
  }
}
```

---

## 2. OpenGraph & Twitter Card Metadata

**File:** `app/[locale]/layout.tsx`

All pages now emit correct `<meta property="og:*">` and `<meta name="twitter:*">` tags, which control how links appear when shared on social platforms (Instagram, TikTok, WhatsApp, Slack, etc.).

```ts
openGraph: {
  type: 'website',
  siteName: 'CahooTravel',
  title: 'CahooTravel — Find Hidden Hotel Price Gaps & Save',
  description: '...',
  images: [{ url: '/opengraph-image.png', width: 1200, height: 630, alt: '...' }],
  locale,
},
twitter: {
  card: 'summary_large_image',
  title: '...',
  description: '...',
  images: ['/opengraph-image.png'],
},
```

---

## 3. Dynamic OG Image

**File:** `app/opengraph-image.tsx`

A dynamic branded Open Graph image (1200×630 px) generated at request time using `next/og` (edge runtime). Shown automatically by Next.js when a page is shared on social media.

- Purple brand background (`#4C1D95`)
- Site name, headline, description, and CTA pill
- No external image hosting required — served from the app itself

To generate a custom static image instead, place a `1200×630` PNG at `public/opengraph-image.png` and remove this file.

---

## 4. Hreflang Alternates (21 Locales)

**File:** `app/[locale]/layout.tsx`

All 21 locale variants are declared as language alternates. This tells Google which URL to serve to users in each language/region and prevents duplicate-content penalties across locale versions.

```ts
const languages: Record<string, string> = {}
for (const loc of routing.locales) {
  languages[loc] = `/${loc}`
}

alternates: { languages }
```

**Locales covered:**
`en-GB`, `de-DE`, `fr-FR`, `es-ES`, `it-IT`, `nl-NL`, `pl-PL`, `pt-PT`, `ja-JP`, `ko-KR`, `ru-RU`, `uk-UA`, `sr-RS`, `mk-MK`, `bg-BG`, `hr-HR`, `sl-SI`, `sr-ME`, `bs-BA`, `sq-AL`, `el-GR`

> **Note:** For full per-page hreflang accuracy, each page's `generateMetadata` would need to append the page path to each locale URL. The current implementation declares site-level language alternates, which is accepted by Google and sufficient for most use cases.

---

## 5. Sitemap

**File:** `app/sitemap.ts`

Auto-generates `/sitemap.xml` via Next.js's built-in convention. Covers **21 locales × 14 public pages = 294 URLs**.

| Page | Priority | Change Frequency |
|---|---|---|
| `/` (home) | 1.0 | daily |
| `/how-it-works` | 0.9 | weekly |
| `/pricing` | 0.9 | weekly |
| `/about` | 0.8 | monthly |
| `/contact` | 0.7 | monthly |
| `/partners` | 0.7 | monthly |
| `/platforms` | 0.7 | weekly |
| `/countries` | 0.7 | weekly |
| `/support` | 0.6 | monthly |
| `/security` | 0.5 | monthly |
| `/privacy` | 0.5 | monthly |
| `/refund-policy` | 0.5 | monthly |
| `/terms` | 0.5 | monthly |
| `/cookies` | 0.4 | monthly |

Protected pages (`/profile`, `/billing`, `/searches`, `/signin`) are intentionally excluded.

Submit to Google Search Console: `https://cahootravel.com/sitemap.xml`

---

## 6. Robots.txt

**File:** `app/robots.ts`

Auto-generates `/robots.txt` via Next.js's built-in convention.

```
User-agent: *
Allow: /
Disallow: /*/profile
Disallow: /*/billing
Disallow: /*/searches

Sitemap: https://cahootravel.com/sitemap.xml
```

Protected user-account pages are blocked from crawling. The sitemap URL is declared so crawlers discover it automatically.

---

## 7. Page-Specific Metadata

### Server-component pages (metadata exported directly)

| Page | File | Title |
|---|---|---|
| About | `app/[locale]/about/page.tsx` | About Us — Our Story & Mission |
| How it works | `app/[locale]/how-it-works/page.tsx` | How It Works — Find the Best Hotel Price in 5 Steps |
| Partners | `app/[locale]/partners/page.tsx` | Affiliate Partners — Earn by Helping Travellers Save |
| Platforms | `app/[locale]/platforms/page.tsx` | Supported Platforms — Compare Prices on 23+ Booking Sites |
| Security | `app/[locale]/security/page.tsx` | Security — How We Protect Your Account & Data |
| Privacy | `app/[locale]/privacy/page.tsx` | Privacy Policy — What Data We Collect & Why |
| Refund policy | `app/[locale]/refund-policy/page.tsx` | Refund Policy |
| Terms | `app/[locale]/terms/page.tsx` | Terms of Service |

### Client-component pages (metadata via sub-layout)

Because `'use client'` components cannot export `metadata`, a thin server-side `layout.tsx` was added to each route folder:

| Page | Layout file | Title |
|---|---|---|
| Pricing | `app/[locale]/pricing/layout.tsx` | Pricing — Simple, Transparent Plans |
| Contact | `app/[locale]/contact/layout.tsx` | Contact Us — Get in Touch |
| Support | `app/[locale]/support/layout.tsx` | Support — Help & Common Questions |
| Countries | `app/[locale]/countries/layout.tsx` | Supported Countries — Compare Hotel Prices from 100+ Locations |
| Cookies | `app/[locale]/cookies/layout.tsx` | Cookie Preferences (`noindex`) |

---

## 8. Noindex on Protected Pages

Pages that require authentication or contain user-specific data are marked `robots: { index: false, follow: false }` so they are excluded from search engine indexes.

| Page | Layout file |
|---|---|
| Profile | `app/[locale]/profile/layout.tsx` |
| Billing | `app/[locale]/billing/layout.tsx` |
| My Searches | `app/[locale]/searches/layout.tsx` |
| Sign In | `app/[locale]/signin/layout.tsx` |

---

## 9. Structured Data (JSON-LD)

### Organization schema

**File:** `app/[locale]/layout.tsx`

Injected on every page. Establishes the CahooTravel brand entity with Google, enabling sitelinks and Knowledge Panel eligibility.

```json
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "CahooTravel",
  "url": "https://cahootravel.com",
  "logo": "https://cahootravel.com/cahootravel-logo.svg",
  "sameAs": [
    "https://www.instagram.com/cahootravel.co",
    "https://www.tiktok.com/@cahootravel.co"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "contactType": "customer support",
    "email": "support@cahootravel.com"
  }
}
```

### FAQPage schema

**File:** `app/[locale]/how-it-works/page.tsx`

Built dynamically from the translated FAQ strings already on the page. Eligible for **Google FAQ rich results** — expanded question/answer blocks shown directly in search results.

```json
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Is CahooTravel free to use?",
      "acceptedAnswer": { "@type": "Answer", "text": "..." }
    },
    ...
  ]
}
```

---

## 10. What Was Already Good (No Changes Needed)

- **Heading hierarchy** — correct H1 → H2 → H3 nesting on all pages
- **Image alt attributes** — all `<img>` tags have descriptive alt text
- **Decorative SVGs** — marked `aria-hidden="true"`
- **Google Analytics** — GA4 tag (`G-5GNKKJ7HE2`) loaded via `next/script` with `afterInteractive` strategy
- **i18n routing** — 21 locale prefixes correctly configured via `next-intl`

---

## Remaining Recommendations

These were identified during the audit but not yet implemented:

| Item | Effort | Impact |
|---|---|---|
| **Per-page hreflang on inner pages** | Medium | High — currently only root URLs are declared as alternates |
| **FAQPage JSON-LD on home page** | Low | Medium — home FAQ is in a client component; requires extracting FAQ to a server sub-component |
| **`apple-touch-icon` (180×180 PNG)** | Low | Low — needed for iOS home screen and some PWA contexts |
| **`SoftwareApplication` schema on pricing page** | Low | Medium — eligible for Google rich results with pricing info |
| **Core Web Vitals audit** | Medium | High — LCP, CLS, INP directly affect ranking; run Lighthouse or PageSpeed Insights |
| **Image format optimisation** | Low | Medium — convert platform logo PNGs to WebP; use `next/image` where possible |
| **Canonical tags per page** | Low | Medium — Next.js sets canonical automatically but explicit per-page canonicals avoid ambiguity for locale variants |
