---
name: seo-optimization
description: Optimize Next.js pages for search engines including meta tags, structured data, sitemaps, and Core Web Vitals. Use when creating landing pages, service pages, blog posts, or improving search rankings for Mawidi's 70+ pages across multiple GCC locations.
---

# SEO Optimization Skill

## Overview

Optimize Mawidi's Next.js pages for search engines. This skill covers meta tags, Open Graph, structured data, sitemaps, and Core Web Vitals—critical for ranking in GCC markets.

**SEO Goals:**

- Rank for "[service] in [location]" queries (600+ combinations)
- Bilingual SEO (Arabic + English)
- Local SEO for GCC cities
- Technical SEO (Core Web Vitals, crawlability)

## When to Use

Use this skill when:

- Creating new landing pages
- Adding service/location pages
- Writing blog posts
- Improving existing page rankings
- Fixing SEO issues
- Adding structured data

## Next.js Metadata API

### Static Metadata

```tsx
// app/[lang]/services/[service]/page.tsx

import { Metadata } from "next";

export const metadata: Metadata = {
  title: "WhatsApp Appointment Booking | Mawidi",
  description:
    "Automate your clinic appointments with WhatsApp. Book demos, manage schedules, and grow your practice.",
  keywords: ["WhatsApp booking", "clinic appointment", "medical scheduling"],

  // Open Graph
  openGraph: {
    title: "WhatsApp Appointment Booking | Mawidi",
    description: "Automate your clinic appointments with WhatsApp.",
    url: "https://mawidi.com/services/whatsapp-booking",
    siteName: "Mawidi",
    images: [
      {
        url: "https://mawidi.com/og/whatsapp-booking.jpg",
        width: 1200,
        height: 630,
        alt: "Mawidi WhatsApp Booking",
      },
    ],
    locale: "en_US",
    type: "website",
  },

  // Twitter Card
  twitter: {
    card: "summary_large_image",
    title: "WhatsApp Appointment Booking | Mawidi",
    description: "Automate your clinic appointments with WhatsApp.",
    images: ["https://mawidi.com/og/whatsapp-booking.jpg"],
  },

  // Robots
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      "max-video-preview": -1,
      "max-image-preview": "large",
      "max-snippet": -1,
    },
  },

  // Alternates for multilingual
  alternates: {
    canonical: "https://mawidi.com/en/services/whatsapp-booking",
    languages: {
      en: "https://mawidi.com/en/services/whatsapp-booking",
      ar: "https://mawidi.com/ar/services/whatsapp-booking",
    },
  },
};
```

### Dynamic Metadata (generateMetadata)

```tsx
// app/[lang]/[location]/[service]/page.tsx

import { Metadata } from "next";
import { getServiceData, getLocationData } from "@/lib/data";

interface Props {
  params: { lang: "ar" | "en"; location: string; service: string };
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const service = await getServiceData(params.service);
  const location = await getLocationData(params.location);
  const isArabic = params.lang === "ar";

  const title = isArabic
    ? `${service.nameAr} في ${location.nameAr} | مواعيدي`
    : `${service.name} in ${location.name} | Mawidi`;

  const description = isArabic
    ? `احجز موعد ${service.nameAr} في ${location.nameAr}. حجز عبر الواتساب، تأكيد فوري.`
    : `Book ${service.name} appointments in ${location.name}. WhatsApp booking, instant confirmation.`;

  return {
    title,
    description,
    openGraph: {
      title,
      description,
      url: `https://mawidi.com/${params.lang}/${params.location}/${params.service}`,
      locale: isArabic ? "ar_SA" : "en_US",
    },
    alternates: {
      canonical: `https://mawidi.com/${params.lang}/${params.location}/${params.service}`,
      languages: {
        en: `https://mawidi.com/en/${params.location}/${params.service}`,
        ar: `https://mawidi.com/ar/${params.location}/${params.service}`,
      },
    },
  };
}
```

## Structured Data (JSON-LD)

### Organization Schema

```tsx
// components/StructuredData/Organization.tsx

export function OrganizationSchema() {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Organization",
    name: "Mawidi",
    alternateName: "مواعيدي",
    url: "https://mawidi.com",
    logo: "https://mawidi.com/logo.png",
    sameAs: [
      "https://twitter.com/mawidi",
      "https://linkedin.com/company/mawidi",
      "https://instagram.com/mawidi",
    ],
    contactPoint: {
      "@type": "ContactPoint",
      telephone: "+974-XXXX-XXXX",
      contactType: "customer service",
      availableLanguage: ["English", "Arabic"],
    },
    address: {
      "@type": "PostalAddress",
      addressCountry: "QA",
      addressRegion: "Doha",
    },
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  );
}
```

### Local Business Schema

```tsx
// components/StructuredData/LocalBusiness.tsx

