logo
Build A Blog With NextJs: A Practical Guide To JAMSTACK
by John Oba - Afrodev24 March, 2023 • 5 min read
Image Banner

If you're tired of hearing the term "Jamstack" but still curious about what it means, don't worry! We'll explain it in a simple way.

We'll be creating a basic blog that is affordable to run, fast, and secure. We'll use NextJS to generate the website statically, and we'll get our blog posts from the public API of WordPress.

A basic understanding of JavaScript and React will be helpful in following along

Server Side Rendering and Static Site Generation are two modern approaches to building web applications that are fast, efficient, and optimized for search engines. By moving some of the workloads from the client's browser to the server, these techniques help to reduce the overall page load time and improve the user experience.

Requirement

Similar to npm, npx is a package runner that executes packages hosted on the npm registry. In this case, we will use npx to execute create-next-app

npx create-next-app@latest <project-name>

Feel free to name your project as you like. I’ll name this blog-demo

npx create-next-app@latest blog-demo --typescript

On the CLI, I selected all the default configurations. (Hit "Enter" to keep going").

When the installation is complete run

cd blog-demo && code .

We can start the development server and preview the default app in the browser.

npm run dev

Our default NextJs app is running on the browser. everything working as expected and our focus directory is the pages directory where all our routes point to the index.tsx. A second page is required to show the post detail when clicked. We can name this page [slug].tsx . This is how dynamic routes are created in NextJs. Let's move this new file into a posts folder. The new pages structures look more like this

📁 pages
   |__ index.tsx
   |
   |__ 📂 posts
        |___[slug].tsx

After removing all that we don't need from pages/index.tsx it took this shape

import Head from 'next/head'
import styles from '@/styles/Home.module.css'


export default function Home() {
 return (
   <>
     <Head>
       <title>Blog Demo</title>
       <meta name="description" content="A practical guide to Jamstack" />
       <meta name="viewport" content="width=device-width, initial-scale=1" />
       <link rel="icon" href="/favicon.ico" />
     </Head>
     <main className={styles.main}>
       
     </main>
   </>
 )
}

Choosing a cms

Jamstack applications rely on content APIs for their dynamic content. There are many CMS options to choose from, including Strapi, Contentful, and Sanity. Choose a CMS that best fits your needs and integrate it with your NextJS project.

find the full list here

Index Page

In the course of this article, we'll be using a Wordpress.com API as a CMS for our blog.
Our blog page needs to fetch the list of blog posts from the CMS and display them on the index page.

import Head from "next/head";
import Link from "next/link";
import styles from "@/styles/Home.module.css";

export default function Home({ posts } : any) {
  return (
    <Head>
        <title>Leccel Blog</title>
        <meta name="description" content="A practical guide to Jamstack" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <h1>In the news</h1>
        <ul>
          {posts.map((post: any) => (
            <li key={post.ID}><Link href={`posts/${post.slug}`}>{post.title}</Link></li>
          ))}
        </ul>
      </main>
  )
}

To retrieve data before rendering, Next.js provides a way to export an async function called getStaticProps from the same file. This function is executed during the build process and allows you to send the retrieved data as props to the page for pre-rendering.

// This function gets called at build time
export async function getStaticProps() {
  // Call the wordpress.com API endpoint to get posts
  const res = await fetch(
    "https://public-api.wordpress.com/rest/v1/sites/200671771/posts"
  );
  const allPosts = await res.json()

  // By returning { props: { posts } }, the Home component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts : allPost.posts,
    },
  }
}

export default function Home({ post }: { post: any }) {
  
  ...

The result from the REST API has this structure. so that is why we had to call allPosts.posts

{
    posts: [
      {
        ID, title, content, excerpt, author,slug, featured_image ...
      }
    ]
}

Here's what it looks like on the browser

Details Page

For the details page, post/[slug].tsx, we’ll get the slug from the route’s param and query our CMS API for the single post's details. We'll be bringing getStaticProps again. Before jumping in, we'll create some helper functions helpers/index.ts to avoid redundant repetition, When calling our CMS API. here's what it looks like

export async function fetchAllPosts(field: string = '') {
  const res = await fetch(
    "https://public-api.wordpress.com/rest/v1/sites/200671771/posts?fields="+field
  );
  return await res.json();
}
  
export async function fetchSinglePost(slug: string) {
  const res = await fetch(
    `https://public-api.wordpress.com/rest/v1/sites/200671771/posts/slug:${slug}`
  );
  return await res.json();
}

And then back to our posts/[slug].tsx we'll be using our helper function to fecth the post by it's slug

import { fetchSinglePost } from "@/helpers";

export const getStaticProps = async (context) => {
  const post = await fetchSinglePost(context.params!.slug as string);

  return {
    props: { post },
  };
};

In the template part, we can show some of the post details

import { fetchSinglePost, createMarkup } from "@/helpers";
import Head from "next/head";
export default function BlogPost({post}: any) {
  return (
    <>
      <Head>
        <title>{post.title}</title>
        <meta
          name="description"
          content={post.title}
        />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta
          property="og:image"
          content={post.featured_image}
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="mian">
          <div className="blog-html">
            <h1 className="text-center">
            {post.title}
            </h1>
            <div
              className="content"
              dangerouslySetInnerHTML={createMarkup(post.content)}
            ></div>
          </div>
      </div>
    </>
  )
}

The post.content is in HTML format, so we need to insert it into our page. ReactJs provides a method for this using dangerouslySetInnerHTML.

function createMarkup(content: string) {
  return {__html: content};
}
<div 
  className="content" 
  dangerouslySetInnerHTML={createMarkup(post.content)}></div>

To pre-render all the available posts, it is important we provide a list of all the post slugs. To do this, we will export an async function getStaticPath that returns an object in the posts/[slug].tsx file

import { fetchAllPosts, fetchSinglePost, createMarkup } from "@/helpers";
...
export async function getStaticPaths() {
  const res = (await fetchAllPosts('slug')).posts.map((post: any) => ({
    params: post,
  }));

  return {
    paths: res,
    fallback: false,
  };
}
...

and a reminder the helper function fetchAllPosts is like this

export async function fetchAllPosts(field: string = '') {
  const res = await fetch(
    "https://public-api.wordpress.com/rest/v1/sites/200671771/posts?fields="+field
  );
  return await res.json();
}

Build and Deploy

next build generates an optimized version of our blog for production and the output is generated in the .next directory.

Vercel is the fastest way to deploy your Next.js application with zero configuration.

Firebase is also a great service to deploy our blog especially when we use SSG without any serverless function. Here's a guide to deploying to firebase

Surge is one of the favorites for fast deployment of static pages. run

npx next build && npx next export && cd out

If you already have surge CLI, then run the legendary command

surge

Here's a list of platform that supports NextJs painlessly

Here's a link to the source code https://github.com/johnexzy/blog-with-nextjs

Conclusion

Static Site Generation is a suitable approach when you have a fixed amount of content, such as in a blog. You can run a Statically Generated Site for free using the platforms mentioned above, with a cost only incurred if you use a server or serverless functions in your application.

Thank you for reading this far.

Cheers 🎉


More Stories from Afrodev

2023 AfroDev