Filtering by Publish Date in your NextJS MDX Blog
When writing for this site, I often find myself needing to be able to keep an article or lesson in draft, but also publish something to production.
Today, this NextJS MDX blog doesn't support the concept of a "draft" post, so let's introduce one.
First, in my content directory where I hold all my MDX files (for me, it's /src/content/articles
), I'm going to add a new file called
draft-article.mdx
.
---
author: Ty Barho
date:
title: A Draft Article
description:
---
This is only a draft.
Filtering Articles by Publish Status with NextJS & MDX
Currently, I have functions for fetching my articles in /src/lib/getContent.js
:
import glob from 'fast-glob'
import * as path from 'path'
import fs from 'fs'
import matter from 'gray-matter'
export async function getAllContent(srcDir) {
let slugs = (
await glob(['*.mdx', '*/index.mdx'], {
cwd: path.join(process.cwd(), `src/content/${srcDir}`),
})
).map((n) => n.replace(/\.mdx$/, ''))
const cb = (slug) => getContentItem(slug, srcDir)
let items = await Promise.all(slugs.map(cb))
return items.sort((a, z) => new Date(z.data.date) - new Date(a.data.date))
}
export async function getContentItem(slug, srcDir) {
const filePath = path.join(process.cwd(), `src/content/${srcDir}/${slug}.mdx`)
const source = fs.readFileSync(filePath)
const { content, data } = matter(source)
return {
content,
data,
slug,
}
}
First, let's update the getAllContent
function to filter out any articles that don't have a date
property set. Like so:
export async function getAllContent(srcDir) {
let slugs = (
await glob(['*.mdx', '*/index.mdx'], {
cwd: path.join(process.cwd(), `src/content/${srcDir}`),
})
).map((n) => n.replace(/\.mdx$/, ''))
const cb = (slug) => getContentItem(slug, srcDir)
let items = await Promise.all(slugs.map(cb))
return items
.filter((item) => item.data.date)
.sort((a, z) => new Date(z.data.date) - new Date(a.data.date))
}
Now, let's check and see that our test article is not showing up in our /articles
page.
As you can see, the draft article is not showing up in the list of articles.
Great! 🦄
Now, we need to make sure that article isn't accessible via the /articles/[slug]
page by visiting http://localhost:3000/articles/draft-article.
Sure enough, we get a 404.
But wait... why?
We haven't written any special code to prevent this article from showing up, so why isn't it showing up?
NextJS getStaticPaths
& fallback: false
The reason is because of how NextJS handles dynamic routes.
When you visit a page like /articles/[slug]
, NextJS will generate a static page for each slug in the getStaticPaths
function.
Here's ours...
export async function getStaticPaths() {
const articles = await getAllContent('articles');
const paths = articles.map(a => `/articles/${a.slug}`);
return {
paths,
fallback: false,
};
}
As you can see, we are using the getAllContent
method we just updated to find our lists of paths.
By using fallback: false
, we are also telling NextJS that we don't want to generate any pages that don't exist in the paths
array.
Here is how fallback: false
works...
Yet another thing NextJS handles for us nearly automatically!
Fixing our NextJS Sitemap to Exclude Drafts
Now, we need to make sure that our sitemap doesn't include any draft articles.
In next-sitemap.config.js
we will add a helper function to grab the front-matter of each article and filter out any articles that don't have a date
property.
// ... other imports
const matter = require('gray-matter')
function getFileData(sourceDir, filePath) {
filePath = path.join(process.cwd(), `./src/content/${sourceDir}/${filePath}`)
const source = fs.readFileSync(filePath)
return matter(source).data
}
// ...
module.exports = {
siteUrl: 'https://www.tybarho.com',
sitemapSize: 7000,
exclude: [],
generateRobotsTxt: true,
additionalPaths: async (config) => {
let result = []
const articles = fs.readdirSync(path.resolve('./src/content/articles'))
const lessons = fs.readdirSync(path.resolve('./src/content/lessons'))
for (const article of articles) {
const data = getFileData('articles', article)
if (data.date) {
result.push(
await config.transform(
config,
`/articles/${article.replace('.mdx', '')}`
)
)
}
}
for (const lesson of lessons) {
const data = getFileData('lessons', lesson)
if (data.date) {
result.push(
await config.transform(
config,
`/lessons/${lesson.replace('.mdx', '')}`
)
)
}
}
return result
},
robotsTxtOptions: {
policies: [
{
userAgent: '*',
allow: '/',
},
],
},
}
Be sure and run yarn postbuild
to regenerate the sitemap and, check /public/sitemap-0.xml
ensure that the draft article is not included.
Note: You may need to run yarn build
first
And that's that! We are now filtering out all of our draft articles! 😎🚀
If you enjoyed this article, please consider following me on Twitter