interface LocalBusinessSchemaProps {
  name: string;
  nameAr: string;
  address: string;
  city: string;
  country: string;
  phone: string;
  services: string[];
}

export function LocalBusinessSchema({
  name,
  address,
  city,
  country,
  phone,
  services,
}: LocalBusinessSchemaProps) {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "MedicalBusiness",
    name,
    address: {
      "@type": "PostalAddress",
      streetAddress: address,
      addressLocality: city,
      addressCountry: country,
    },
    telephone: phone,
    priceRange: "$$",
    openingHoursSpecification: [
      {
        "@type": "OpeningHoursSpecification",
        dayOfWeek: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"],
        opens: "09:00",
        closes: "18:00",
      },
    ],
    hasOfferCatalog: {
      "@type": "OfferCatalog",
      name: "Services",
      itemListElement: services.map((service, i) => ({
        "@type": "Offer",
        itemOffered: {
          "@type": "Service",
          name: service,
        },
        position: i + 1,
      })),
    },
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  );
}
```

### FAQ Schema

```tsx
// components/StructuredData/FAQ.tsx

interface FAQItem {
  question: string;
  answer: string;
}

export function FAQSchema({ items }: { items: FAQItem[] }) {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    mainEntity: items.map((item) => ({
      "@type": "Question",
      name: item.question,
      acceptedAnswer: {
        "@type": "Answer",
        text: item.answer,
      },
    })),
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  );
}
```

### Breadcrumb Schema

```tsx
// components/StructuredData/Breadcrumb.tsx

interface BreadcrumbItem {
  name: string;
  url: string;
}

export function BreadcrumbSchema({ items }: { items: BreadcrumbItem[] }) {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    itemListElement: items.map((item, index) => ({
      "@type": "ListItem",
      position: index + 1,
      name: item.name,
      item: item.url,
    })),
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  );
}
```

## Sitemap Generation

### Dynamic Sitemap

```typescript
// app/sitemap.ts

import { MetadataRoute } from "next";
import { getServices, getLocations, getBlogPosts } from "@/lib/data";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = "https://mawidi.com";
  const services = await getServices();
  const locations = await getLocations();
  const blogPosts = await getBlogPosts();

  const staticPages = ["", "/about", "/pricing", "/contact", "/features"];

  const staticEntries = staticPages.flatMap((page) => [
    {
      url: `${baseUrl}/en${page}`,
      lastModified: new Date(),
      changeFrequency: "weekly" as const,
      priority: page === "" ? 1 : 0.8,
      alternates: {
        languages: {
          en: `${baseUrl}/en${page}`,
          ar: `${baseUrl}/ar${page}`,
        },
      },
    },
    {
      url: `${baseUrl}/ar${page}`,
      lastModified: new Date(),
      changeFrequency: "weekly" as const,
      priority: page === "" ? 1 : 0.8,
    },
  ]);

  // Service × Location combinations (600+ pages)
  const serviceLocationEntries = services.flatMap((service) =>
    locations.flatMap((location) =>
      ["en", "ar"].map((lang) => ({
        url: `${baseUrl}/${lang}/${location.slug}/${service.slug}`,
        lastModified: new Date(),
        changeFrequency: "monthly" as const,
        priority: 0.7,
      })),
    ),
  );

  // Blog posts
  const blogEntries = blogPosts.flatMap((post) =>
    ["en", "ar"].map((lang) => ({
      url: `${baseUrl}/${lang}/blog/${post.slug}`,
      lastModified: post.updatedAt,
      changeFrequency: "weekly" as const,
      priority: 0.6,
    })),
  );

  return [...staticEntries, ...serviceLocationEntries, ...blogEntries];
}
```

### Robots.txt

```typescript
// app/robots.txt/route.ts

export function GET() {
  const content = `
User-agent: *
Allow: /
Disallow: /api/
Disallow: /staff/
Disallow: /dashboard/

Sitemap: https://mawidi.com/sitemap.xml
  `.trim();

  return new Response(content, {
    headers: {
      "Content-Type": "text/plain",
    },
  });
}
```

## Core Web Vitals

### Image Optimization

```tsx
import Image from 'next/image';

