How to Use Next.js for SEO and Server-Side Rendering Strategies

next.js for seo

Optimizing your website or blog content for search engines using SEO (Search Engine Optimization) is a core digital marketing strategy.

SEO is measured through performance metrics (page loading time), structured data (semantic and information architecture), and user experience (good usability), and enables more users to access your content and discover your offering. 

However, since frameworks for building Single-Page Applications (SPAs) like Angular and React have gained popularity, many web pages have dipped in their online visibility and search engine rankings.

This is because these frameworks solely render the page content in the browser (Client-side) through JavaScript file downloads, meaning search engine crawlers struggle to inspect page content.

To make your website more search engine and SEO friendly, it’s best to generate pages on the server side (Server-side Rendering — SSR) to ensure content is readily available — and more easily crawlable — when accessing a URL. That’s where Next.js for SEO comes in. 

Why is Next.js good for SEO?

Next.js offers several features that benefit SEO, including font and image optimization, custom head, script, and link components.

However, its primary benefit is its ability to render web pages on the server (SSR) instead of in the browser (Client-side).

This allows search engine crawlers to better understand and index web pages. It also provides a faster load time, improving important Core Web Vitals that are key to SEO success. 

In this article, we’ll explore how to leverage Next.js (one of the most popular React frameworks) to create server-side rendered pages optimized for search engines. 

How to leverage Next.js for SEO

To illustrate how to use Next.js for SEO, we’ll create a project called “Country of the Day” that displays information about a given country based on what day it is using the publicly-available API REST Countries.

The repository for this project is available on GitHub.

Project setup

With the API integrations set up and the business logic in place, we can access today’s country information with the following line of code:

const country = await getCountry();Code language: TypeScript (typescript)

The ‘country‘ object provides the country’s name, capital city, flag, population, and currency, among others, as shown in this example.

Static metadata

To define the title and meta description of the page, export a constant called ‘metadata’ within the page file, as shown in the example below:

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'Country of the day',
  description: 'Discover the country of the day in our amazing page!',
}
 
export default function Page() {}Code language: TypeScript (typescript)

You can also create a standardized template that automatically adds the website name to each page title, increasing brand awareness.

You can do this in the layout.tsx file at the root of the app directory:

import { Metadata } from 'next';

export const metadata: Metadata = {
  title: {
    default: 'Country of the day',
    template: '%s - Country of the day',
  }
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return children;
}Code language: TypeScript (typescript)

Thanks to this code, all website pages will follow the same “page title + website name” structure. 

Dynamic metadata

Alternatively, you can create dynamic metadata within the application by exporting an asynchronous function called ‘generateMetadata‘, as shown in the example below:

import { Metadata } from 'next'
 
export async function generateMetadata(): Promise<Metadata> {
  const country = await getCountry()

  const {
    name,
    flags,
  } = country;
 
  return {
    title: name.common,
    description: name.official,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...flags.png],
    },
  }
}
 
export default function Page() {}Code language: TypeScript (typescript)

This code retrieves the name and flag of the country to create the title, meta description, and Open Graph images of the page.

Dynamic Metadata also allows you to retrieve parameters from the page’s URL, such as the id or the slug. To do this, use the following code:

import { Metadata } from 'next'
 
type Props = {
  params: { id: string }
}
 
export async function generateMetadata(
  { params }: Props,
): Promise<Metadata> {
  // read route params
  const id = params.id
 
  // fetch data
  const product = await fetch(`https://.../${id}`).then((res) => res.json())
 
  return {
    title: product.title,
    description: product.description,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...product.image.url],
    },
  }
}
 
export default function Page({ params }: Props) {}Code language: TypeScript (typescript)

File-based metadata

If you want to create the app icons for your Country of the Day application, you can use the file-based metadata with Vercel’s Edge Functions.

To do this, create a favicon based on the country of the day. Using the Next.js documentation, add a file called icon.tsx in the root of the app directory:

import getCountry from "@/services/country";
import { ImageResponse } from "next/og";

export const runtime = "edge";

export const size = {
  width: 32,
  height: 32,
};
export const contentType = "image/png";

export default async function Icon() {
  const country = await getCountry();

  return new ImageResponse(
    (
      <div
        style={{
          background: "#333",
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          paddingLeft: 4,
          paddingRight: 4,
        }}
      >
        <img src={country.flags.png} alt={country.name.official} />
      </div>
    ),
    {
      ...size,
    },
  );
}Code language: TypeScript (typescript)

And there you have it — a dynamic favicon based on the country of the day 🤩

