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.
\
.mdx as components and export metadata alongside content.@next/mdx with the App Router and kept indexing simple by importing metadata directly from each MDX file.\
Content is code: The App Router treats a folder as a route and page.mdx as a component. You get layouts, streaming, and RSC benefits for free.
First‑party MDX: The official plugin is maintained with Next.js and plays nicely with routing, metadata, and bundling.
Lower cognitive load: For a small product site, I don’t want a content compiler, watcher, or a GraphQL layer. A few MDX files and some imports are enough.
\
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);
\
mdx-components.tsx
import type { MDXComponents } from 'mdx/types'; export function useMDXComponents(components: MDXComponents = {}): MDXComponents { return { ...components, }; }
\
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[]; }; }
\
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';
\
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] }
\
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, }, ]; }
\
metadata) from any .mdx file. That made the blog index and sitemap trivial.*.mdx module declaration means TS won’t complain when you do import { metadata } from 'some-post/page.mdx'.\
What Contentlayer gives you:
title or date.content/ directory, compute slugs/paths, and query everything in one place.readingTime, slug, canonical URLs, etc., at build time.Native MDX strengths (why I chose it here):
.mdx files and imports.app/blog/[slug]/page.mdx, same place users will visit.*.mdx module declaration plus optional Zod to validate metadata if you want stricter checks.\