// Optimized hero image
<Image
  src="/hero-image.jpg"
  alt="Medical clinic appointment booking"
  width={1200}
  height={630}
  priority // Load immediately for LCP
  quality={85}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..." // Pre-generated blur
/>

// Lazy-loaded images below fold
<Image
  src="/feature-image.jpg"
  alt="Feature description"
  width={600}
  height={400}
  loading="lazy"
/>
```

### Font Optimization

```tsx
// app/layout.tsx
import { Plus_Jakarta_Sans } from "next/font/google";

const font = Plus_Jakarta_Sans({
  subsets: ["latin"],
  display: "swap", // Prevent FOIT
  preload: true,
  variable: "--font-display",
});
```

### Script Loading

```tsx
import Script from 'next/script';

// Analytics - load after interactive
<Script
  src="https://analytics.example.com/script.js"
  strategy="afterInteractive"
/>

// Non-critical - load when idle
<Script
  src="https://widget.example.com/chat.js"
  strategy="lazyOnload"
/>
```

## Bilingual SEO

### Hreflang Implementation

```tsx
// In layout or page
<head>
  <link rel="alternate" hrefLang="en" href="https://mawidi.com/en/page" />
  <link rel="alternate" hrefLang="ar" href="https://mawidi.com/ar/page" />
  <link
    rel="alternate"
    hrefLang="x-default"
    href="https://mawidi.com/en/page"
  />
</head>;

// Or via metadata API
export const metadata: Metadata = {
  alternates: {
    canonical: "https://mawidi.com/en/page",
    languages: {
      en: "https://mawidi.com/en/page",
      ar: "https://mawidi.com/ar/page",
    },
  },
};
```

### Arabic SEO Tips

```tsx
// Use Arabic keywords naturally
const arMetadata = {
  title: 'حجز مواعيد عبر الواتساب | مواعيدي',
  description: 'نظام حجز المواعيد عبر الواتساب للعيادات والصالونات. حجز فوري، تذكيرات آلية، إدارة المواعيد.',
  keywords: ['حجز مواعيد', 'واتساب', 'عيادات', 'صالونات', 'قطر', 'دبي'],
};

// Ensure RTL is set
<html lang="ar" dir="rtl">
```

## SEO Checklist

### Every Page Must Have:

- [ ] Unique title tag (50-60 characters)
- [ ] Meta description (150-160 characters)
- [ ] H1 tag (one per page)
- [ ] Canonical URL
- [ ] Hreflang tags (for bilingual)
- [ ] Open Graph tags
- [ ] Twitter Card tags
- [ ] Structured data (as applicable)

### Technical SEO:

- [ ] Sitemap submitted to Google Search Console
- [ ] Robots.txt properly configured
- [ ] No broken links (404s)
- [ ] HTTPS enabled
- [ ] Mobile-friendly
- [ ] Core Web Vitals passing

### Content SEO:

- [ ] Target keyword in title
- [ ] Target keyword in H1
- [ ] Target keyword in first 100 words
- [ ] Internal links to related pages
- [ ] External links to authoritative sources
- [ ] Alt text on all images

## Monitoring

### Google Search Console

```typescript
// Track indexing status
// Check for crawl errors
// Monitor search performance

// Key metrics:
// - Impressions
// - Clicks
// - Average position
// - CTR
```

### Core Web Vitals Monitoring

```typescript
// app/api/analytics/web-vitals/route.ts

import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  const { metric, value, page } = await request.json();

  // Log to analytics service
  console.log({
    metric, // LCP, FID, CLS, FCP, TTFB
    value,
    page,
    timestamp: new Date().toISOString(),
  });

  return NextResponse.json({ success: true });
}
```

## Quick Reference

### Title Tag Formula

```
[Primary Keyword] [Secondary Keyword] | [Brand]
Example: "WhatsApp Booking for Clinics | Mawidi"

Arabic:
[الكلمة الرئيسية] | [العلامة التجارية]
Example: "حجز المواعيد عبر الواتساب | مواعيدي"
```

### Meta Description Formula

```
[Value proposition]. [Key feature]. [Call to action].
Example: "Automate clinic appointments with WhatsApp.
Instant booking, automatic reminders. Try free today."
```

## See Also

- [generate-api-docs.ts](../generate-api-docs.ts) - API documentation
- [frontend-design](../frontend-design/SKILL.md) - Page design best practices
- `app/sitemap.ts` - Sitemap implementation
