DEV Community

Cover image for SEO and i18n Implementation Guide for Next.js App Router: Dynamic Metadata and Internationalization
Sho Ayuba
Sho Ayuba

Posted on

SEO and i18n Implementation Guide for Next.js App Router: Dynamic Metadata and Internationalization

This article will provide practical implementation methods for optimizing SEO in internationalized (i18n) web applications using Next.js 14's App Router and Next-intl. We'll focus on metadata configuration for dynamically generated pages and multilingual support, using an e-commerce site as a concrete example.

Target Audience

This guide is for developers who:

  • Have basic understanding of Next.js App Router
  • Know TypeScript fundamentals
  • Have basic SEO knowledge

Development Environment

{
  "next": "^14.2.22",
  "next-intl": "^3.26.3",
  "react": "^18",
  "typescript": "^5"
}
Enter fullscreen mode Exit fullscreen mode

1. Project Structure

1.1 Directory Layout

src/
  ├── app/
  │   └── [locale]/
  │       └── product/
  │           └── [id]/
  │               ├── page.tsx
  │               └── layout.tsx
  ├── lib/
  │   └── metadata/
  │       └── productMetadata.ts
  ├── api/
  │   └── model/
  │       └── Product.ts
  └── i18n/
  │   ├── routing.ts
  │
  └── messages/
      ├── en.json
      └── ja.json
Enter fullscreen mode Exit fullscreen mode

1.2 Implementing Internationalized Routing

// src/i18n/routing.ts
import { defineRouting } from "next-intl/routing";
import { createNavigation } from "next-intl/navigation";

export const routing = defineRouting({
  locales: ["en", "ja"],
  defaultLocale: "en",
});

export const { Link, redirect, usePathname, useRouter } = createNavigation(routing);
Enter fullscreen mode Exit fullscreen mode

1.3 Product Data Type Definition

// src/api/model/Product.ts
export interface Product {
  id: string;
  name: string;
  code: string;
  category: {
    id: string;
    name: string;
  };
  manufacturer?: string;
  description: string;
  images: {
    small: string;
    large: string;
  };
  price: number;
  stock: number;
}
Enter fullscreen mode Exit fullscreen mode

2. Dynamic Metadata Generation

2.1 Implementing Metadata Generation Logic

// src/lib/metadata/productMetadata.ts
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import type { Product } from "@/api/model/Product";
import { routing } from "@/i18n/routing";

