Back to Rules
Next.js92% popularity

Next.js Dynamic Metadata API

Use the Metadata API for SEO optimization with dynamic titles, descriptions, and OpenGraph images.

VercelUpdated Mar 18, 2024

Overview

The Next.js Metadata API provides a declarative way to manage document metadata, replacing the head component approach from Pages Router. It supports both static configuration for pages and dynamic generation based on route parameters, enabling comprehensive SEO optimization for every page. The metadata object accepts standard HTML meta tags as camelCase properties. title and description create the page title and meta description that appear in search results. The title template pattern appends site name consistently while allowing page-specific prefixes for branding. OpenGraph and Twitter Card metadata enable rich link previews when sharing pages on social platforms. Each platform has specific requirements—OGP tags for Facebook and LinkedIn, Twitter Card tags for Twitter. The icons field generates favicons, apple-touch-icons, and manifest icons from image files automatically. Dynamic metadata for dynamic routes uses the generateMetadata function receiving params and searchParams. This enables fetching data for individual pages and populating metadata from databases or APIs. Proper error handling with notFound() ensures missing content returns 404 rather than incomplete pages with invalid metadata. JSON-LD structured data can be added alongside metadata for enhanced search result rich snippets. The script tag with strategy='jsonld' injects schemaorg JSON into pages. Different schema types suit different page types—WebSite for homepages, Article for blog posts, and Product for e-commerce pages.

Code Example

.nextjsrules
import { Metadata, ResolvingMetadata } from 'next';
import { notFound } from 'next/navigation';
import { db } from '@/lib/db';
import { SchemaMarkup } from '@/components/SchemaMarkup';

type Props = {
  params: { slug: string };
  searchParams: { [key: string]: string | string[] | undefined };
};

async function getProject(slug: string) {
  const project = await db.project.findUnique({
    where: { slug },
    include: {
      author: { select: { name: true, image: true } },
      tags: true,
      _count: { select: { views: true, likes: true } },
    },
  });

  return project;
}

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const project = await getProject(params.slug);

  if (!project) {
    return {
      title: 'Project Not Found',
    };
  }

  const previousImages = (await parent).openGraph?.images || [];

  return {
    title: {
      default: project.title,
      template: '%s | DevRules Directory',
    },
    description: project.description,
    authors: [{ name: project.author.name }],
    keywords: project.tags.map(t => t.name),
    openGraph: {
      type: 'article',
      url: 'https://devrules.com/projects/' + project.slug,
      title: project.title,
      description: project.description,
      images: [
        {
          url: project.coverImage || '/default-og.png',
          width: 1200,
          height: 630,
          alt: project.title,
        },
        ...previousImages,
      ],
      publishedTime: project.createdAt.toISOString(),
      modifiedTime: project.updatedAt.toISOString(),
      authors: [project.author.name],
      tags: project.tags.map(t => t.name),
    },
    twitter: {
      card: 'summary_large_image',
      title: project.title,
      description: project.description,
      images: [project.coverImage || '/default-og.png'],
      creator: project.author.twitterHandle,
    },
    alternates: {
      canonical: 'https://devrules.com/projects/' + project.slug,
      languages: {
        'en-US': 'https://devrules.com/projects/' + project.slug,
        'es-ES': 'https://devrules.com/es/projects/' + project.slug,
      },
    },
    robots: {
      index: true,
      follow: true,
      googleBot: {
        index: true,
        follow: true,
        'max-video-preview': -1,
        'max-image-preview': 'large',
        'max-snippet': -1,
      },
    },
  };
}

export default async function ProjectPage({ params }: Props) {
  const project = await getProject(params.slug);

  if (!project) {
    notFound();
  }

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'SoftwareApplication',
    name: project.title,
    description: project.description,
    author: {
      '@type': 'Person',
      name: project.author.name,
    },
    datePublished: project.createdAt.toISOString(),
    dateModified: project.updatedAt.toISOString(),
    applicationCategory: 'DeveloperApplication',
    operatingSystem: 'Any',
    keywords: project.tags.map(t => t.name).join(', '),
  };

  return (
    <article>
      <SchemaMarkup data={jsonLd} />

      <h1>{project.title}</h1>
      <p>{project.description}</p>
      {/* Rest of component */}
    </article>
  );
}

More Next.js Rules

NEXTJS
98%

Next.js App Router Server Components First

Prioritize React Server Components for data fetching and static content. Client components only when interactivity is required.

server-componentsapp-routerperformance
'use server';

import { db } from '@/lib/database';
import { cache } from 'react';

export const getProjects = cache(async () => {
  return db.project...
Jan 15, 2024by Vercel Engineering
View Rule
NEXTJS
93%

Next.js Image Optimization Best Practices

Use the Image component with proper sizing, formats, and loading strategies for maximum performance.

nextjsimage-optimizationperformance
import Image from 'next/image';
import Link from 'next/link';

export function ProjectCard({ project }: { project: Project }) {
  return (
    <Link h...
Feb 28, 2024by Vercel
View Rule
NEXTJS
94%

Next.js Middleware Authentication

Implement authentication at the edge with Next.js Middleware for protected routes.

nextjsmiddlewareauthentication
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';

const JWT_SECRET = new ...
Mar 8, 2024by Next.js Team
View Rule