Loading...
Loading...
Next.js has a built-in metadata management system for SEO. Metadata can be set statically via the metadata object or dynamically via the generateMetadata function.
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Hack Frontend',
description: 'Frontend interview preparation platform',
keywords: ['frontend', 'interview', 'javascript', 'react']
}
For pages where metadata depends on data, use generateMetadata:
// app/docs/[slug]/page.tsx
import type { Metadata } from 'next'
import { getDoc } from '@/lib/docs'
export async function generateMetadata({
params
}: {
params: { slug: string }
}): Promise<Metadata> {
const doc = await getDoc(params.slug)
return {
title: doc.title,
description: doc.description,
keywords: doc.keywords,
openGraph: {
title: doc.title,
description: doc.description,
type: 'article'
}
}
}
Next.js deduplicates requests: if generateMetadata and the page component call the same fetch, the request runs once.
export const metadata: Metadata = {
title: 'JavaScript Interview Problems',
openGraph: {
title: 'JavaScript Interview Problems',
description: 'Solve problems from real interviews',
url: 'https://hackfrontend.com/problems',
siteName: 'Hack Frontend',
images: [
{
url: '/images/HackFrontendBanner.png',
width: 1200,
height: 630
}
],
locale: 'en_US',
type: 'website'
},
twitter: {
card: 'summary_large_image',
title: 'JavaScript Interview Problems',
description: 'Solve problems from real interviews',
images: ['/images/HackFrontendBanner.png']
}
}
To add a suffix or prefix to child page titles:
// app/layout.tsx
export const metadata: Metadata = {
title: {
template: '%s | Hack Frontend',
default: 'Hack Frontend'
}
}
// app/docs/page.tsx
export const metadata: Metadata = {
title: 'Knowledge Base'
}
// Result: "Knowledge Base | Hack Frontend"
Next.js can generate a sitemap automatically:
// app/sitemap.ts
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
const docs = getAllDocs()
return [
{ url: 'https://hackfrontend.com', lastModified: new Date() },
{ url: 'https://hackfrontend.com/problems', lastModified: new Date() },
...docs.map(doc => ({
url: `https://hackfrontend.com/docs/${doc.slug}`,
lastModified: doc.updatedAt
}))
]
}
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/auth/']
},
sitemap: 'https://hackfrontend.com/sitemap.xml'
}
}
For rich snippets in search results:
// app/docs/[slug]/page.tsx
export default async function DocPage({ params }) {
const doc = await getDoc(params.slug)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: doc.title,
description: doc.description,
author: { '@type': 'Organization', name: 'Hack Frontend' }
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>{doc.content}</article>
</>
)
}
Interview tip:
Metadata is inherited from parent segments to children and merged. A child segment can override any field from the parent.