Mastering MDX Metadata Configuration in Next.js with TypeScript

Learn how to configure, customize, and optimize MDX metadata in Next.js applications with TypeScript for better SEO and content organization.

Back

Table of Contents

Mastering MDX Metadata Configuration in Next.js

A comprehensive guide to setting up, customizing, and optimizing metadata in MDX files for Next.js applications with TypeScript support

🌟 Introduction to MDX Metadata

If you're building a modern web application or blog with Next.js, you've likely encountered MDX - the powerful format that combines Markdown with JSX. One of the most crucial aspects of working with MDX files is properly configuring metadata to enhance SEO, improve content organization, and enable powerful filtering and sorting capabilities.

In this guide, we'll explore everything you need to know about MDX metadata configuration in Next.js projects, with TypeScript support for type safety. Whether you're building a blog, documentation site, or content-heavy application, mastering metadata will take your project to the next level.

[Image suggestion: A visual diagram showing the relationship between MDX files, metadata, and how they integrate with Next.js]


🤔 What is MDX Metadata and Why Does It Matter?

MDX metadata refers to the structured information about your content that lives alongside the actual content itself. This typically appears at the top of MDX files in a format called "frontmatter" - a YAML-like section enclosed between triple dashes.

Here's a simple example:

---
title: "My First Blog Post"
date: "2023-05-15"
author: "Jane Developer"
tags: ["next.js", "mdx", "tutorial"]
---
 
# Hello World!
 
This is my first blog post using MDX...

Why metadata matters:

  • Content organization - Sort, filter, and categorize your content
  • SEO optimization - Provide search engines with structured data
  • Dynamic features - Build related posts, tag clouds, and author pages
  • Development efficiency - Define content properties in a type-safe way

🛠 Setting Up MDX in Next.js with TypeScript

Before diving into metadata configuration, let's ensure we have MDX properly set up in our Next.js project with TypeScript support.

1. Install Required Dependencies

npm install @next/mdx @mdx-js/loader @mdx-js/react gray-matter
npm install -D @types/mdx

2. Configure Next.js for MDX

Create or update your next.config.js file:

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [],
    rehypePlugins: [],
  },
});
 
module.exports = withMDX({
  pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
});

3. Define TypeScript Types for Metadata

Create a types/mdx.d.ts file to define the structure of your metadata:

declare module '*.mdx' {
  export interface MDXMetadata {
    title: string;
    date: string;
    author: string;
    description?: string;
    tags?: string[];
    [key: string]: any;
  }
 
  const metadata: MDXMetadata;
  export { metadata };
  
  const content: React.ComponentType;
  export default content;
}

This TypeScript definition ensures you get proper type checking and autocompletion when working with your metadata.


📝 Creating a Metadata Extraction Utility

Now that we have the basic setup, let's create a utility function to extract metadata from MDX files. We'll use the fs module and gray-matter to read the files and parse the frontmatter.

// lib/mdx.ts
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { MDXMetadata } from '../types/mdx';
 
const POSTS_DIRECTORY = path.join(process.cwd(), 'posts');
 
export function getAllPostsMetadata(): MDXMetadata[] {
  const fileNames = fs.readdirSync(POSTS_DIRECTORY);
  
  return fileNames.map((fileName) => {
    // Remove ".mdx" from file name to get id
    const id = fileName.replace(/\.mdx$/, '');
    
    // Read markdown file as string
    const fullPath = path.join(POSTS_DIRECTORY, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf8');
    
    // Use gray-matter to parse the post metadata section
    const { data } = matter(fileContents);
    
    // Validate and return the metadata
    return {
      id,
      ...(data as Omit<MDXMetadata, 'id'>)
    };
  });
}
 
export function getPostBySlug(slug: string) {
  const fullPath = path.join(POSTS_DIRECTORY, `${slug}.mdx`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');
  
  // Use gray-matter to parse the post metadata section
  const { data, content } = matter(fileContents);
  
  return {
    content,
    metadata: {
      slug,
      ...(data as Omit<MDXMetadata, 'slug'>)
    }
  };
}

[Image suggestion: A flowchart showing how the metadata extraction process works, from MDX files to structured data]


⚡ Advanced Metadata Configuration Techniques

1. Extending Metadata with External Data

Sometimes you want to add metadata that isn't directly in your MDX files. For example, you might want to add a "lastModified" date based on Git history or add a "readingTime" estimate.

You can use the remark-mdx-metadata plugin to modify metadata externally:

npm install remark-mdx-metadata

Then update your next.config.js:

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [
      [require('remark-mdx-metadata'), {
        // Add custom metadata to all MDX files
        update: (metadata) => ({
          ...metadata,
          readingTime: calculateReadingTime(metadata.content),
          editUrl: `https://github.com/yourusername/yourrepo/edit/main/posts/${metadata.slug}.mdx`,
        }),
      }],
    ],
    rehypePlugins: [],
  },
});

2. Validating Metadata with TypeScript

To ensure your metadata meets your requirements, you can create a validation function:

// lib/validateMetadata.ts
import { MDXMetadata } from '../types/mdx';
 
export function validateMetadata(metadata: Partial<MDXMetadata>, filePath: string): MDXMetadata {
  // Required fields
  if (!metadata.title) {
    throw new Error(`Missing required "title" field in ${filePath}`);
  }
  
  if (!metadata.date) {
    throw new Error(`Missing required "date" field in ${filePath}`);
  }
  
  // Validate date format
  if (!/^\d{4}-\d{2}-\d{2}$/.test(metadata.date)) {
    throw new Error(`Invalid date format in ${filePath}. Use YYYY-MM-DD format.`);
  }
  
  // Return validated metadata
  return metadata as MDXMetadata;
}

