Skip to main content
Mohamed

SEO in Next.js

Explore how you can improve the SEO of your Nextjs website

Introduction

If you’re building a website with Next.js , you probably want people to actually find it on Google, right? Well, that’s where SEO (Search Engine Optimization) comes in!

Good SEO isn’t just about stuffing keywords everywhere , it’s about helping search engines understand your content so they can rank and index it .

and Next.js makes this super easy with built-in tools to manage metadata, social previews, sitemaps, and more.

What I Will Cover in This Article

  • ✅ Set up metadata (static & dynamic)
  • ✅ Social previews of your website
  • ✅ Generate sitemaps
  • ✅ Generate robots.txt


I) Metadata in Next.js (Static & Dynamic)

Metadata provides key information about our webpage to search engines and social media platforms for example its includes :

  • Title (<title>) – The main page title shown in search results and in the tab bar of the browser
  • Meta Description (<meta name="description">) – A small description of your page’s content

1) How To Use Metadata in Next.js

Nextjs provide an metadata api that simplifies managing metadata across pages
And there is two types of metadata (static and dynamic metadat) , the static metadata related to the static pages for example (home page , contact page ,..) , and dynamic metadata related to the dynamic pages for example product page

a) Static metadata

So to define metadata in static pages you need just to export an object named ‘metadata’ with the type of ‘Metadata’ from next if you are using typescript
Example :

export const metadata: Metadata = {
    title: 'Smarty E-Book Store',
    description: "Find your e-book easily with Smarty E-Book Store",
};

This is a basic usage of the metadata
But in real websites we need to handle a lot of pages ,
for example in each page we normally need to display the main name of the website followed by the name of the page like (All Listed E-Books | Smarty E-book)
so to do this we should to define a template in the layout.tsx file
for example :

export const metadata: Metadata = {
  title: {
    default: "Smarty E-Book Store",
    template: "%s | Smarty E-book"
  },
  description: "Find your e-book easily with Smarty E-Book Store",
};

In this example we defined a template for the title , so all pages’s titles under this layout.tsx file will end by the ‘| Smarty E-Book ‘ and the main page of this layout will take the default title which is ‘Smarty E-Book Store’

Here is an example from a page under this layout :

export const metadata: Metadata = {
  title : 'List Of E-Books' , 
  description: 'all the listed e-books in smarty e-books'
}

But in some cases we don’t need to use a template for the title ; so all we need in this case is to use the absolute property inside the title

export const metadata: Metadata = {
  title: {
    absolute: 'List Of E-Books'
  },
  description: 'all the listed e-books in smarty e-books'
}

So here even if the template title exist , the title of this page will override it and use only the provided title in the absolute property

In this object (metadata) we have also the possibility to define the open graph metatags that help us to control how our urls will display when we share it on social media

For example

export const metadata: Metadata = {
  title: {
    default : "Smarty E-Book Store",
    template : "%s | Smarty E-book"
  },
  description: "Find your e-book easily with Smarty E-Book Store",
  openGraph: {
    title: "Smarty E-Book Store",
    description: "Find your e-book easily with Smarty E-Book Store",
    url: "http://localhost:3000",
    type: "website",
    siteName: "Smarty E-Book"
  }
};

b) Dynamic metadata

We need dynamic metadata when we deal with dynamic pages and data , for example if we are fetching data from an api that provides products we can’t determine each metadata for each product page

Here the generateMetadata() asynchronous function came to help us to generate metadata for all our fetched items

export async function generateMetadata({ params }: Params): Promise<Metadata | undefined> {

  const ebook = await getEbookData(params.id)
  if (!ebook[0]) {
    return
  }

  return {
    title: ebook[0].title,
    description: ebook[0].description,
    openGraph: {
      title: ebook[0].title,
      description: ebook[0].description,
      type: 'book',
      url : `http://localhost:3000/books/${ebook[0].id}`
    }
  }
}

So this hero function used usually in the single item page which placed for example in /books/[id]/page.tsx

And it has the possibility to accept the params which we will us to fetch the data related to each param (id in this example)

So as you can see we return a metadata object that contains the same properties of the metadata object that we use in the static metadata

