Use Markdoc and Next.js to Build a Git-powered Markdown Blog
Tags: nextjs, blog, tutorial
Published: 2023-02-14 16:30:52

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/ezgif_com-webp-to-png_455d54acb05ef04816809bdcfb05321c.jpg" alt="Get Started with Markdoc and Next.js."/></figure>

Most modern developer blogs and documentation websites have one thing in common— they run on JAMstack (static websites) and their content is file-based and powered by Git. This allows multiple developers to collaboratively edit content with perks like versioning and version control. In this tutorial, we’re going to see how we can build a simple yet powerful and interactive blog with [Next.js](https://nextjs.org/) and [Markdoc](https://markdoc.dev/).

## A Brief Overview of the Article

This tutorial will cover how we can leverage modern tools and frameworks to build a fast & performant blog without relying on a backend to manage our content.

We’ll look into Next.js, its features, and why it’s important. We'll then take a look at tools like Markdoc that we can use to author and render markdown content into pages for blogs and documentation sites.

We’ll also explore the pros and cons of this approach to building static blogs and documentation websites.

Let’s get right into it!

## A Introduction to the Stack

Let’s take a quick look at the tech stack we’ll be working with in this article.

### JAMstack

[JAMStack](https://jamstack.org/) is an acronym for JavaScript API and Markup Stack. It's basically the way modern sites are built using tools like static site generators that can generate static content that is served over the internet. JavaScript is used for functionality and APIs are used to provide data.

For the past few years, since JAMstack became widespread, it has revolutionized the way many websites are built. [JAMstack sites](https://code.pieces.app/blog/best-jamstack-frameworks-for-developing-static-and-dynamic-sites) are fast and performant due to their static nature.

In order to provide content, APIs are used. These APIs can be called at build time during the static generation to provide content that will be sent to the client, but this is not the only way we can provide content.

We can use template files like Markdown to create content or even pages for our site using static site generators.

### Next.js

According to [Jamstack.org](https://jamstack.org/generators/next/), Next.js is a minimalistic framework for server-rendered React applications as well as statically exported React apps.

Since Next.js offers both Server Side Rendering (SSR) and Static Site Generation (SSG), it’s a great choice for building fast applications.

### Markdoc & File/Git-based content

According to [the official docs](https://markdoc.dev/docs/getting-started), Markdoc is a Markdown-based document format and a framework for publishing content. It was designed at Stripe to meet the needs of their user-facing[ product documentation](http://stripe.com/docs). Markdoc extends Markdown with a[ custom syntax](https://markdoc.dev/docs/syntax) for tags and annotations, providing a way to tailor content to individual users and introduce interactive elements.

With Markdoc, we can manage our content in markdown files and provide it to the frontend at build time without a database.

The Markdoc syntax is a superset of Markdown. This features markdown syntax with a few extensions to the syntax, such as tags and annotations.

Markdoc features several core concepts which include:

- **Nodes:** These are the elements that Markdoc inherits from Markdown.
- **Tags:** Tags are the main syntactic extension that Markdoc adds on top of Markdown. Similar to HTML, each tag is enclosed with `{%` and `%}` and includes the tag name, attributes, and content.
- **Annotations:** These can be added to nodes to customize how they are rendered.


You can always view the full list of core concepts in the[ syntax docs](https://markdoc.dev/docs/syntax).

## What We’re Building with Next.js and Markdoc

We will be building a simple markdown-powered Next.js blog using Markdoc.

You can find the final result here: [https://markdoc-app.vercel.app/](https://markdoc-app.vercel.app/)

### Prerequisites

To follow along, you should have:

- A basic understanding of JavaScript, React, and [ Markdown syntax](https://www.markdownguide.org/basic-syntax/)
- [Node.js (latest LTS version)](https://nodejs.org/en/download/)
- [Visual Studio Code](https://code.visualstudio.com/)


### Seting up Next.js with Markdoc

Let’s follow[ the steps to build a Next.js app](https://nextjs.org/docs#automatic-setup).

First, navigate to the folder of choice and run the command:

```
npx create-next-app@latest
# or
yarn create next-app
# or
pnpm create next-app
```

Once installed, navigate to the newly created directory to install the Markdoc package. We’ll be installing `@markdoc/next.js` and `@markdoc/markdoc`:

```
cd <name of app>
npm install @markdoc/next.js @markdoc/markdoc
```

Next, we update our `next.config.js`

```
const withMarkdoc = require('@markdoc/next.js');

module.exports = withMarkdoc(/* options */)({
 pageExtensions: ['md', 'mdoc', 'js', 'jsx', 'ts', 'tsx']
});
```

Also, we’ll set up TailwindCSS and Tailwind Typography to style our application. To do that, we’ll follow the steps in the[ Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).

Install `tailwindcss` and its peer dependencies via `npm`, and then run the `init` command to generate both `tailwind.config.js` and `postcss.config.js`.

```
npm install -D tailwindcss postcss autoprefixer @tailwindcss/typography
npx tailwindcss init -p
```

Add the paths to all of our template files in your `tailwind.config.js` file. We’ll also add the Tailwind typography plugin:

```
// ./tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
 content: [
  "./pages/**/*.{js,ts,jsx,tsx}",
  "./components/**/*.{js,ts,jsx,tsx}",
  "./layouts/**/*.{js,ts,jsx,tsx}",
],
theme: {
  extend: {},
 },
 plugins: [require("@tailwindcss/typography")],
};
```

Add the `@tailwind` directives for each of Tailwind’s layers to your `./styles/globals.css` file.

```
// ./styles/globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;
```

_To keep this tutorial short, the styling that will be applied to this application can be accessed on_[ GitHub](https://github.com/miracleonyenma/markdoc-app/blob/main/styles/globals.css)_. You can copy the contents and enter them in your_ `./styles.globals.css` _file._

## Creating our First Post

Create a new .md file within /pages/articles and name it getting-started.md:

```
---
title: Get started with Markdoc
description: How to get started with Markdoc
---
# Get started with Markdoc
```

Now, if we simply start the development server with the following command:

```
npm run dev
```

We should have something like the image below when we navigate to `/articles/getting-started/`.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image11_30057ec79a7ac3e33c4ac289835f8f2c.png" alt="The initial Markdoc project viewed in the browser. "/></figure>

Here, we can see that the content of our markdown file has been rendered, but it’s pretty bare.

We need to display the data (i.e., the `title` and `description`) included in the front matter of the `.md` file:

```
---
title: Get started with Markdoc
description: How to get started with Markdoc
---
```

The front matter data will be displayed on the article page and also added to the site’s metadata. In the following sections, we’ll cover how we can achieve that.

## Creating a SiteHeader Component

First, we have to create a global site header component which will be included in our layouts.

Create a new file— `./components/SiteHeader.jsx`:

```
// ./components/SiteHeader.jsx
const { default: Link } = require("next/link");
const SiteHeader = () => {
  return (
    <header className="site-header">
      <div className="wrapper">
        <Link href={"/"}>
          <figure title="Site header">
            <h1>My site</h1>
          </figure>
        </Link>
        <nav className="site-nav">
          <ul className="links">
            <li className="link">
          <Link href={"/articles"}>Articles</Link>
        </li>
       </ul>
     </nav>
   </div>
 </header>
 );
};

export default SiteHeader;
```

Next, we’ll create our layouts.

## Creating a SiteLayout in Markdoc

The `<SiteLayout />` layout component will be responsible for displaying all pages that are not rendered by Markdoc. We’ll also create another `<ArticleLayout />` component which will be responsible for displaying pages rendered by Markdoc, e.g., `.md` files.

Now, let’s create the layout. Create a new file `./layouts/SiteLayout.jsx`:

```
// ./layouts/SiteLayout.jsx
const { default: SiteHeader } = require("../components/SiteHeader");
const SiteLayout = ({ children }) => {
  return (
   <>
    <SiteHeader />
    <main>{children}</main>
   </>
  );
};

export default SiteLayout;
```

## Creating ArticleLayouts

Create a new file— `./layouts/ArticleLayout.jsx`:

```
// ./layouts/ArticleLayout.jsx

import Head from "next/head";
import SiteHeader from "../components/SiteHeader";

const ArticleLayout = ({ markdoc, children }) => {
  const { title, description } = markdoc?.frontmatter;
  return (
   <>
    <Head>
     <title>{title}</title>
     <meta name="description" content={description} />
    </Head>
    <SiteHeader />
    <article className="site-article">
     <div className="wrapper">
      <header className="article-header">
       <div className="wrapper">
        <h1 className="font-extrabold text-6xl">{title}</h1>
        <p className="text-2xl">{description}</p>
       </div>
      </header>
      <div className="article-content prose">{children}</div>
     </div>
    </article>
   </>
  );
};

export default ArticleLayout;
```

Here, we have `markdoc` as a prop in this component. With that, we get `title` and `description` by destructuring.

Using the Next.js `<Head>` component, we add the `title` and `description` to our page meta.

To display the data within the page, we add it to the `.article-header` element.

Finally, to display the actual markdown content, we pass `children` to the `article-content.prose` element.

Now that we have created these components, let’s see how we can add them to our application.

## Setting up Dynamic Layouts in Next.js

So far, we’ve created two different layouts for our application. We want to display the `<SiteLayout />` component on normal pages while we use the `<ArticleLayout />` component on article pages, that is, pages rendered with Markdoc.

Next.js allows us to define layouts on a _per-page basis_ by adding the `getLayout` property to our page.

Since we cannot easily add a `getLayout` property to a `.md` file to define which layout will be used for the page, we can define a default layout for such pages. Then `.js` pages can define their layouts using the `getLayout` property.

To get this working, we’ll modify our `./pages/_app.js` file:

```
// ./pages/_app.js
import ArticleLayout from "../layouts/ArticleLayout";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
 // configure default article layout
 const articleLayout = (page) => {
  // pass `markdoc` props to ArticleLayout
  return (
   page.props.markdoc && (
    <ArticleLayout markdoc={page.props.markdoc}> {page}</ArticleLayout>
   )
  );
};

// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || articleLayout;

  return getLayout(<Component {...pageProps} />);
}

export default MyApp;
```

In order to make `<ArticleLayout />` the default layout, we create a function `articleLayout()` which accepts page as a parameter. Within this function, we return the `<ArticleLayout />` component while passing the `page` and `markdoc` props.

Next, we initialize `getLayout` and assign the layout defined in `Component` (the current page) or `articleLayout` if the page does not define a layout.

Now that we’ve set up dynamic layouts in our application, let’s define the layout for our home page(`./pages/index.js`):

```
// ./pages/index.js
import Head from "next/head";
import styles from "../styles/Home.module.css";
import SiteLayout from "../layouts/SiteLayout";
export default function Home() {
  return (
   <div className={styles.container}>
    <Head>
     <title>My Site</title>
     <meta name="description" content="This is my Next.js site" />
     <link rel="icon" href="/favicon.ico" />
    </Head>
    <section>
     <header className="flex flex-col items-center justify-center min-h-screen">
      <h1 className="font-extrabold text-7xl">Welcome to my site</h1>
      <p className="text-2xl">I&apos;m glad you&apos;re here</p>
     </header>
    </section>
   </div>
  );
}
// define layout for home page
Home.getLayout = (page) => {
  return <SiteLayout> {page} </SiteLayout>;
};
```

Now, if we go to `http://localhost:3000`  in our browser, we should have something like this:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image9_cc492533c3ee7246105a654503cd7fd5.png" alt="A browser window with a site landing page that reads, &quot;Welcome to my site. I&#x27;m glad you&#x27;re here.&quot;"/></figure>

Also, if we go to `http://localhost:3000/articles/getting-started`, we should see our content with the heading contained in the front matter:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image1_84c335582a015b972b8585b2a2f89f2f.png" alt="A styled homepage for a website about using Markdoc."/></figure>

## Building out the Blog

In the following sections, we’ll gradually build out our blog while exploring some of Mardoc’s features.

We’ll cover how to add custom components using tags, customize nodes, create functions, and more.

### Markdoc syntax

The Markdoc syntax is built on Markdown with a few additions or extensions to the syntax including nodes, tags, functions, and annotations; we’ve talked a bit about this in previous sections and you can learn all about it[ in their docs](https://markdoc.dev/docs/syntax).

### Creating Custom Tags & Attributes

According to the Markdoc docs, tags are the main syntactic extension that Markdoc adds on top of Markdown. Each tag is enclosed with `{%` and `%}`, and includes the tag name,[ attributes](https://markdoc.dev/docs/syntax#attributes), and the content body.

Markdoc comes out-of-the-box with four built-in tags: `if`, `else`, `table`, and `partial`. However, we can also create custom tags of our own. To illustrate this, we’ll create a custom `infobox` tag.

First, we can create the component that will be rendered. Create a new file, `./components/Infobox.jsx`:

```
// ./components/Infobox.jsx

const Infobox = ({ type, title, children }) => {
  return (
   <div className={`info-box ${type}`}>
    <details>
     <summary>{title}</summary>
     <div>{children}</div>
    </details>
   </div>
  );
};

export default Infobox;
```

Here, we dynamically include the type value in the element class. This way, we can define styles for the `.info-box` element depending on the type.

### Creating a Custom Infobox

Next, we’ll create a custom `infobox` tag definition in our Markdoc schema by creating a new file, `./markdoc/tags/infobox.markdoc.js`:

```
// ./markdoc/tags/infobox.markdoc.js
import Infobox from "../../components/Infobox";

export const infobox = {
  render: Infobox,
  attributes: {
   type: {
    type: String,
    default: "info",
    matches: ["warning", "info", "error"],
   },
   title: {
    type: String,
   },
  },
};
```

Here, in our `infobox` declaration, we import and define the component that will be rendered with the `render` property.

We also define the attributes that our tag accepts using the `attributes` property and they include `type` and `title`.

For the `type` attribute, we defined the type of value it accepts, the default value (`'info'`) and other acceptable matches, including `'warning'` and `'error'`.

Next, we will create a `./markdoc/tags/index.js` file to export our Markdoc tags:

```
// ./markdoc/tags/index.js
/* Use this file to export your markdoc tags */
export * from './infobox.markdoc';
```

Great! With that, we can add our `infobox` to our Markdoc document. Back in `./pages/articles/getting-started.md`, we’ll add some more content with our new infobox tag:

```
---
title: Get started with Markdoc
description: How to get started with Markdoc
---
## Get started with Markdoc
Markdoc is a static site generator that uses Markdown files as input and outputs HTML files.
Markdoc features several core concepts which include:
- **Nodes**:
 These are the elements that Markdoc inherits from Markdown
- **Tags**:
 Tags are the main syntactic extension that Markdoc adds on top of Markdown.   Similar to HTML, each tag is enclosed with `{%` and `%}` and includes the tag name, attributes, and the content body.
- **Annotations**:
 These can be added to nodes to customize how they are rendered
### Installation
To install markdoc in Next.js, run the following command:
```bash
npm install @markdoc/next.js @markdoc/markdoc
```
### Background
Markdoc was built by [Stripe](https://stripe.com/) to power their developer documentation.

{%infobox title="Hey there!" type="info"%}
Here's some info for you!
{%/infobox%}

{%infobox title="Hey there!" type="warning"%}
Here's a warning for you!
{%/infobox%}

{%infobox title="Hey there!" type="error"%}
Here's an error for you!
{%/infobox%}
```

Here, in the `{% infobox %}` tag, we define the title and type attributes for each. When we run the app, we should see something like this:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image6_29d7c927532ebb2c26bb9ba823cb6b07.png" alt="A webpage built with Markdoc with three dropdown information sections. "/></figure>

Sweet! With custom Markdoc tags, we’re able to add components that can do just about anything to our `.md` files.

### Customizing Default Nodes in Markdoc

Next, we’ll be looking at how to customize Nodes. To illustrate this, we’ll be customizing the default blockquote node.

First, we create a new `Blockquote` component that will be used. Create a new file: `./components/Blockquote.jsx`

```
// ./components/Blockquote.jsx
const Blockquote = ({ children }) => {
  return <blockquote className="blockquote">{children}</blockquote>;
};
export default Blockquote;

Create a new file: ./markdoc/nodes.js

// ./markdoc/nodes.js
import Blockquote from "../components/Blockquote";
export const blockquote = {
  render: Blockquote,
  attributes: {
   author: {
    type: String,
  },
 }
}
```

Now, by styling the `.blockquote` class that was attached to the `<Blockquote />` component, we can add the following to our article:

```
> Is there such an element as a "blockquote"?
```

And have something like this:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image3_25f44a953623e3a2795d57da9b8d979f.png" alt="A webpage with three dropdown sections and a blockquote."/></figure>

Now we’ve seen how to customize a node with a custom component. In the next section, we’ll take a look at another handy Markdoc feature, Partials, and how we can use variables in Markdoc.

### Variables and Partials in Markdoc

Variables allow us to customize your Markdoc documents at runtime. Variables are accessed using the `$` symbol.

You can pass variables in a few ways:

- Through the `variables` field on the[ Config](https://markdoc.dev/docs/syntax#config) object. Also, the frontmatter of a Markdoc page can be accessed via the `$markdoc` variable in the document.
- If we add the following to our document:


```
The title of this page is: **{% $markdoc.frontmatter.title %}**
```

- We should see the title of the document:


<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image4_7991156ddeaee1214e67ad735a23352f.png" alt="A blockquote with a short text block discussing Markdoc after it."/></figure>

- Via the[ variables attribute](https://markdoc.dev/docs/variables#with-partials) on a[ partial tag](https://markdoc.dev/docs/partials).
- Manually from within your[ Node](https://markdoc.dev/docs/nodes) or[ Tag](https://markdoc.dev/docs/tags) `transform` functions.


Markdoc uses `partials` to reuse content across documents. A separate Markdoc file stores the content, and it's referenced from within the `partial` tag.

We can create a `partial` that displays a particular promotional content in any page we add it to.

To create a `partial`, create a new file, `./markdoc/partials/BikePromo.md`:

```
----
It seems you’re enjoying this article on **{% $title %}**. I'm sure you'll be interested in the following offer:
## Buy a bike and get two wheels free!
What do you get when you buy a bike?
_Two wheels!_
Well, for a limited time only, you can get two new bike wheels for free when you purchase one of our bikes!
Find out more [here](/), or maybe not, *I'm not your boss*.
----
```

Partials automatically load from the `/markdoc/partials/` directory. Now, if we add the following to our document:

```
{% partial file="bike-promo.md" /%}
```

It will load and render the variables and contents of `markdoc/partials/bike-promo.md`:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image5_ad23d7fc2114d2a4c9c2700516cd4d77.png" alt="A webpage with intricately formatted text. "/></figure>

Next, we’re going to see how we can use another Markdoc feature: Functions.

### Using Functions in Markdoc

Functions in Markdoc allow us to extend Markdoc with custom utilities, which lets us transform our content and[ variables](https://markdoc.dev/docs/syntax#variables) at runtime.

Markdoc comes out-of-the-box with six built-in functions: `equals`, `and`, `or`, `not`, `default`, and `debug`. You can learn more about these[ functions in the Markdoc docs](https://markdoc.dev/docs/functions).

Custom function registrations are almost identical to tags and nodes, except you create a `./markdoc/functions.js` file instead. Within this file, we’ll create an `includes` function that checks if a string contains a defined sub-string.

```
// ./markdoc/functions.js

// ./markdoc/functions.js
export const includes = {
 transform(parameters) {
  const [string, value] = Object.values(parameters);
  return string.includes(value);
 },
};
```

Now, we can add our custom function to our document:

```
{% if includes($markdoc.frontmatter.title, "Llamas") %}
 > This page is about Llamas
 {% else /%}
 > This page is not about Llamas
{% /if %}
```

With that, we should have something like this:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image10_13cd337fab4bf79c53957e984f7f4d68.png" alt="The webpage with the same text as before and an additional blockquote."/></figure>

So far, we’ve explored the basic features of Markdoc and seen how we can use it to build out an impressive article page. In the next section, we’ll quickly cover how to add images to our content.

## Working with Images in Markdoc

We can simply use the markdown syntax to add any image that is in our `/public` directory to our article. For example, if we add the following to `./pages/articles/getting-started.md`:

```
![image](/vercel.svg)
```

We should see that the image shows up in the rendered page:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image7_4d544f1b67dbc586924289aabbe6df41.png" alt="The webpage with a rendered image below the text. "/></figure>

We can also do something similar in the front matter section of the document and add a cover image for our article.

To do that, first add the path to the image in the front matter section:

```
---
title: Get started with Markdoc
description: How to get started with Markdoc
cover: /images/martin-sanchez-gD3dUQpMlvk-unsplash.jpg
---
```

Next, in `./layouts/ArticleLayout.jsx`, we’ll add the image:

```
// ./layouts/ArticleLayout.jsx

import Head from "next/head";
import Image from "next/image";
import SiteHeader from "../components/SiteHeader";
const ArticleLayout = ({ markdoc, children }) => {
 const { title, description, cover } = markdoc.frontmatter;
 return (
  <>
   {/* ... */}
   <article className="site-article">
    <div className="wrapper">
     <header className="article-header">
      {/* render image */}
      <div className="img-cont relative h-56">
       <Image
        src={cover}
        fill={true}
        alt="cover"
        className="object-cover rounded-b-2xl"
       />
      </div>
      {/* ... */}
     </header>
     {/* ... */}
    </div>
   </article>
  </>
 );
};

export default ArticleLayout;
```

With that, we should see our image:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image2_0ed93de2f1d4b5d701cbbcbf5271e4fa.jpg" alt="The website landing page with a header image."/></figure>

In the next section, we’ll see how we can render a list of our articles.

## Creating an Articles page

First, we’ll have to install a few packages, namely:

- `glob-promise` so we can find all of our Markdown files in the `articles/` folder
- `gray-matter` so we can extract the title from the Markdown frontmatter


To install, run:

```
npm install gray-matter glob-promise
```

Now, create a new file: `./pages/articles/index.js`

```
// ./pages/articles/index.js

import fs from "fs";
import glob from "glob-promise";
import matter from "gray-matter";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import path from "path";
import SiteLayout from "../../layouts/SiteLayout";

export const getStaticProps = async () => {
  // Find all Markdown files in the /articles directory
  const ARTICLES_DIR = path.join(process.cwd(), "pages/articles");
  const articlesPaths = await glob("**/*.md", { cwd: ARTICLES_DIR });

  const articles = articlesPaths.map((articlePath) => {
    // get the slug from the markdown file name
    const slug = path.basename(articlePath, path.extname(articlePath));
    // read the markdown files
    const source = fs.readFileSync(
      path.join(process.cwd(), "pages/articles", articlePath),
      "utf8"
    );
    // use gray-matter to parse the article frontmatter section
    const { data } = matter(source);
    const { title, description, cover } = data;

    return {
      title,
      description,
      cover,
      slug,
    };
  });
  return {
    props: {
      articles,
    },
  };
};

const Articles = ({ articles }) => {
  return (
    <>
      <Head>
        <title>My articles</title>
        <meta name="description" content="View all my articles" />
      </Head>
      <section>
        <header className="articles-header">
          <div className="wrapper">
            <h1 className="font-extrabold text-5xl">
              Hey there, view all my articles
            </h1>
          </div>
        </header>
        <ul className="articles">
          {articles.map((article) => (
            <li key={article.slug} className="article">
              <Link href={`/articles/${article.slug}`}>
                <header className="article-item-header">
                  <Image
                    src={article.cover}
                    width={300}
                    height={200}
                    alt="cover"
                  />
                  <div className="details">
                    <h2 className="font-bold text-3xl">{article.title}</h2>
                    <p> {article.description} </p>
                  </div>
                </header>
              </Link>
            </li>
          ))}
        </ul>
      </section>
    </>
  );
};
export default Articles;

// define layout for articles page
Articles.getLayout = (page) => {
  return <SiteLayout> {page} </SiteLayout>;
};
```

Here, we’re using `getStaticProps` to do the following:

- Retrieve all of the `.md` files in the `./pages/articles` folder
- Read the contents of each file and obtain the `slug` and `frontmatter` data


Then, we export the data and access it from our `Articles` component as `props`. We can create a list of articles with slug, title, description, and cover data.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image8_dc6ebb3ffa497ba5189af414914eedc2.jpg" alt="The website has been transformed into a blog landing page. "/></figure>

Sweet!

## Conclusion

In this article, we built out a blog using Markdoc and all we have to do to update our content is edit a markdown file or create a new one, save it, and redeploy.

Since the content and the application live together, we do not have to go back and forth between a CMS or database in order to update our blog.

We can also add more features, like tags and sorting by date, just by including the information in the front matter of each `.md` file.

Thanks for going through this tutorial! I hope you learned a thing or two; feel free to keep exploring.

### Further Reading and Resources

Here are a few links you might find useful:

#### Source Code and Live Example

- [Source code on GitHub](https://github.com/miracleonyenma/markdoc-app)
- [Live example hosted on Vercel](https://markdoc-app.vercel.app/)


#### Further Reading

- [Markdoc documentation](https://markdoc.dev/docs/nextjs)
- [Using Markdoc with Next.js](https://markdoc.dev/docs/nextjs)