Agent skill

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.

Stars 2
Forks 0

Install this agent skill to your Project

npx add-skill https://github.com/canatufkansu/claude-skills/tree/main/seo-metadata

SKILL.md

SEO Metadata

Base Metadata Configuration

tsx
// 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

tsx
// 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

tsx
// 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

tsx
// 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

tsx
// 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:

html
<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

json
// 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"
    }
  }
}

Expand your agent's capabilities with these related and highly-rated skills.

canatufkansu/claude-skills

framer-motion-animations

Subtle animation patterns for hero sections, card reveals, page transitions, and scroll-triggered effects using Framer Motion. Use when adding animations to components, implementing scroll effects, or creating page transitions.

2 0
Explore
canatufkansu/claude-skills

json-content-management

JSON-driven content architecture for services, programmes, FAQs, testimonials, and policies with TypeScript interfaces and locale-aware data fetching. Use when defining content schemas, creating data utilities, adding new content types, or fetching localized business data.

2 0
Explore
canatufkansu/claude-skills

accessibility-a11y

Semantic HTML, keyboard navigation, focus states, ARIA labels, skip links, and WCAG contrast requirements. Use when ensuring accessibility compliance, implementing keyboard navigation, or adding screen reader support.

2 0
Explore
canatufkansu/claude-skills

email-resend

Email sending via Resend API for contact forms and booking requests with locale-aware templates. Use when implementing email notifications, contact form submissions, or transactional emails.

2 0
Explore
canatufkansu/claude-skills

nextjs-app-router

Next.js 15 App Router patterns for Server/Client Components, async params, layouts, route handlers, Server Actions, and data fetching. Use when creating routes, pages, layouts, API endpoints, or implementing form submissions with revalidation.

2 0
Explore
canatufkansu/claude-skills

zod-react-hook-form

Form validation combining Zod schemas with React Hook Form, including localized error messages, Server Action integration, and shadcn/ui Form components. Use when building forms, validating user input, handling form submissions, or implementing Server Actions with validation.

2 0
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results