II) Creating a Sitemap in Next.js

1) Why we will need a sitemap

Sitemap is the key of our website that search engines (like google , bing , etc) can use to discover and index our pages faster and more efficiently

2) How to generate sitemap in nextjs

a) Static sitemap.xml

The main format of the sitemap is a xml file named ‘sitemap.xml’ , so if your website is small and not includes dynamic pages , the nextjs gives you the possibility to create it simply inside the app folder and write the static pages in it

For example

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://localhost:3000</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
    <changefreq>monthly</changefreq>
  </url>
  <url>
    <loc>http://localhost:3000/books</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
    <changefreq>monthly</changefreq>
  </url>
</urlset>

b) Dynamic sitemap.xml

But if you have dynamic data you absolutely need to generate this file programmatically by creating sitemap.js or sitemap.ts (if you are using typescript) inside the app folder , in this file you need to export an asynchronous function that’s return a sitemap object

For example

export default async function sitemap() : Promise<MetadataRoute.Sitemap> {
    
    const ebooks : Ebook[] = await getAllEbooks()

    const ebooksUrls = ebooks.map((ebook)=> ({
        url : `http://localhost:3000/books/${ebook.id}`,
        lastModified : new Date(),
    }))

    return [
        {
            url : 'http://localhost:3000' ,
            lastModified : new Date(),
            changeFrequency : 'monthly',
        },
        {
            url : 'http://localhost:3000/books' ,
            lastModified : new Date(),
            changeFrequency : 'monthly',
        },
        ...ebooksUrls
    ]

}

So in this example we fetch the dynamic data and create an object ebooksUrls that includes just the url and the lastModified property , then we inject it under the static pages

This function will generate a sitemap.xml file in your project and you can access it in this example from http://localhost:3000/sitemap.xml

the generated sitemap.xml in this example is :

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://localhost:3000</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
    <changefreq>monthly</changefreq>
  </url>
  <url>
    <loc>http://localhost:3000/books</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
    <changefreq>monthly</changefreq>
  </url>
  <url>
    <loc>http://localhost:3000/books/d3d6</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
  </url>
  <url>
    <loc>http://localhost:3000/books/8785</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
  </url>
  <url>
    <loc>http://localhost:3000/books/5381</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
  </url>
  <url>
    <loc>http://localhost:3000/books/3348</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
  </url>
  <url>
    <loc>http://localhost:3000/books/5156</loc>
    <lastmod>2025-02-09T11:40:08.826Z</lastmod>
  </url>
</urlset>

III) Controlling Crawlers with robots.txt

robots.txt just as important as sitemap.xml , it’s tells search engines which parts of your site they can or cannot crawl

1) Static robots.txt

in nextjs if you have a small website you can write the robots.txt directly in the app folder for example

User-Agent: *
Allow: /
Disallow: /admin/
Sitemap: http://localhost:3000/sitemap.xml

So in this example we defined the User-agent to * which means that the rules of this file will be applied to all the crawlers for example (Googlebot, Bingbot)

And the allow property which means that the crawlers can index all the pages ,

But the rules in the Disallow property can override it so the crawlers in this case cannot index the admin directory

We also tell search engines where to find our sitemap

2) Dynamic robots.txt

Nextjs offer also a programmatical way to generate a dynamic robots.txt , by creating a file named robots.js or robots.ts (if you are in love with typescript) inside the app folder export an asynchronous function just like we did in the sitemap , but we need to return a Robots object from the MetadataRoute

For example if we want to generate the previous robots.txt file we need to defined it like this :

import { MetadataRoute } from "next";

export default async function robots(): Promise<MetadataRoute.Robots> {

    return {
        rules : {
            allow : '/',
            userAgent : '*',
            disallow : '/admin/'
        },
        sitemap : 'http://localhost:3000/sitemap.xml'
    }
}


Conclusion

SEO might seem like a lot at first, but once you’ve got the basics down, it’s really just about consistency and making sure Google knows what’s up. So, take it one step at a time, keep improving, and before you know it, your site will be climbing those rankings like it’s in a race!

Thanks for reading, and good luck with your SEO adventures in Nextjs ❤️