How to setup dynamic meta tags for SEO using getServerSideProps in Next.js?

How to setup dynamic meta tags for SEO using getServerSideProps in Next.js?

In a previous article, we discussed about leveraging the inbuilt SEO features of Next.js for pages using the next/head library. We learnt on how we can add meta tags to our page to generate site previews and embeds for our pages.

Working with meta tags on pages with listing content like home, product catalog, etc. is straightforward. We can preset this values and need not rely on fetched content for the page title.

However, in cases, where you have dynamic page title like category listing page, product detail page, etc., it would be useful to update the title dynamically. You can access the parameters from the url, and fetch data to update the Head dynamically, but because this is retrieved on the client side, it will be undefined when the link is shared.

To overcome this, we can make use of server side rendering and get the dynamic details to be retrieved at the server end rather than on the client end. In this article, we are going to create a basic Next.js app to fetch the Houses of Hogwarts. I will create a listing and detail page and show you how to make use of getServerSideProps to fetch data and send it to the page.

Note: This article makes use of the pages router.

Getting Started

(you can alternatively make use of your own project and skip to the “Fetching Houses Detail Data” section if you already have a listing and a detail page).

We will create a Next.js project to display a list of houses of Hogwarts and on clicking each house, we can view details about it. We will make use of the following free API: https://wizard-world-api.herokuapp.com/Houses

Create a new Next.js project using:

yarn create next-app

For the project configuration, I have used the following settings. I have chosen to use tailwind for styling and the src directory to keep our code organized.

Creating the listing page

For the listing page, we will fetch data from the API and list it in the UI. The image below shows the list of houses and its founders.

Now the traditional way of fetching data in a React application is to make use of the useEffect hook to call the fetch function.

import { useEffect, useState } from "react";
import CoffeeHead from "@/components/CoffeeHead";
import Link from "next/link";

export default function Home() {
    const [housesData, setHousesData] = useState([]);

    const fetchData = async () => {
        const res = await fetch(
            "https://wizard-world-api.herokuapp.com/Houses"
        );
        const data = await res.json();

        setHousesData(data);
    };
    useEffect(() => {
        fetchData();
    }, []);

    return (
        <>
            <CoffeeHead
                title={"Houses of Hogwarts"}
                description={"A Next.js app to list the houses of Hogwarts"}
                image={`${process.env.NEXT_PUBLIC_SITE_URL}/Hogwarts-Houses.jpg`}
                page_url={process.env.NEXT_PUBLIC_SITE_URL}
            />
            <h1 className="text-2xl font-bold text-center my-4">
                Houses of Hogwarts
            </h1>

            <ul className="space-y-2 max-w-md m-auto">
                {housesData.map((house) => (
                    <li
                        key={house.id}
                        className="flex flex-col p-3 bg-white text-black border rounded"
                    >
                            <p>{house.name}</p>
                            <p className="text-sm text-gray-600">
                                Founder: {house.founder}
                            </p>
                    </li>
                ))}
            </ul>
        </>
    );
}

I have made use of the CoffeeHead component from the meta-tags article and because we know this is the home page, I have statically set the values for the title, description, image_url and page_url.

For the page_url and the image_url, I have retrieved the NEXT_PUBLIC_SITE_URL from the .env file so that I can account for the change in the url upon deployment.

Link to the Listing Detail Page

Dealing with the static listing page is easier when compared to the dynamic detail page. First, let us link the listing item to the detail page. We can do this by replacing li with Link to the detail page.

<Link
    key={house.id}
    href={`/houses/${house.id}`}
    className="flex flex-col p-3 bg-white text-black border rounded"
>
    <p>{house.name}</p>
    <p className="text-sm text-gray-600">
        Founder: {house.founder}
    </p>
</Link>

Here we have set the href to the houses detail page and we are passing the house.id to the page as a parameter

Creating the Houses Detail page

Let us create a detail page by making use of the dynamic routes feature that Next.js has provided us. Create a folder called houses in the pages folder and add a file called [id].js. This is the detail page.

First let us retrieve and display the id of the house passed as a parameter in the router. We do this by making use of the useRouter hook.

import { useRouter } from "next/router";
import React from "react";

const HouseDetail = () => {
    const router = useRouter();
    const { id } = router.query;
    return <div>House ID: {id}</div>;
};

export default HouseDetail;

On clicking any of the houses, you will now see a detail page with like below:

Fetching Houses Detail Data

We can go with the traditional way of making use of the fetch function in the useEffect hook like we did for the home page. But since the useEffect hook is called on the client side, the page title and details will be undefined on page load or share. It will only render in the browser, which will not be useful to search engines or when the page is shared.

So, we will need to make use of getServerSideProps function and fetch our data on the server side.

export const getServerSideProps = async (context) => {
    const { id } = context.query;

    const res = await fetch(
        `https://wizard-world-api.herokuapp.com/Houses/${id}`
    );
    const house = await res.json();
    house.image = `${process.env.NEXT_PUBLIC_SITE_URL}/Hogwarts-Houses.jpg`;

    const page_url = context.req.headers.host + `/houses/${id}`;

    return { props: { house, page_url } };
};

I retrieve the id using the context and then fetch the data from the API. I also dynamically generate the page url using host from the request header. The house and page_url are then passed as props to the Page component.

Here is the entire code for the detail page:

import CoffeeHead from "@/components/CoffeeHead";
import { useRouter } from "next/router";
import React from "react";

const HouseDetail = ({ house, page_url }) => {
    const router = useRouter();
    const { id } = router.query;
    return (
        <>
            <CoffeeHead
                title={house.name}
                description={`${house.name} was founded by ${house.founder} and represented by ${house.animal}.`}
                image={house.image}
                page_url={page_url}
            />

            <a
                href="#"
                className="block max-w-md p-6 bg-white border border-gray-200 rounded-lg shadow m-auto mt-6"
            >
                <h5 className="mb-2 text-2xl font-bold text-gray-900">
                    {house.name}
                </h5>
                <p className="font-normal text-gray-700">
                    Founded by {house.founder}
                </p>
                <p className="font-normal text-gray-700">
                    House Animal: {house.animal}
                </p>
            </a>
        </>
    );
};

export default HouseDetail;

export const getServerSideProps = async (context) => {
    const { id } = context.query;

    const res = await fetch(
        `https://wizard-world-api.herokuapp.com/Houses/${id}`
    );
    const house = await res.json();
    house.image = `${process.env.NEXT_PUBLIC_SITE_URL}/Hogwarts-Houses.jpg`;

    const page_url = context.req.headers.host + `/houses/${id}`;

    return { props: { house, page_url } };
};

You will see that the meta tags are generated on the server side and rendered in the page.

There you have it! Dynamically generated meta tags by making using of the SSR feature of Next.js.

If you want to know more about getServerSideProps, check out this page: https://nextjs.org/docs/pages/api-reference/functions/get-server-side-props

Drop a comment if you have any questions.


This page is part of the Next.js playbook, which is in the works at Coffee. Drop a mail to if you want to get on the waitlist.