Menu

Metadata System

What is Metadata?

Metadata is information that describes webpage content, contained within the HTML <head> tag, and is crucial for SEO and social media sharing.

Example:

<head>
  <title>Nexty.dev - Build and ship your SaaS faster</title>
  <meta name="description" content="Next.js SaaS starter template..." />
  <meta property="og:title" content="Nexty.dev" />
  <meta property="og:image" content="https://nexty.dev/og.png" />
  <link rel="canonical" href="https://nexty.dev/" />
  <link rel="alternate" hreflang="en-US" href="https://nexty.dev/" />
  <link rel="alternate" hreflang="zh-CN" href="https://nexty.dev/zh" />
</head>

Nexty.dev Metadata System

Core Function: constructMetadata()

Location: lib/metadata.ts

Purpose:

  • Unified generation of metadata for all pages
  • Automatic multilingual handling
  • Automatic generation of canonical and hreflang tags
  • Automatic generation of Open Graph and Twitter cards

Function Parameters

lib/metadata.ts
type MetadataProps = {
  page?: string              // Page name (deprecated, no configuration needed)
  title?: string             // Page title
  description?: string       // Page description
  images?: string[]          // OG images (relative or absolute paths)
  noIndex?: boolean          // Whether to prevent indexing (default false)
  locale?: Locale            // Current language (en, zh, ja)
  path?: string              // Page path (must start with /)
  canonicalUrl?: string      // Canonical URL (optional)
  availableLocales?: string[] // Available language list (optional)
  useDefaultOgImage?: boolean // Default true
}

1. title - Page Title

title: "About Us"

Generation Rules:

  • Homepage: {title} - {siteConfig.tagLine}
    • Example: Nexty.dev - Build and ship your SaaS faster
  • Other Pages: {title} | {siteConfig.name}
    • Example: About Us | Nexty.dev

If not provided, the default value from the multilingual file will be used (title in the Home namespace).

2. description - Page Description

description: "Learn more about our mission and team."

Purpose:

  • Displays in search results
  • Displays in social media cards

If not provided, the default value from the multilingual file will be used.

3. images - OG Images

images: post.featuredImageUrl ? [post.featuredImageUrl] : [],

Supported Formats:

  • Relative path: ["images/about.png"]https://nexty.dev/images/about.png
  • Absolute path: ["https://cdn.example.com/about.png"]

In most cases, you don't need to configure images for individual pages. The system will automatically select default images based on the page language, for example:

  • English: /og.png
  • Chinese: /og_zh.png
  • Japanese: /og_ja.png

4. noIndex - Prevent Indexing

noIndex: true

Purpose:

  • Generates <meta name="robots" content="noindex, nofollow" />
  • Tells search engines not to index the page

Use Cases:

  • Premium content pages
  • Pages accessible only after login
  • Test pages
  • 404 pages

5. locale - Current Language

Pass the current language identifier to automatically handle language information in metadata and necessary multilingual URLs.

locale: "zh" // en, zh, ja

Purpose:

  • Affects URL generation
  • Affects OG image selection
  • Affects <html lang="zh"> attribute

6. path - Page Path

path must start with / and should not include the language prefix, for example:

path: "/about"

constructMetadata() will automatically handle multilingual information and URLs by combining locale and path.

Purpose:

  • Used to generate canonical URLs, helping search engines identify the canonical version of a page to avoid duplicate content issues
  • Used to generate hreflang links, letting search engines understand the language and regional versions of a page, providing users in different regions with the correct language version

7. canonicalUrl - Canonical URL

canonicalUrl: "/blogs"

Use Cases:

When multiple URLs point to the same content, such as /blogs and /blogs?ref=docs, you need to tell search engines that their canonical version URL is /blogs. This prevents search engines from treating parameterized URLs as new pages, which would dilute SEO authority.

Typically, manual configuration is not needed as the system will automatically reference path for configuration. Manual configuration is only necessary when you need to point the current page to a different URL.

8. availableLocales - Available Language List

availableLocales: ["en", "zh"]  // Only English and Chinese versions available

Use Cases:

When a page doesn't have versions in all languages (such as blog articles).

Purpose:

  • Generates hreflang links only for existing language versions
  • Avoids generating links pointing to 404 pages

Example: Blog Article

// Detect which languages have this article
const availableLocales: string[] = []
for (const checkLocale of LOCALES) {
  const post = await getPostBySlug(slug, checkLocale)
  if (post) availableLocales.push(checkLocale)
}
 
// Generate hreflang only for existing languages
constructMetadata({
  // ...
  availableLocales: availableLocales.length > 0 ? availableLocales : undefined,
})

If this parameter is not provided, hreflang links will be generated for all languages.

9. useDefaultOgImage - Whether to Use Default OG Image

useDefaultOgImage defaults to true, meaning it uses the automatically matched OG Image from constructMetadata.

If you want to automatically generate OG Image styles and content based on content in dynamic pages, you need to set useDefaultOgImage to false. At the same time, create an opengraph-image.tsx file in the same directory as the dynamic page to develop content-adaptive OG Image styles.

Generated Metadata Content

constructMetadata() generates the following content:

1. Basic Meta Tags

<title>About Us | Nexty.dev</title>
<meta name="description" content="Learn more about our mission..." />
<meta name="keywords" content="" />
<meta name="author" content="nexty.dev" />

2. Canonical URL

<link rel="canonical" href="https://nexty.dev/about" />

3. Multilingual hreflang

