Skip to content
______________________
V.1.0.0 // SECURE CONNECTION
Return_to_vault
[CONSTRUCT: 2026-02-20]

Next.js MDX Blog Loader

Next.jsMDXTypeScript

Next.js MDX Blog Loader

A file-system blog loader that reads MDX files from a directory, parses frontmatter with gray-matter, calculates reading time, and returns sorted posts with pinned entries first. No database, no CMS, no API. Just files on disk and a function that reads them. This powers the blog on this site.

When to Use

  • Building a static MDX blog in the Next.js App Router without a headless CMS
  • You want reading time estimation and pinned post support out of the box
  • Keeping your content workflow simple: write an .mdx file, it shows up

The Code

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const BLOG_DIR = path.join(process.cwd(), 'src/content/blog');

interface BlogPostMeta {
  slug: string;
  title: string;
  excerpt: string;
  date: string;
  readingTime: string;
  tags: string[];
  published: boolean;
  pinned?: boolean;
}

export async function getBlogPosts(): Promise<BlogPostMeta[]> {
  const posts = getLocalPosts();
  return posts.sort((a, b) => {
    if (a.pinned && !b.pinned) return -1;
    if (!a.pinned && b.pinned) return 1;
    return new Date(b.date).getTime() - new Date(a.date).getTime();
  });
}

function getLocalPosts(): BlogPostMeta[] {
  if (!fs.existsSync(BLOG_DIR)) return [];
  return fs.readdirSync(BLOG_DIR)
    .filter((file) => file.endsWith('.mdx'))
    .map((file) => {
      const content = fs.readFileSync(path.join(BLOG_DIR, file), 'utf-8');
      const { data } = matter(content);
      return {
        slug: file.replace('.mdx', ''),
        title: data.title,
        excerpt: data.excerpt,
        date: data.date,
        readingTime: `${Math.ceil(content.split(/\s+/).length / 200)} min read`,
        tags: data.tags || [],
        published: data.published,
        pinned: data.pinned,
      } as BlogPostMeta;
    })
    .filter((post) => post.published);
}

Notes

The published frontmatter field is your draft system. Set it to false and the post won't appear anywhere. Reading time assumes 200 words per minute, which is conservative but honest.

Share

"End of transmission."

[CLOSE_CONSTRUCT]