← Back to list

seo-metadata
by canatufkansu
Set of skills that can be used in Claude
⭐ 0🍴 0📅 Dec 22, 2025
SKILL.md
name: seo-metadata description: 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.
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"
}
}
}
Score
Total Score
45/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
3ヶ月以内に更新
+5
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
○言語
プログラミング言語が設定されている
0/5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon