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"
}
}
}
More from canatufkansu/claude-skills
next-intl-i18n
next-intl internationalization for 6 locales (pt-PT, en, tr, es, fr, de) with locale-prefixed routing, useTranslations/getTranslations patterns, and message file structure. Use when adding translations, creating localized pages, implementing language switchers, or handling locale routing.
45sitemap-robots
Automated sitemap generation for all locale URLs, robots.txt configuration, and llms.txt for AI crawler optimization. Use when setting up sitemap.xml, configuring crawling rules, or improving discoverability for search engines and AI systems.
35tailwind-shadcn
Tailwind CSS utility patterns with shadcn/ui component usage, theming via CSS variables, and responsive design. Use when styling components, installing shadcn components, implementing dark mode, or creating consistent design systems.
29json-ld-schemas
JSON-LD structured data for Organization, Person, Service, Product, FAQPage, and BreadcrumbList with reusable components. Use when implementing schema.org markup, adding rich snippets, or improving search engine understanding of page content.
22framer-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.
22responsive-mobile-first
Mobile-first responsive patterns with sticky headers, floating CTAs, accessible navigation, and touch-friendly interactions. Use when implementing responsive layouts, mobile navigation, or ensuring touch-friendly UI.
13