export async function generateProductMetadata(
  productDataPromise: Promise<Product>,
  locale: string
): Promise<Metadata> {
  const [productData, t] = await Promise.all([
    productDataPromise,
    getTranslations({ locale, namespace: "metadataProduct" }),
  ]);

  // SEO optimization: Information structuring
  const descriptionParts = [
    productData.name,
    `${productData.category.name} #${productData.code}`,
    productData.manufacturer ? `${t("manufacturedBy")}${productData.manufacturer}` : "",
    t("checkPrice")
  ].filter(Boolean);

  const description = descriptionParts.join(" | ");

  // Dynamic keyword generation
  const productSpecificKeywords = [
    productData.name,
    productData.category.name,
    productData.manufacturer,
    `${productData.name} ${t("price")}`,
    `${productData.name} ${t("stock")}`,
    `${productData.category.name} ${t("products")}`
  ].filter(Boolean);

  const baseKeywords = t("keywords").split(", ");
  const allKeywords = [...new Set([...productSpecificKeywords, ...baseKeywords])];

  return {
    title: `${productData.name} | ${productData.category.name} | ${t("siteTitle")}`,
    description,
    keywords: allKeywords.join(", "),
    metadataBase: new URL("https://example.com"),

    alternates: {
      canonical: `/${locale}/product/${productData.id}`,
      languages: {
        "x-default": `/en/product/${productData.id}`,
        ...Object.fromEntries(
          routing.locales.map((l) => [l, `/${l}/product/${productData.id}`])
        ),
      },
    },

    openGraph: {
      type: "website",
      siteName: t("siteTitle"),
      title: `${productData.name} | ${productData.category.name}`,
      description,
      locale,
      alternateLocale: routing.locales.filter((l) => l !== locale),
      images: productData.images.large ? [
        {
          url: productData.images.large,
          width: 1200,
          height: 630,
          alt: productData.name,
        },
      ] : undefined,
    },
    robots: {
      index: true,
      follow: true,
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

2.2 Translation File Setup

// messages/en.json
{
  "metadataProduct": {
    "siteTitle": "Example Store",
    "manufacturedBy": "Manufactured by",
    "checkPrice": "Check price and availability",
    "price": "price",
    "stock": "stock",
    "products": "products",
    "keywords": "online store, ecommerce, shopping, price comparison, stock check, product reviews, online shopping"
  }
}

// messages/ja.json
{
  "metadataProduct": {
    "siteTitle": "Example Store",
    "manufacturedBy": "製造元",
    "checkPrice": "価格と在庫状況をチェック",
    "price": "価格",
    "stock": "在庫",
    "products": "商品",
    "keywords": "オンラインストア, eコマース, 通販, 価格比較, 在庫確認, 商品レビュー, オンラインショッピング"
  }
}
Enter fullscreen mode Exit fullscreen mode

2.3 Page Component Implementation

// app/[locale]/product/[id]/page.tsx
import { generateProductMetadata } from "@/lib/metadata/productMetadata";
import { cache } from 'react';
import { notFound } from 'next/navigation';

const getProductDataCached = cache(async (id: string) => {
  const response = await fetch(`/api/products/${id}`);
  if (!response.ok) {
    notFound();
  }
  return response.json();
});

export async function generateMetadata({ 
  params 
}: { 
  params: { id: string; locale: string } 
}) {
  const productDataPromise = getProductDataCached(params.id);
  return generateProductMetadata(productDataPromise, params.locale);
}

export default async function ProductPage({ 
  params 
}: { 
  params: { id: string } 
}) {
  const productData = await getProductDataCached(params.id);
  // Page content implementation
}
Enter fullscreen mode Exit fullscreen mode

3. SEO Optimization Key Points

3.1 Metadata Structuring

When structuring metadata, consider:

  1. Title Optimization

    • Order information by importance
    • Use pipes (|) for proper separation
    • Include site name for brand recognition
  2. Description Structuring

    • Clear information segmentation
    • Include only necessary information
    • Match search intent
  3. Keyword Optimization

    • Combine dynamic and static keywords
    • Consider search patterns
    • Natural keyword placement

3.2 Multilingual Considerations

  1. URL Structure Design

    • Include language codes
    • Set proper canonical URLs
    • Configure x-default
  2. Content Optimization

    • Language-appropriate descriptions
    • Cultural considerations
    • Proper keyword translation

3.3 OpenGraph Configuration

For OpenGraph settings:

  1. Basic Configuration

    • Choose appropriate type settings
    • Maintain siteName and title consistency
    • Optimize descriptions
  2. Image Handling

    • Set appropriate sizes and ratios
    • Configure alt attributes
    • Handle missing images gracefully

4. Performance Optimization

4.1 Data Fetching Optimization

  1. Utilizing Cache
   const getProductDataCached = cache(async (id: string) => {
     // Data fetching logic
   });
Enter fullscreen mode Exit fullscreen mode
  • Prevent duplicate requests
  • Improve response time
  • Ensure consistent data
  1. Error Handling
    • Display 404 pages appropriately
    • Maintain user experience
    • Ensure crawlability

4.2 Rendering Optimization

  1. Separate Metadata Generation

    • Clear logic separation
    • Improve reusability
    • Enhance maintainability
  2. Optimize Async Processing

    • Parallel processing for speed
    • Efficient resource usage

Summary

We've covered key implementation points for SEO and internationalization in Next.js App Router:

  • Dynamic metadata generation implementation
  • Multilingual support considerations and optimization
  • SEO best practices
  • Performance optimization techniques

These implementations enable building SEO-optimized multilingual web applications effectively.

References

Top comments (1)

Collapse
 
mehmetakar profile image
mehmet akar

SEO Optimization in Next.js is crucial but unfortunately it is often overlooked. Thanks, Great Tips!