There are many ways to host blogs with Next.js but I needed something fast & simple: plain MDX files, first‑party support, and zero extra content pipelines. No Contentlayer (which is unmaintained). No next-mdx-remote. No heavy weighted CMS systems.

TL;DR

Why MDX + App Router?

  1. Add the official MDX plugin and let Next treat MD/MDX as pages.

next.config.js

import createMDX from '@next/mdx';

const withMDX = createMDX({
  // Add remark/rehype plugins if/when needed
  options: {
    remarkPlugins: [],
    rehypePlugins: [],
  },
});

/** @type {import('next').NextConfig} */
const nextConfig = {
  ...
  pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
};

export default withMDX(nextConfig);

  1. Optionally customize how MDX renders components (I kept it minimal for now):

mdx-components.tsx

import type { MDXComponents } from 'mdx/types';

export function useMDXComponents(components: MDXComponents = {}): MDXComponents {
  return {
    ...components,
  };
}

  1. Type the metadata you export from MDX so TS understands it when imported elsewhere.

src/types/mdx.d.ts

declare module '*.mdx' {
  import type { ComponentType } from 'react';
  const MDXComponent: ComponentType<any>;
  export default MDXComponent;
  export const metadata: {
    title?: string;
    description?: string;
    date?: string;
    author?: string;
    tags?: string[];
  };
}

  1. Create a post as a route. In the App Router, a folder is your slug and page.mdx is the page.

src/app/blog/how-to-export-ig-followers-tutorial/page.mdx

export const metadata = {
  title: 'How to Export Instagram Followers (CSV, Excel, JSON)',
  description: 'Step-by-step guide…',
  date: '2025-08-28',
};

import Image from 'next/image';

  1. Build a simple index page by importing metadata straight from MDX modules.

src/app/blog/page.tsx

import Link from 'next/link';
import { metadata as igExport } from './how-to-export-ig-followers-tutorial/page.mdx';

const posts = [
  {
    slug: 'how-to-export-ig-followers-tutorial',
    title: igExport?.title ?? 'How to Export Instagram Followers',
    description: igExport?.description,
    date: igExport?.date,
  },
];

export default function BlogIndexPage() {
  // Render cards linking to /blog/[slug]
}

  1. Keep your sitemap honest by importing the same metadata for lastModified.

src/app/sitemap.ts

import type { MetadataRoute } from 'next';
import { metadata as igExportPost } from './blog/how-to-export-ig-followers-tutorial/page.mdx';
import { getURL } from '@/utils/get-url';

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    // …other routes
    {
      url: getURL('blog/how-to-export-ig-followers-tutorial'),
      lastModified: igExportPost?.date ? new Date(igExportPost.date) : new Date(),
      changeFrequency: 'weekly',
      priority: 0.7,
    },
  ];
}

The Aha Moments (and a few gotchas)

Contentlayer vs. Native MDX

What Contentlayer gives you:

Native MDX strengths (why I chose it here):