Back to posts
Next.js SEO

Photo by Growtika

Next.js SEO

Eric Stober / May 27, 2025

Next.js is a powerful React framework. If you'd like to know more about it check out my Introduction to Next.js post.

In this post we will be covering some of the important steps you can take to improve your Next.js website's SEO. The steps are based on my own implementation of SEO for Next.js web applications. Pleae note: I will only be covering the Next.js App Router in this post.

What is SEO?

SEO is short for search engine optimization, which is the process of improving a website's visibility in the search results of search engines such as Google, Bing, etc. This process helps your website be seen by more users. There are many factors that contribute to SEO, however we will not get into all of those factors in this post.

Meta Tags

Meta tags are HTML tags that provide information about a web page to search engines. These tags are placed within the <head> section.

Standard meta tags you should include:

  • title
  • description
  • keywords
  • robots
  • viewport
  • charSet

Meta Tags for Next.js App Router

Static Metdata

Static metadata can be defined by adding export const metadata inside either page.tsx or layout.tsx:

export const metadata: Metadata = {
  title: 'Enter your title',
  description:
    'Enter your description',
  keywords: [
    'Enter',
    'Your',
    'Keywords',
  ],
  openGraph: {
    url: 'https://enter-your-domain.com',
    type: 'website',
    title: 'Enter your title',
    description:
      'Enter your description',
    images: [
      {
        url: 'Enter your image url',
        width: 1200,
        height: 630,
        alt: 'Enter your image alt'
      }
    ]
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Enter your title',
    description:
      'Enter your description',
    images: [
      {
        url: 'Enter your image url',
        width: 1200,
        height: 630,
        alt: 'Enter your image alt'
      }
    ]
  },
  alternates: {
    canonical: 'https://enter-your-domain.com'
  },
  icons: {
    icon: [
      {
        url: '/favicon.ico',
        type: 'image/x-icon'
      }
    ],
    shortcut: [
      {
        url: '/favicon.ico',
        type: 'image/x-icon'
      }
    ]
  }
};

Dynamic Metadata

Dynamic metadata can be defined by using the generateMetadata function. This is great for dynamic pages such as blog posts [slug]/page.tsx:

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

  if (!post) {
    return {
      title: 'Post Not Found | Enter your title',
      description: 'The requested post could not be found.'
    };
  }

  const { metadata } = post;
  const { title, summary, image } = metadata;

  return {
    title: `${title} | Enter your title`,
    description: summary || 'Read this amazing post on my blog.',
    openGraph: {
      title: `${title} | Enter your title`,
      description: summary || 'Read this amazing post on my blog.',
      images: image
        ? [
            {
              url: image,
              width: 1200,
              height: 630,
              alt: title
            }
          ]
        : undefined
    },
    twitter: {
      card: 'summary_large_image',
      title: `${title} | Enter your title`,
      description: summary || 'Read this amazing post on my blog.',
      images: image ? [image] : undefined
    }
  };
}

Note: If using the Next.js App Router, the charSet and viewport meta tags are automatically added.

JSON-LD Schema

JSON-LD stands for JavaScript Object Notation for Linked Data. JSON-LS is used to implement schema markup, which helps search engines understand the content of web pages.

You can use this tool to assist with generating a JSON-LD Schema Schema Markup Generator Tool.

Here is an example JSON-LD Schema:

const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'BlogPosting',
  headline: 'Enter your title',
  image: 'Enter your image url',
  datePublished: 'Enter your publish date',
  author: {
    '@type': 'Person',
    name: 'Enter your author name'
  },
  publisher: {
    '@type': 'Organization',
    name: 'Enter your name or organization name',
    logo: {
      '@type': 'ImageObject',
      url: 'Enter your image url'
    }
  },
  description: 'enter your descriptions',
  url: `https://enter-your-domain.com/enter-your-page`
};

Then place inside the page's HTML:

export default function Page() {
  return (
    <div>
      {/* other parts */}
      <script
        type='application/ld+json'
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
    </div>
  );
}

Test your JSON-LD Schema with Google's Rich Results Test.

Sitemap

A sitemap outlines the structure of your website so that search engines can crawl and index your website more efficiently. The example below will demonstrate how to inlcude both static and dynamic pages in your sitemap.

Define the sitemap.ts file in the app folder.

import { getAllPostSlugsAndDates } from '@/lib/posts';
import type { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const defaultPages: MetadataRoute.Sitemap = [
    {
      url: 'https://enter-your-domain.com',
      lastModified: new Date().toISOString(),
      changeFrequency: 'monthly',
      priority: 1
    },
    {
      url: 'https://enter-your-domain.com/posts',
      lastModified: new Date().toISOString(),
      changeFrequency: 'monthly',
      priority: 0.9
    },
    {
      url: 'https://enter-your-domain.com/contact',
      lastModified: new Date().toISOString(),
      changeFrequency: 'monthly',
      priority: 0.9
    }
  ];

  const postSlugs = getAllPostSlugsAndDates();

  const postPages = postSlugs.map(post => {
    return {
      url: `https://enter-your-domain.com/${post.slug}`,
      lastModified: post.publishedAt,
      changeFrequency: 'daily' as const,
      priority: 0.8
    };
  });

  const sitemap = [...defaultPages, ...postPages];

  return sitemap;
}

You can access the sitemap via https://enter-your-domain.com/sitemap.xml.

robots.txt

The robots.txt file tells search engines which pages to crawl and which pages to ignore. You can prevent search engines from crawling a page by adding it to the disallow list.

Define the robots.ts file in the app folder.

import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: ['/'],
      disallow: ['/admin/']
    },
    sitemap: ['https://eric-stober.com/sitemap.xml']
  };
}

You can access the sitemap via https://enter-your-domain.com/robots.txt.

Conclusion

By implementing the examples outlined in this post you can optimize your Next.js website SEO.

Here is a recap of the important points we touched on:

  1. Meta Tags
  2. JSON-LD Schema
  3. Sitemap
  4. robots.txt