How to Build a Static Blog Site with Next.js: A React Tutorial

In this React tutorial, we’ll build a simple, static website for a blog using the React framework Next.js, learn how to make use of static resources in Markdown files, and then use that to present blog posts for our project site.

 

Building a Static Blog Site

For front-end developers, the wonderful server-side rendering (SSR) framework Next.js can be a powerful addition to the toolbox. Next.js provides an easy way to build a website that will have static content, such as a portfolio or blog site. Right out of the box, Next.js has great website building tools, including a workflow routing system and a webpack to automatically build bundles behind the scenes so we can focus on content from the very start. 

For this React tutorial, we’ll build a static blog site. Because the pages on our site won’t change often, there’s no need for a database to store posts; instead, we’ll store the posts statically in source code using Markdown and Next.js

We’ll use these packages in our project:

React: Declarative JS library for UI

Next.js: Isomorphic React on the server and the browser

Material-UI: Gorgeous, out-of-the-box UI design 

MDX: “JSX in Markdown for ambitious projects”

You’ll find the source code repository for this project available here.

 

React Tutorial: Getting Started

1. First, we’ll create a new directory for our project, initializing a package.json file and then install the React and Next.js packages:

mkdir blog-next
cd blog-next
npm init -y
npm install --save react react-dom next

2. Next.js has its own built-in router system ready to use, so we just need to create a pages directory:

mkdir pages

We’ll add new pages as React JSX components as we build more of the website.

3. Continuing with the component library picked option (Material-UI), install the library:

yarn add @material-ui/core

Now we’re ready to build the layout and new components with Material Design.

 

Creating the Website Layout

The layout for our website includes a navbar with:

• The mail link to home (title) 

• An “About” link

• A section title for Feature Blog posts

• A two-column grid displaying cards with the title, date of publication and a summary of the content for each post

 

 

1. Define /pages/index.jsx. That will also be our landing page at my-domain.com/, where my-domain is the domain for production, or simply localhost:3000 if we are in development. 

2. Define the index page as follows:

const Index = () => {
  const classes = useStyles();
  return (
    <React.Fragment>
      <CssBaseline />
      <Header />
      <Container maxWidth="lg" className={classes.container}>
        <Box my={4} display="flex" justifyContent="center">
          <Typography variant="h4" component="h1" gutterBottom>
            Featured Blog posts
          </Typography>
        </Box>
        <Grid container spacing={4}>
          {blogPosts.map(post => (
            <PostCard key={post.title} post={post} />
          ))}
        </Grid>
      </Container>
      <Footer title="My Blog" description="Hi there, this is my blog!" />
    </React.Fragment>
  );
};

Here we’re using several components inside the /components directory, such as Header for the top navigation panel, and Footer for the bottom panel. These components are relatively small and straightforward; you can take a look at these components at my github repo

The third component, PostCard, and the imported array blogPosts, deserve a closer look, because they are the core elements of the micro-blog engine.

Let’s take a look at PostCard.jsx first:

const PostCard = ({ post }) => {
  const classes = useStyles();
  return (
    <Grid item xs={12} md={6}>
      <Link href={post.path}>
        <Card className={classes.card}>
          <div className={classes.cardDetails}>
            <CardContent>
              <Typography component="h2" variant="h5">
                {post.title}
              </Typography>
              <Typography variant="subtitle1" color="textSecondary">
                {post.publishedAt}
              </Typography>
              <Typography variant="subtitle1" paragraph>
                {post.summary}
              </Typography>
              <Typography variant="subtitle1" color="primary">
                Continue reading...
              </Typography>
            </CardContent>
          </div>
        </Card>
      </Link>
    </Grid>
  );
}

This is a component that expects a prop post that contains a title, publish date, path, or href and summary, as we can see in the layout example above.

 

Setting Up Markdown Configuration in Next.js

We must somehow provide these properties from the blog posts we write in Markdown—that is, inside the pages directory—to the PostCard component so that we can display a preview on the land page. How? The answer is that because our site is static, we can just read the properties from the file system!

But first, we must install and configure the package we’ll use to work with Markdown: MDX, which comes ready to work with Next.js.

1. Add next and a plug-in for Next.js:

yarn add @next/mdx @mdx-js/loader

2. Then create file /next.config.js and include these contents:

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/
});

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'md', 'mdx']
});

This tells Next to automatically import files with the ‘md’ extension and process them as if they were JSX files. We’ll see that our post is actually both a JSX React component and a Markdown file at the same time.

 

Importing Markdown Posts Inside React

Now we define the JavaScript utility to load the posts in Markdown. 

1. Create the folder /data and inside the file get-blog-posts.js:

const fs = require('fs');
const path = require('path');

const META = /export\s+const\s+meta\s+=\s+(\{(\n|.)*?\n\})/;
const DIR = path.join(process.cwd(), './pages/blog/');
const files = fs
  .readdirSync(DIR)
  .filter((file) => file.endsWith('.md'));