3. Creating Custom Metadata Fields

You can extend your metadata schema to include custom fields specific to your project:

// types/mdx.d.ts
export interface MDXMetadata {
  // Standard fields
  title: string;
  date: string;
  author: string;
  
  // SEO fields
  description?: string;
  ogImage?: string;
  
  // Content organization
  tags?: string[];
  category?: string;
  
  // Custom fields
  featured?: boolean;
  sponsoredContent?: boolean;
  difficulty?: 'beginner' | 'intermediate' | 'advanced';
  
  // Dynamic fields
  readingTime?: number;
  editUrl?: string;
  
  [key: string]: any;
}

📊 Using Metadata in Your Next.js Pages

Now that we have our metadata extraction set up, let's see how to use it in Next.js pages.

For Static Site Generation (SSG)

// pages/blog/[slug].tsx
import { GetStaticProps, GetStaticPaths } from 'next';
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
import { MDXMetadata } from '../../types/mdx';
import { getAllPostsMetadata, getPostBySlug } from '../../lib/mdx';
 
interface PostPageProps {
  source: MDXRemoteSerializeResult;
  metadata: MDXMetadata;
}
 
export default function Post({ source, metadata }: PostPageProps) {
  return (
    <div>
      <h1>{metadata.title}</h1>
      <div>By {metadata.author} • {metadata.date}</div>
      {metadata.tags && (
        <div>
          {metadata.tags.map(tag => (
            <span key={tag} className="tag">{tag}</span>
          ))}
        </div>
      )}
      <main>
        <MDXRemote {...source} />
      </main>
    </div>
  );
}
 
export const getStaticPaths: GetStaticPaths = async () => {
  const posts = getAllPostsMetadata();
  
  return {
    paths: posts.map(post => ({
      params: { slug: post.id }
    })),
    fallback: false
  };
};
 
export const getStaticProps: GetStaticProps = async ({ params }) => {
  const { content, metadata } = getPostBySlug(params?.slug as string);
  const mdxSource = await serialize(content);
  
  return {
    props: {
      source: mdxSource,
      metadata
    }
  };
};

Creating a Blog Index with Metadata

// pages/blog/index.tsx
import Link from 'next/link';
import { GetStaticProps } from 'next';
import { getAllPostsMetadata } from '../../lib/mdx';
import { MDXMetadata } from '../../types/mdx';
 
interface BlogIndexProps {
  posts: MDXMetadata[];
}
 
export default function BlogIndex({ posts }: BlogIndexProps) {
  return (
    <div>
      <h1>Blog Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <Link href={`/blog/${post.id}`}>
              <a>
                <h2>{post.title}</h2>
                <p>{post.description}</p>
                <div>By {post.author} • {post.date}</div>
              </a>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}
 
export const getStaticProps: GetStaticProps = async () => {
  const posts = getAllPostsMetadata().sort((a, b) => 
    new Date(b.date).getTime() - new Date(a.date).getTime()
  );
  
  return {
    props: {
      posts
    }
  };
};

[Image suggestion: Screenshot of a blog index page showing metadata in action with title, date, and tags]


🔍 Optimizing Metadata for SEO

Metadata isn't just for organizing your content - it's also crucial for SEO. Here's how to leverage your MDX metadata for better search engine visibility:

1. Generate Dynamic <head> Content

// components/SEO.tsx
import Head from 'next/head';
import { MDXMetadata } from '../types/mdx';
 
interface SEOProps {
  metadata: MDXMetadata;
  url: string;
}
 
export default function SEO({ metadata, url }: SEOProps) {
  const title = `${metadata.title} | Your Site Name`;
  const description = metadata.description || 'Default description';
  const ogImage = metadata.ogImage || '/default-og-image.jpg';
  
  return (
    <Head>
      <title>{title}</title>
      <meta name="description" content={description} />
      
      {/* Open Graph */}
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:url" content={url} />
      <meta property="og:image" content={ogImage} />
      
      {/* Twitter */}
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={ogImage} />
      
      {/* Add structured data if needed */}
    </Head>
  );
}

Then use this component in your blog post page:

// In your blog post component
<SEO 
  metadata={metadata} 
  url={`https://yourdomain.com/blog/${metadata.id}`} 
/>

🚀 Conclusion and Next Steps

Properly configuring MDX metadata in your Next.js application unlocks powerful capabilities for content management, organization, and SEO. With TypeScript support, you can ensure type safety and improve developer experience when working with your content.

Key takeaways:

  • MDX metadata provides structured information about your content
  • TypeScript integration ensures type safety and improves developer experience
  • Extraction utilities help you process and organize your content
  • Advanced techniques like external metadata modification enhance flexibility
  • SEO optimization leverages metadata for better search visibility

Where to go from here:

  1. Implement a tagging system for your content using metadata
  2. Create category pages that automatically group related content
  3. Add search functionality that indexes your metadata
  4. Set up author profiles based on author metadata
  5. Explore advanced plugins like remark-mdx-metadata for custom workflows

By mastering MDX metadata configuration in Next.js, you've taken a significant step toward building a robust, maintainable, and SEO-friendly content platform.

Happy coding!

Written by

Marcus Ruud

At

Fri Nov 10 2023