How To Create a Simple Blog with Next.js and Markdown
Lets create a simple blog listing and navigation using Next.js and Markdown
Posted by Nuno Alves on Oct 22 2020
Article in english
10 min · No views · No likes
photo by Jess Bailey on Unsplash
In this short tutorial I'll walk you through how you can create an easy and quick (very simple) blog website for publishing your articles using Next.js and Markdown.
What is Next.js
Next.js is a production ready open source React framework that allows quick generation of both static and dynamic websites using JAMstack. It provides functionalities like Pre-rendering, both SSG (Static Site Generation) and SSR (Server Side Rendering), hybrid SSG & SSR, Client-side routing, API routes, TypeScript support to name a few.
What is Markdown
Markdown is a plain text formatting syntax, used to add formatting capabilities to plain text documents.
Creating Next.js project
To create a Next.js application, open the terminal and just run the following command:
npx create-next-app nextjs-markdown-blog
After, you should have a folder called nextjs-markdown-blog
.
Execute the following commands
cd nextjs-markdown-blogyarn dev# ornpm run dev
And development server starts on port 3000 by default. Open http://localhost:3000 from your browser and you should see the following:
If you see the previous window, the starter template page, it means everything went smoothly and we are ready to proceed!
Next.js Application folder structure
You should have the above folder structure. I'll not enter into details about Next.js folder structure and routing since it is not on the scope of this tutorial, but you can check the official documentation here.
To keep the simplicity of this article and focus on what is presented, you can delete folders styles
and api
under pages
.
Also, delete the first line of _app.js
:
1import '../styles/globals.css';2
That's should do it for now.
Starting Blog implementation
Let's start by creating a site.config.json
file in the root folder, so we have a unique place for website global configuration without hardcoding values on each file. On it, place the following code:
1{2"title": "My Blog Website",3"description": "Simple Next.js Markdown Blog Website"4}5
Now, go to index.js and type the following code, replacing the existing one:
1const Index = () => {2return <h1>My Blog Website</h1>;3};45export default Index;6
You should now see on http://localhost:3000:
Let's now add a little bit of page customization using layouts, but first, let me briefly explain how Data Fetching works on Next.js:
As stated in the beginning of the article, we can have two types of pre-rendering, Static Generation or Server Side Rendering. Basically we have three methods for fetching data for pre-rendering:
- Static Generation using
- getStaticProps: for fetching data at build time
- getStaticPaths: for defining paths / routes to pre-render based on data
- Server Side Rendering using
- getServerSideProps: for fetching data on each request
Let's then, using Static Generation strategy for our blog, inject previously defined site configurations into the page. Add the following code to index.js
1const Index = ({ title, description, ...props }) => {2return (3<>4<h1>{title}</h1>5<p>{description}</p>6</>7);8};910export const getStaticProps = async () => {11const siteConfig = await import('../../site.config.json');1213return {14props: {15title: siteConfig.title,16description: siteConfig.description17}18};19};2021export default Index;22
This should be what we see when we navigate to our index page now:
Adding Blog template components
We will create a new folder in the root of the project called components
. Inside we will create 3 files Footer.js
, Header.js
and Layout.js
.
1const Footer = () => {2return <footer>Copyright 2020</footer>;3};45export default Footer;6
1import Link from 'next/link';23const Header = () => {4return (5<header className="content">6<nav className="navigation">7<Link href="/" passHref>8<a>My Blog</a>9</Link>10<Link href="/about" passHref>11<a>About</a>12</Link>13</nav>14</header>15);16};1718export default Header;19
1import Head from 'next/head';2import Footer from './Footer';3import Header from './Header';45const Layout = ({ children, title, ...props }) => {6return (7<>8<Head>9<title>{title}</title>10<meta title={title} />11</Head>12<section className="content-wrapper">13<Header />14<div className="content">{children}</div>15</section>1617<Footer />18</>19);20};2122export default Layout;23
Now, adapt index.js
and create a new page about.js
:
1import Layout from '../components/Layout';23const Index = ({ title, description, ...props }) => {4return (5<Layout title={title}>6<h1>{title}</h1>7<p>{description}</p>8<main>9<p>Blog Posts here!</p>10</main>11</Layout>12);13};1415export const getStaticProps = async () => {16const siteConfig = await import('../site.config.json');1718return {19props: {20title: siteConfig.title,21description: siteConfig.description22}23};24};2526export default Index;27
1import Layout from '../components/Layout';23const About = ({ title, description, ...props }) => {4return (5<Layout title={`${title} - About`}>6<h1>{title}</h1>7<p>{description}</p>8<p>This is my About Page!</p>9</Layout>10);11};1213export const getStaticProps = async () => {14const siteConfig = await import('../site.config.json');1516return {17props: {18title: siteConfig.title,19description: siteConfig.description20}21};22};2324export default About;25
This is how our site looks up to now:
Adding Posts Dynamic Routing and Markdown processing
For this step we will need to install the following additional packages:
- gray-matter: parse front-matter from string or file
- react-markdown: for rendering markdown in react
- raw-loader: webpack loader to allow importing files as a String.
Run the following commands:
yarn add gray-matter react-markdown raw-loaderornpm install gray-matter react-markdown raw-loader
Create a next.config.js
file at root, paste the following and restart the server afterward:
1module.exports = {2webpack: function (config) {3config.module.rules.push({4test: /\.md$/,5use: "raw-loader",6});7return config;8},9};10
Now, let's create a dynamic route! For this, we will create a post
folder under pages
and a file [slug].js
under newly created folder post
. Also create another folder named posts
at root, where markdown blog posts will be added. Next.js let us add brackets to a page to create these dynamic routes (slugs) and make possible to access what page was called using this slug inside the file.
Add the following to [slug].js
:
1import Link from "next/link";2import Layout from "../../components/Layout";3import matter from "gray-matter";4import ReactMarkdown from "react-markdown";56const Post = ({ title, frontmatter, markdownContent }) => {7if (!frontmatter) {8return <></>;9}1011return (12<Layout title={`${title} - ${frontmatter.title}`}>13<Link href='/' passHref>14<a>Back</a>15</Link>16<article>17<h1>{frontmatter.title}</h1>18<p>{frontmatter.author}</p>19<p>20<ReactMarkdown source={markdownContent} />21</p>22</article>23</Layout>24);25};2627export const getStaticProps = async ({ ...ctx }) => {28const { slug } = ctx.params;2930const siteConfig = await import("../../site.config.json");31const content = await import(`../../posts/${slug}.md`);32const data = matter(content.default);3334return {35props: {36title: siteConfig.title,37frontmatter: data.data,38markdownContent: data.content,39},40};41};4243export const getStaticPaths = async () => {44const slugs = ((ctx) => {45const keys = ctx.keys();46const data = keys.map((key, index) => {47return key.replace(/^.*[\\\/]/, "").slice(0, -3);48});49return data;50})(require.context("../../posts", true, /\.md$/));5152const paths = slugs.map((slug) => `/post/${slug}`);5354return {55paths,56fallback: false,57};58};5960export default Post;6162
In getStaticProps
we retrieve information from site.config.js
as we've done previously, but now, we also return metadata - using gray-matter to parse it - and markdown data from our blog post markdown file.
In getStaticPaths
we define the list of paths that will be rendered at build time. We iterate over all files on posts
folder, parse files names to define respective slugs and return a path list based on those ones. We also set fallback to false
so 404 is returned in case some page is not under this list.
Let's finally create a markdown blog post!
Create a file under posts
folder and name it whatever you like, for example myfirstpost.md
. Use markdown and add something like the following:
1---2title: My first post3author: Nuno Alves4---56# First Heading78Pariatur nostrud fugiat do deserunt occaecat excepteur. Consequat consectetur consequat exercitation pariatur ex ex proident ullamco ex velit officia amet laborum exercitation. Nostrud commodo eu duis sint.910Elit irure adipisicing officia enim ea eiusmod. Ad officia reprehenderit aute fugiat eiusmod ipsum ad Lorem pariatur commodo mollit esse proident. Velit magna fugiat ad veniam pariatur incididunt reprehenderit voluptate veniam aliquip.1112## Some List1314- Item 115- Item 216- Item 317
If you now navigate to our post page you should see
Let's now complete ou simple blog by creating a new component PostList.js
under components
folder for listing our blog posts...
1import Link from "next/link";23const PostList = ({ posts }) => {4if (posts === "undefined") {5return null;6}78return (9<div>10{!posts && <div>No posts found!</div>}11<ul>12{posts &&13posts.map((post) => (14<li key={post.slug}>15<Link href={{ pathname: `/post/${post.slug}` }} passHref>16<a>{post.frontmatter.title}</a>17</Link>18</li>19))}20</ul>21</div>22);23};2425export default PostList;26
...and updating index.js
to display it.
1import matter from "gray-matter";2import Layout from "../components/Layout";3import PostList from "../components/PostList";45const Index = ({ title, description, posts, ...props }) => {6return (7<Layout title={title}>8<h1>{title}</h1>9<p>{description}</p>10<main>11<PostList posts={posts} />12</main>13</Layout>14);15};1617export const getStaticProps = async () => {18const siteConfig = await import("../site.config.json");1920const posts = ((context) => {21const keys = context.keys();22const values = keys.map(context);2324const data = keys.map((key, index) => {25let slug = key.replace(/^.*[\\\/]/, "").slice(0, -3);26const value = values[index];27const document = matter(value.default);28return {29slug,30frontmatter: document.data,31markdownContent: document.content,32};33});34return data;35})(require.context("../posts", true, /\.md$/));3637return {38props: {39title: siteConfig.title,40description: siteConfig.description,41posts,42},43};44};4546export default Index;47
Create just another markdown post to have more on the list. You should now have on your browser when navigate to index page
And that's finally it! I will leave styling for another post.
If you prefer you can also check or download source code from my github.
If you like this post, click Like
button below. You can also share your comments or suggestions with me.