Derya A. Antonelli
Posted on August 16, 2021
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:
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
Then, run this command:
cd nextjs-blog
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 .
In order to run our project on the development server, run this command:
npm run dev
Now your page will run on port 3000. This is how it looks:
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
Now folder structure will look like this:
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
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;
}
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;
}
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>
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';
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>
);
}
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,
},
};
}
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 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,
},
};
}
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;
}
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;
}
And done! You've generated a static page with external data dependencies.
I hope you find this article helpful. Thank you for reading.
Posted on August 16, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.