How to fetch data with static rendering in next.js

dantonelli

Derya A. Antonelli

Posted on August 16, 2021

How to fetch data with static rendering in next.js

Welcome to my first post on How to? series. In this article, I'm going to show step-by-step tutorials to getting Next.js to render your fetched content on a statically generated page. For doing this, we'll use getStaticProps() function which will be covered in later sections.

At the end of tutorial, our page will look like this:

image

You can access the finished project here.

Prerequisites

  • A WordPress instance with WPGraphQL plugin.

In these examples, I utilized WordPress headless CMS (content-management system) for managing blog contents and WPGraphQL API for fetching data. If you are interested in learning about how to configure Wordpress CMS from the scratch, I suggest you check out Rob Kendal's Configuring WordPress as a headless CMS with Next.js article series. For this exercise, you are free to to fetch content from any headless CMS of your choice.

Step 1: Creating a Next.js project

We'll start by creating a new project by using project create tool by Next.js. Open your terminal, and run the following command:

npx create-next-app nextjs-blog
Enter fullscreen mode Exit fullscreen mode

Then, run this command:

cd nextjs-blog
Enter fullscreen mode Exit fullscreen mode

cd stands for "change directory", so we are simply changing our current directory to the project folder we just created.

This step is optional for VS code users. When you run the command below, your project folder will automatically open on VS Code:

code .
Enter fullscreen mode Exit fullscreen mode

In order to run our project on the development server, run this command:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Now your page will run on port 3000. This is how it looks:

image



Step 2: Remove files

Now open your project on any code editor you like (I prefer VS Code) and remove these files:

  • /pages/api
  • /pages

Alternatively, I generally find integrated terminal of VS Code very handy in managing the file operations. On VS Code, navigate to View > Terminal menu command to open your terminal. There's no need to change directory as it'll directly start in the root of the project. Then, type this command:

rm -r pages/api
Enter fullscreen mode Exit fullscreen mode

Now folder structure will look like this:

image


Step 3: Add files

  • Add /lib folder in the root of the project.

  • Add /lib/api.js file.

  • Add /.env.local file in the root of the project.



Step 4: Saving local environment

We have created /.env.local file for managing our application environment constants. These constants generally consist of URLs, API endpoints, and API keys. In our case, we will keep our URL in our local environment file. In /.env.local, type your unique domain name of your WordPress instance with GraphQL endpoint:

WP_API_URL=http://www.your-custom-website.com/graphql
Enter fullscreen mode Exit fullscreen mode

Important Note
Make sure that you restart the development server after making changes in the /.env.local file.


Step 5: Fetching data

Now that we have got our WP_API_URL variable covered, we can start writing our functions for fetching data. Navigate to /lib/api.js and add this line:

const API_URL = process.env.WP_API_URL;

async function fetchAPI(query, { variables } = {}) {
  const headers = { 'Content-Type': 'application/json' };

  const res = await fetch(API_URL, {
    method: 'POST',
    headers,
    body: JSON.stringify({ query, variables }),
  });

  const json = await res.json();
  if (json.errors) {
    console.log(json.errors);
    console.log('error details', query, variables);
    throw new Error('Failed to fetch API');
  }
  return json.data;
}
Enter fullscreen mode Exit fullscreen mode

We've created this function to fetch the blog contents with GraphQL query parameter. You can consider this function as a reusable structure that can be utilized throughout different fetch requests.

Then, it's time to ask GraphQL to fetch our content by adding this function:

export async function getRecentPosts() {
  const data = await fetchAPI(`query getRecentPosts {
  posts(where: {orderby: {field: DATE, order: DESC}}, first: 6) {
    edges {
      node {
        id
        title
        slug
        featuredImage {
          node {
            altText
            mediaItemUrl
          }
        }
      }
    }
  }
}
`);

  return data?.posts;
}
Enter fullscreen mode Exit fullscreen mode



Step 6: Change index file

Now we will perform some changes in pages/index.js, which is the entry page. Remove everything between <main></main> elements before pasting the code below, also remove <footer></footer>.

        <section>
          <div className={styles.articlesContainer}>
            <h1 className={styles.articleTitle}>latest articles</h1>
            <div className={styles.articleGrid}>
              {edges.map(({ node }) => (
                <div className={styles.article} key={node.id}>
                  <img
                    src={node.featuredImage.node?.mediaItemUrl}
                    alt={node.featuredImage.node?.altText}
                    className={styles.articleThumb}
                  />
                  <h2 className={styles.articleContent}>{node.title}</h2>
                </div>
              ))}
            </div>
          </div>
        </section>

Enter fullscreen mode Exit fullscreen mode

We import fetch function on the top of the page, which we'll utilize to make a request for fetching blog contents.

import { getRecentPosts } from '../lib/api';
Enter fullscreen mode Exit fullscreen mode

Add this parameter to the Home function component.

{ recentPosts: { edges } }

This is how our Home component looks like now. Parameters will be covered in the next section.

import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { getRecentPosts } from '../lib/api';

export default function Home({ recentPosts: { edges } }) {
  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <section>
          <div className={styles.articlesContainer}>
            <h1 className={styles.articleTitle}>latest articles</h1>
            <div className={styles.articleGrid}>
              {edges.map(({ node }) => (
                <div className={styles.article} key={node.id}>
                  <img
                    src={node.featuredImage.node?.mediaItemUrl}
                    alt={node.featuredImage.node?.altText}
                    className={styles.articleThumb}
                  />
                  <h2 className={styles.articleContent}>{node.title}</h2>
                </div>
              ))}
            </div>
          </div>
        </section>
      </main>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now it is time to add the Next.js function at the bottom of /pages/index.js:

export async function getStaticProps() {
  const recentPosts = await getRecentPosts();
  return {
    props: {
      recentPosts,
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

What role getStaticProps() function play here?

Next.js has one very important concept: pre-rendering.
That means, instead of having client-side run JavaScript and generate HTML such as in React, Next.JS generates HTML in advance and sends each generated page to client-side. Every page has been associated with a JavaScript code executed to make page interactive after being loaded by the browser. This is a process called hydration. You can make a simple experiment to see how this works: turn off JavaScript on your browser, run a Next.js project, and see the magic. HTML will still be rendered on your browser, however, it'll not be interactive until JavaScript is turned on.

Static generation, on the other hand, is one of the methods of pre-rendering that generates HTML at build time. Using getStaticProps() function, we have generated a static page with data dependencies, in this case, our blog contents. We simply asked Next.js to fetch the data with async getStaticProps() function. Then, Next.js produced a statically generated HTML passing the props of fetched data to the parameters of Home component, and sent it to the client-side. That's it! Next.js will make sure that data is fetched before the HTML is rendered, and all these happened in build-time.

image
Image is taken from official Next.js website

One important note

Please make sure you use getStaticProps() function in a file located under /pages folder as it works only in a page.

Finally, our /pages/index.js page will look like this:

import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { getRecentPosts } from '../lib/api';

export default function Home({ recentPosts: { edges } }) {
  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <section>
          <div className={styles.articlesContainer}>
            <h1 className={styles.articleTitle}>latest articles</h1>
            <div className={styles.articleGrid}>
              {edges.map(({ node }) => (
                <div className={styles.article} key={node.id}>
                  <img
                    src={node.featuredImage.node?.mediaItemUrl}
                    alt={node.featuredImage.node?.altText}
                    className={styles.articleThumb}
                  />
                  <h2 className={styles.articleContent}>{node.title}</h2>
                </div>
              ))}
            </div>
          </div>
        </section>
      </main>
    </div>
  );
}

export async function getStaticProps() {
  const recentPosts = await getRecentPosts();
  return {
    props: {
      recentPosts,
    },
  };
}
Enter fullscreen mode Exit fullscreen mode



Step 7: Change style files

Open /styles/globals.css, delete all content, and paste this code:

*,
*::after,
*::before {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
}

html {
  font-size: 62.5%; /* 1 rem = 10px; 10px/16px = 62.5% */
}

body {
  font-size: 2.5rem;
  box-sizing: border-box;
  line-height: 1.5;
}
Enter fullscreen mode Exit fullscreen mode

Then, navigate to /styles/Home.module.css, delete all content, and paste this code:

.articleTitle {
  font-size: 3rem;
  text-transform: uppercase;
  text-align: center;
  letter-spacing: 0.12em;
}

.articleContent {
  font-size: 2.5rem;
  padding: 0 6.5rem;
  text-align: center;
  font-weight: 400;
}

.articlesContainer {
    padding: 6.5rem 4rem;
}

.articleGrid {
    margin-top: 6.5rem;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    row-gap: 6.5rem;
    column-gap: 3rem;
}

.articleThumb {
    width: 100%;
    height: 28rem;
    margin-bottom: 3rem;
}
Enter fullscreen mode Exit fullscreen mode

And done! You've generated a static page with external data dependencies.

I hope you find this article helpful. Thank you for reading.

💖 💪 🙅 🚩
dantonelli
Derya A. Antonelli

Posted on August 16, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related