<link rel="alternate" hreflang="en-US" href="https://nexty.dev/about" />
<link rel="alternate" hreflang="zh-CN" href="https://nexty.dev/zh/about" />
<link rel="alternate" hreflang="ja-JP" href="https://nexty.dev/ja/about" />
<link rel="alternate" hreflang="x-default" href="https://nexty.dev/about" />

Purpose of x-default:

  • Tells search engines which version to display when the user's language doesn't match
  • Usually points to the default language (English)

4. Open Graph Tags

<meta property="og:type" content="website" />
<meta property="og:title" content="About Us | Nexty.dev" />
<meta property="og:description" content="Learn more about..." />
<meta property="og:url" content="https://nexty.dev/about" />
<meta property="og:site_name" content="Nexty.dev" />
<meta property="og:locale" content="en" />
<meta property="og:image" content="https://nexty.dev/og.png" />
<meta property="og:image:alt" content="About Us | Nexty.dev" />

5. Twitter Card Tags

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="About Us | Nexty.dev" />
<meta name="twitter:description" content="Learn more about..." />
<meta name="twitter:site" content="https://nexty.dev/about" />
<meta name="twitter:image" content="https://nexty.dev/og.png" />
<meta name="twitter:creator" content="@judewei_dev" />

6. Robots Tags

<!-- noIndex = false (default) -->
<meta name="robots" content="index, follow" />
 
<!-- noIndex = true -->
<meta name="robots" content="noindex, nofollow" />

constructMetadata() Usage Examples

Scenario 1: Homepage

app/[locale]/layout.tsx
export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { locale } = await params
  const t = await getTranslations({ locale, namespace: 'Home' })
  
  return constructMetadata({
    title: t('title'), // Read from multilingual file
    description: t('description'), // Read from multilingual file
    locale: locale as Locale, // Pass current language identifier for automatic handling of language info and necessary multilingual URLs in metadata
    path: '/', // Homepage path
  })
}

Scenario 2: Regular Page

app/[locale]/(basic-layout)/about/page.tsx
export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { locale } = await params
  const t = await getTranslations({ locale, namespace: 'About' })
  
  return constructMetadata({
    title: t('title'),
    description: t('description'),
    locale: locale as Locale,
    path: `/about`,
  })
}

Scenario 3: Using Custom OG Image

export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { locale } = await params
  const t = await getTranslations({ locale, namespace: 'Pricing' })
  
  return constructMetadata({
    title: t('title'),
    description: t('description'),
    locale: locale as Locale,
    path: `/pricing`,
    images: ['images/og/pricing.png'],  // Custom OG image
  })
}

Scenario 4: Dynamic OG Image

export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { slug } = await params;
  const result = await getProductBySlug(slug);
 
  if (!result.success || !result.data) {
    return constructMetadata({
      title: "404",
      description: "Product not found",
      noIndex: true,
      path: `/product/${slug}`,
    });
  }
 
  const product = result.data;
  const fullPath = `/product/${slug}`;
 
  return constructMetadata({
    title: product.name,
    description: product.tagline,
    path: fullPath,
    useDefaultOgImage: false, // 🔑 Key: Disable default image
    // Next.js will automatically find and apply the opengraph-image.tsx file in the same directory.
  })
}

Scenario 5: Blog Article (Multilingual)

app/[locale]/(basic-layout)/blogs/[slug]/page.tsx
export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { locale, slug } = await params
  const { post } = await getPostBySlug(slug, locale)
  
  if (!post) {
    return constructMetadata({
      title: "404",
      description: "Page not found",
      noIndex: true,  // Don't index 404 pages
      locale: locale as Locale,
      path: `/blogs/${slug}`,
    })
  }
  
  // Detect which languages have this article
  const availableLocales: string[] = []
  for (const checkLocale of LOCALES) {
    const { post: localePost } = await getPostBySlug(slug, checkLocale)
    if (localePost) {
      availableLocales.push(checkLocale)
    }
  }
  
  return constructMetadata({
    title: post.title,
    description: post.description,
    images: post.featuredImageUrl ? [post.featuredImageUrl] : [],
    locale: locale as Locale,
    path: `/blogs/${post.slug.replace(/^\//, '')}`,
    availableLocales: availableLocales.length > 0 ? availableLocales : undefined,
  })
}

Scenario 6: Premium/Restricted Content

export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { locale } = await params
  
  return constructMetadata({
    title: "Premium Dashboard",
    description: "Access your premium features.",
    locale: locale as Locale,
    path: `/dashboard/premium`,
    noIndex: true,  // Don't let search engines index premium content
  })
}

Scenario 7: Merging Multiple URLs with Canonical

// Scenario 1: /free-trial and /pricing point to the same content, but you want search engines to consider /pricing as the canonical URL
// Scenario 2: /blogs and /blogs?ref=docs point to the same page, you need to tell search engines /blogs is the canonical URL
 
export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { locale } = await params
  
  return constructMetadata({
    title: "Start Your Free Trial",
    description: "Try all features for free.",
    locale: locale as Locale,
    path: "/free-trial",
    canonicalUrl: "/pricing",  // Point to /pricing as canonical URL
  })
}

Scenario 8: Using External CDN for OG Images

export async function generateMetadata({
  params,
}: MetadataProps): Promise<Metadata> {
  const { locale } = await params
  
  return constructMetadata({
    title: "Case Study",
    description: "How we helped Company X achieve results.",
    locale: locale as Locale,
    path: "/case-studies/company-x",
    images: [
      "https://cdn.yourdomain.com/case-studies/company-x-og.png"  // Absolute URL
    ],
  })
}