Skip to main content
Back to Blog
Tutorials
3 min read
November 16, 2024

How to Add SEO Meta Tags and Open Graph Images in Next.js

Configure SEO meta tags, Open Graph images, and Twitter cards in Next.js App Router using the Metadata API. Improve search visibility and social sharing.

Ryel Banfield

Founder & Lead Developer

Every page on your website needs proper meta tags for search engines and social media. Next.js App Router provides a Metadata API that makes this straightforward and type-safe.

Static Metadata

For pages with static content, export a metadata object:

// app/about/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "About Us | Your Company",
  description: "Learn about our team, mission, and approach to building custom software.",
  openGraph: {
    title: "About Us | Your Company",
    description: "Learn about our team, mission, and approach to building custom software.",
    url: "https://yourdomain.com/about",
    siteName: "Your Company",
    images: [
      {
        url: "https://yourdomain.com/images/og/about.jpg",
        width: 1200,
        height: 630,
        alt: "Your Company team",
      },
    ],
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "About Us | Your Company",
    description: "Learn about our team and mission.",
    images: ["https://yourdomain.com/images/og/about.jpg"],
  },
};

export default function AboutPage() {
  return <main>...</main>;
}

Dynamic Metadata

For pages with dynamic content (blog posts, product pages), use generateMetadata:

// app/blog/[slug]/page.tsx
import type { Metadata } from "next";

interface Props {
  params: Promise<{ slug: string }>;
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);

  return {
    title: post.metaTitle,
    description: post.metaDescription,
    openGraph: {
      title: post.metaTitle,
      description: post.metaDescription,
      url: `https://yourdomain.com/blog/${slug}`,
      type: "article",
      publishedTime: post.date,
      authors: [post.author],
      images: [
        {
          url: `https://yourdomain.com/api/og?title=${encodeURIComponent(post.title)}`,
          width: 1200,
          height: 630,
          alt: post.title,
        },
      ],
    },
    twitter: {
      card: "summary_large_image",
      title: post.metaTitle,
      description: post.metaDescription,
    },
  };
}

Root Layout Metadata

Set default metadata in your root layout that applies to all pages:

// app/layout.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://yourdomain.com"),
  title: {
    default: "Your Company | Custom Software Development",
    template: "%s | Your Company",
  },
  description: "Custom web design, development, and software solutions for businesses.",
  openGraph: {
    type: "website",
    siteName: "Your Company",
    locale: "en_US",
  },
  twitter: {
    card: "summary_large_image",
    creator: "@yourhandle",
  },
  robots: {
    index: true,
    follow: true,
  },
};

The title.template pattern means page-level titles like "About Us" become "About Us | Your Company".

metadataBase is critical β€” it resolves relative URLs in your Open Graph images to absolute URLs.

Dynamic Open Graph Images

Generate OG images dynamically using Next.js's built-in ImageResponse:

// app/api/og/route.tsx
import { ImageResponse } from "next/og";

export const runtime = "edge";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const title = searchParams.get("title") ?? "Your Company";

  return new ImageResponse(
    (
      <div
        style={{
          width: "100%",
          height: "100%",
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          justifyContent: "center",
          backgroundColor: "#0a0a0a",
          color: "#ffffff",
          padding: "60px",
        }}
      >
        <div style={{ fontSize: 60, fontWeight: 700, textAlign: "center" }}>
          {title}
        </div>
        <div style={{ fontSize: 24, marginTop: 20, color: "#a3a3a3" }}>
          yourdomain.com
        </div>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  );
}

Or use the file-based approach with opengraph-image.tsx:

// app/opengraph-image.tsx
import { ImageResponse } from "next/og";

export const runtime = "edge";
export const alt = "Your Company";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default function Image() {
  return new ImageResponse(
    (
      <div style={{ /* your design */ }}>
        Your Company
      </div>
    ),
    { ...size }
  );
}

Essential Meta Tags Checklist

Every Page

  • <title>: Unique, under 60 characters
  • meta description: Unique, under 160 characters
  • og:title: Matches or slightly differs from title
  • og:description: Matches or slightly differs from description
  • og:image: 1200x630px image
  • og:url: Canonical URL
  • og:type: "website" or "article"
  • twitter:card: "summary_large_image"

Blog Posts (Additional)

  • article:published_time: ISO date
  • article:author: Author name
  • article:tag: Relevant tags

Robots

  • robots: "index, follow" for public pages
  • robots: "noindex" for utility pages (thank you, success)

Canonical URLs

Prevent duplicate content issues:

export const metadata: Metadata = {
  alternates: {
    canonical: "https://yourdomain.com/about",
  },
};

JSON-LD Structured Data

Add structured data alongside meta tags:

export default function AboutPage() {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "AboutPage",
    name: "About Your Company",
    description: "Learn about our team and mission.",
    url: "https://yourdomain.com/about",
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <main>...</main>
    </>
  );
}

Testing

  1. Google Rich Results Test: Validate structured data
  2. Facebook Sharing Debugger: Preview OG tags
  3. Twitter Card Validator: Preview Twitter cards
  4. LinkedIn Post Inspector: Preview LinkedIn sharing
  5. Open Graph Preview: opengraph.xyz for quick checks

Need SEO Optimization?

We configure comprehensive SEO, meta tags, structured data, and OG images for every site we build. Contact us for SEO and web development services.

SEOmeta tagsOpen GraphNext.jstutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles