seo-metadata

Dynamic metadata generation per locale with canonical URLs, Open Graph tags, Twitter cards, and hreflang alternates. Use when implementing page metadata, setting up SEO for multilingual sites, or configuring social sharing previews.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "seo-metadata" with this command: npx skills add canatufkansu/claude-skills/canatufkansu-claude-skills-seo-metadata

SEO Metadata

Base Metadata Configuration

// app/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL!),
  title: {
    template: '%s | Studio Name',
    default: 'Studio Name - Pilates & Yoga',
  },
  description: 'Professional Pilates and Yoga coaching for strength and calm.',
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
};

Per-Page Dynamic Metadata

// app/[locale]/services/page.tsx
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { locales } from '@/i18n.config';

type Props = {
  params: Promise<{ locale: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'meta.services' });
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      canonical: `${siteUrl}/${locale}/services`,
      languages: Object.fromEntries(
        locales.map((loc) => [loc, `${siteUrl}/${loc}/services`])
      ),
    },
    openGraph: {
      title: t('title'),
      description: t('description'),
      url: `${siteUrl}/${locale}/services`,
      siteName: 'Studio Name',
      locale: locale.replace('-', '_'),
      type: 'website',
      images: [
        {
          url: `${siteUrl}/og/services.jpg`,
          width: 1200,
          height: 630,
          alt: t('title'),
        },
      ],
    },
    twitter: {
      card: 'summary_large_image',
      title: t('title'),
      description: t('description'),
      images: [`${siteUrl}/og/services.jpg`],
    },
  };
}

Metadata Helper Function

// lib/metadata.ts
import type { Metadata } from 'next';
import { locales, type Locale } from '@/i18n.config';

interface GenerateMetadataOptions {
  locale: Locale;
  path: string;
  title: string;
  description: string;
  image?: string;
  noIndex?: boolean;
}

export function generatePageMetadata({
  locale,
  path,
  title,
  description,
  image = '/og/default.jpg',
  noIndex = false,
}: GenerateMetadataOptions): Metadata {
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL!;
  const fullUrl = `${siteUrl}/${locale}${path}`;
  const imageUrl = image.startsWith('http') ? image : `${siteUrl}${image}`;

  return {
    title,
    description,
    robots: noIndex ? { index: false, follow: false } : undefined,
    alternates: {
      canonical: fullUrl,
      languages: Object.fromEntries(
        locales.map((loc) => [loc, `${siteUrl}/${loc}${path}`])
      ),
    },
    openGraph: {
      title,
      description,
      url: fullUrl,
      siteName: 'Studio Name',
      locale: locale.replace('-', '_'),
      type: 'website',
      images: [{ url: imageUrl, width: 1200, height: 630, alt: title }],
    },
    twitter: {
      card: 'summary_large_image',
      title,
      description,
      images: [imageUrl],
    },
  };
}

Usage with Helper

// app/[locale]/about/page.tsx
import { generatePageMetadata } from '@/lib/metadata';
import { getTranslations } from 'next-intl/server';

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'meta.about' });

  return generatePageMetadata({
    locale: locale as Locale,
    path: '/about',
    title: t('title'),
    description: t('description'),
    image: '/og/about.jpg',
  });
}

Dynamic Route Metadata

// app/[locale]/programmes/[slug]/page.tsx
import { getProgrammeBySlug } from '@/lib/data';

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale, slug } = await params;
  const programme = await getProgrammeBySlug(locale as Locale, slug);
  
  if (!programme) {
    return { title: 'Not Found' };
  }

  return generatePageMetadata({
    locale: locale as Locale,
    path: `/programmes/${slug}`,
    title: programme.title,
    description: programme.outcomes.slice(0, 2).join('. '),
  });
}

Hreflang in HTML Head

The metadata API handles hreflang automatically via alternates.languages. The rendered HTML will include:

<link rel="canonical" href="https://example.com/en/services" />
<link rel="alternate" hreflang="pt-PT" href="https://example.com/pt-PT/services" />
<link rel="alternate" hreflang="en" href="https://example.com/en/services" />
<link rel="alternate" hreflang="tr" href="https://example.com/tr/services" />
<link rel="alternate" hreflang="es" href="https://example.com/es/services" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/services" />
<link rel="alternate" hreflang="de" href="https://example.com/de/services" />
<link rel="alternate" hreflang="x-default" href="https://example.com/pt-PT/services" />

Translation Keys Structure

// messages/en.json
{
  "meta": {
    "home": {
      "title": "Pilates & Yoga Coaching",
      "description": "Transform your body with personalized training"
    },
    "services": {
      "title": "Our Services",
      "description": "1:1 sessions, group classes, and online coaching"
    },
    "about": {
      "title": "About Us",
      "description": "Meet your instructor and learn our approach"
    }
  }
}

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

next-intl-i18n

No summary provided by upstream source.

Repository SourceNeeds Review
General

framer-motion-animations

No summary provided by upstream source.

Repository SourceNeeds Review
General

json-ld-schemas

No summary provided by upstream source.

Repository SourceNeeds Review
General

tailwind-shadcn

No summary provided by upstream source.

Repository SourceNeeds Review