Traverse files on server side

I found this solution in https://github.com/mxstbr/mxstbr.com/blob/master/data/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') || file.endsWith('.mdx'))

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))

API Routing (Next.js 9.1.7)

With this new feature, we can create a simle data collection api.

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

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

export default (req, res) => {
    const { tag, page } = req.query
    const numPerPage = 7
    var blogs = files
        .map((file, index) => {
            const name = path.join(DIR, file)
            const contents = fs.readFileSync(name, 'utf-8')

            const match = META.exec(contents)

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

            // eslint-disable-next-line no-eval
            let meta = eval('(' + match[1] + ')')
            meta.tags = meta.tags.map(t => t.toLowerCase())
            return {
                ...meta,
                path: file.replace(/\.mdx?$/, ''),
                index,
            }
        })
        .filter(meta => meta.published)
        .filter(meta => {
            if (tag) {
                return meta.tags.indexOf(tag.toLowerCase()) > -1
            } else {
                return true
            }
        })
        .sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt))

    const total = blogs.length

    if (page) {
        blogs = blogs.slice(0 + (page - 1) * numPerPage, 0 + page * numPerPage)
    } else {
        blogs = blogs.slice(0, numPerPage)
    }

    const totalPages = Math.ceil(total / numPerPage)
    res.status(200).json({
        blogs: blogs,
        totalPages: totalPages,
        total: total,
    })
}

Basically, I move the original solution here and use res.status(200).json() to return the blogs. With this, for pages that should be rendered in the same template, such as pagination, search page, just pass those query parameters like const { tag, page } = req.query and add logic before passing it into json.