You can also create other files such as robots.txt, sitemap.xml, and Open Graph images, as shown in the example below.

OpenGraph

The Open Graph, an internet protocol developed by Facebook, standardizes metadata to improve how a page’s content is represented.

With it, you can enhance the link-sharing experience on the web, attracting more visitors to your content.

However, creating and sharing images for each new page on a website can be costly. Instead, let’s automate this process.

Similar to creating a favicon, create a file called opengraph-image.tsx in the directory of your page within the app folder of your application. 

This code will return an ImageResponse — an image generated with JavaScript. The coolest part is that you can create images based on the route segment of the application, as the Next.js documentation shows here.

In this example, let’s create an image similar to the layout below:

og tag image example brazilian tag

Using the Vercel OG Image Playground tool, create a file with the following code:

import getCountry from "@/services/country";
import { ImageResponse } from "next/og";

export const runtime = "edge";

export default async function Image() {
  const country = await getCountry();

  return new ImageResponse(
    (
      <div
        style={{
          backgroundColor: "#151718",
          width: "100%",
          height: "100%",
          display: "flex",
          textAlign: "center",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        <div
          style={{
            color: "rgb(237, 237, 237)",
            display: "flex",
            fontSize: "4rem",
            fontWeight: "bold",
            flexDirection: "column",
            alignItems: "center",
          }}
        >
          <img
            src={country.flags.png}
            width="320"
            height="200"
            style={{ borderRadius: 8, marginBottom: 48 }}
          />
          <div
            style={{
              backgroundImage:
                "linear-gradient(180deg, rgb(237, 237, 237), rgb(139, 149, 154))",
              backgroundClip: "text",
              color: "transparent",
            }}
          >
            {country.name.official}
          </div>
          <div style={{ display: "flex", fontSize: "2.4rem" }}>
            is the country of the day
          </div>
          <div style={{ display: "flex", fontSize: "1.6rem", marginTop: 48 }}>
            🔗 countryoftheday.vercel.app
          </div>
        </div>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    },
  );
}Code language: TypeScript (typescript)

As you can see, the code dynamically retrieves the name and flag of the country and creates an image through JavaScript code.

This will make the page content more engaging when you share the link on social media. We recommend using the website opengraph.xyz to conduct your tests after publishing the page:

social media sharing og tag example brazilian flag

JSON-LD

Last but not least, you can structure your content using the JSON Linked Data protocol to enhance the display of the page in Google Search results, for example.

Structured data is a standardized format to provide information about a page and classify its content.

For example, on a country page, structured data can provide information about the country’s population, total area, local currency, and so on.

“Rotten Tomatoes added structured data to 100,000 unique pages and measured a 25% higher click-through rate on pages enhanced with structured data compared to pages without this feature.

Continuing with the Country of the Day example, you could structure a page’s content as follows:

import { Country, WithContext } from "schema-dts";

import getCountry from "@/services/country";

export default async function Page() {
  const country = await getCountry();
  const { name, flags, coatOfArms, latlng } = country;

  const jsonLd: WithContext<Country> = {
    "@context": "https://schema.org",
    "@type": "Country",
    image: flags.png,
    latitude: latlng[0],
    longitude: latlng[1],
    logo: coatOfArms.png,
    name: name.official,
    alternateName: name.common,
  };

  return (
    <article>
      {/* Page content */}

      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
    </article>
  );
}Code language: TypeScript (typescript)

Note the inclusion of  “application/ld+json” to add structured data into the HTML of the page.

It’s also worth noting how the npm library schema-dts helps to type and correctly define the provided information.

With these measures, you’re on your way to achieving excellent results in search engines.

Plus, the examples presented already include server-side rendering, ensuring that the content is immediately available when accessing any page.

You can check out the finalized project at Country of the Day.

Next.js is a great choice for SEO projects

Since releasing Next.js version 13, Next.js has made a significant leap in its SEO-focused features. What previously required third-party libraries is now possible natively. 

Plus, developing an application with this framework ensures its performance is optimized for SEO from the get-go.

Here at Cheesecake, we’ve already started using Next.js for SEO with server-side rendering in some of our projects. 

To learn more about what we’ve been working on lately, be sure to check out our portfolio.

If you have a project that could benefit from Next.js or have an idea you’d like to discuss, send us a message — we’d love to hear from you. 

About the author.

Jonathan Felipe de Oliveira
Jonathan Felipe de Oliveira

I love helping people using creative solutions, from design to code. Also I love art, music, outdoors and travel. Pet and Plant father 🥸