module.exports = files
  .map(file => {
    const name = path.join(DIR, file);
    const contents = fs.readFileSync(name, 'utf8');
    const match = META.exec(contents);

    if (!match || typeof match[1] !== 'string')
      throw new Error(`${name} needs to export const meta = {}`);

    const meta = eval('(' + match[1] + ')');

    return {
        ...meta,
        path: '/blog/' + file.replace(/\.mdx?$/, '')
      };
  })
  .filter((meta) => meta.published)
  .sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));

This function looks inside the folder /pages/blog/ for any file with extension .md. Inside each file, it expects an object called meta, where the properties of the post reside (title, path, summary, and publish date), which is what the function exports in the end.

Let’s see what a blog post looks like:

import BlogPost from '../../components/BlogPost';

export const meta = {
  published: true,
  publishedAt: '2019-01-15',
  title: 'This is my very first blog post',
  summary:
    'I am learning how to build a blog using Markdown in a static web page build on top of Next.js'
};

export default ({ children }) => <BlogPost meta={meta}>{children}</BlogPost>;

<<Actual contents go here>>

The example above is the first part of a post at /pages/blog/my-first-post.md (note that I added a sub dir blog inside pages, to keep the posts apart from the other pages). It starts by importing a React component BlogPost, which is the frame for the contents of the blog, as we’ll see shortly. Then we have the meta-object with the meta-data of the blog post, and a default export of this JSX file (a React component). Finally, we have the post content! That’s how we write a post in our blog. It’s so simple and neat!

 

Handling Static Resources

Returning to our preview of the posts on the index page, we should be able to import the meta-data and display it in the cards. 

However, we must find a way, since we’re working with static data, to have the import happen at compile-time. A regular import won’t work because, when it’s executed in the browser, it will try to bring data up that is in the server.  The solution is to pre-evaluate the contents given by get-blog-posts.js in the server, before they’re served to the client. 

1. To do this, we’ll use a few plugins available in babel: babel-plugin-macros and babel-plugin-preval:

yarn add --dev babel-plugin-macros babel-plugin-preval

2. Add a file .babelrc and the contents:

{
  "presets": ["next/babel"],
  "plugins": ["macros"]
}

3. Add a file at /data/blog-posts.js (this is where the magic happens!):

import preval from "babel-plugin-preval/macro";

module.exports = preval`module.exports = require('./get-blog-posts.js');`;

The last line pre-evaluates the contents retrieved by get-blog-posts.js, so we are really exporting the array of meta-objects instead of the function to fetch them.

 

Displaying a Blog Post as a Stand-alone Page

Finally, let’s see how a blog post displays:

We see:

• The navigation and footer panels on the index page

• A link “Back to blog” in the upper left

• A link to return to the landing page

• Links in the lower section to go to the previous and/or next blog

To get the previous/next links, we need to use blog-posts.js here:

const BlogPost = ({ meta, children }) => {
  const current = blogposts.map(({ title }) => title).indexOf(meta.title);
  const next = blogposts[current - 1];
  const prev = blogposts[current + 1];

  return (
    <Fragment>
      <Header />
      <Container maxWidth="md">
        <Box my={4}>
          <Link href="/">{'< '} BACK TO BLOG</Link>
        </Box>
        <Typography variant="h4" component="h1" gutterBottom>
          {meta.title}
        </Typography>
        {children}
        <hr />
        <Box my={4} display="flex" justifyContent="center">
          <Box mx={4}>
            {prev && (
              <NextPost
                href={prev.path}
                position="< Previous post"
                title={prev.title}
              />
            )}
          </Box>
          <Box mx={4}>
            {next && (
              <NextPost
                href={next.path}
                position="Next post >"
                title={next.title}
              />
            )}
          </Box>
        </Box>
      </Container>
      <Footer title="My Blog" description="Hi there, this is my blog!" />
    </Fragment>
  );
};

 

Now You Have a New Tool for Simple, Static Websites in Your Toolbox!

In this React tutorial, we created a simple blog site using Markdown and Next.js. This provides many benefits, including:

• SEO optimization and reduced bootstrap time, by using SSR

• Avoidance of the need to host a database, since the posts are stored in source control

• Out-of-the box gorgeous text formatting with Markdown

• Continuous deployment and free hosting for your website using services such as zeit.co

React is well established and will continue to shine as a framework for SSR and static websites, especially with the increasing popularity of the JAM stack and tools like GatsbyJS and Next.js.

Now you have a valuable tool in your toolbox to build simple, static websites using SSR in front-end development. I hope you enjoy using it!

 

Interested in learning more about React? Check out our tutorial on building your own Instagram using React Native!

 

Eduardo Pineda

Eduardo Pineda has been a software engineer since 2010 and is currently working as a Senior Web Developer at Gorilla Logic. Eduardo initially graduated as an electronics engineer, but gradually transitioned to software engineering—working first with embedded devices, instrument drivers, desktop apps, and more recently working in full-stack development with technologies like Node.js, React, Java, and .Net. Eduardo always tries to follow best practices and stay up-to-date with the latest technologies so he can offer high performance and robust software solutions. In his free time, he loves to run, go to the movies, brew kombucha, work on pet projects, and write in his blog (https://epineda.dev/.)

Related Articles

Ready to be Unstoppable?

Partner with Gorilla Logic, and you can be.