Blogging SEO & AEO: Step-by-step guide to implement MDX

Blogging SEO & AEO: Step-by-step guide to implement MDX
Amice Wong
3 weeks, 6 days ago
6 min read
Blogging SEO & AEO: Step-by-step guide to implement MDX

In my previous post, I explained WHY I chose Next.js instead of Django, MDX instead of BlockNote for the Closmore SalesOS.

The goal was simple: Total control over the HTML signal.

If you want AI agents to cite your work, you can't hide your content inside 10 layers of "Div Soup" from a standard block editor. You need clean, semantic code.

Here is exactly how I implemented it in Next.js 16, including the "final hurdle" that almost broke my layout.

Step 1: The Foundation (Next.js 16 Config)

First, install the official packages:

npm install @next/mdx @mdx-js/loader @mdx-js/react


Since I am using TypeScript (next.config.ts), the setup is slightly different from the old tutorials.

You have to tell Next.js to treat .mdx files as pages.

//next.config.ts
import type { NextConfig } from "next";
import createMDX from "@next/mdx";

const nextConfig: NextConfig = {
  pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
};

const withMDX = createMDX({});
export default withMDX(nextConfig);


You also need a file called mdx-components.tsx in your root folder. This is a "hook" that allows you to inject custom styles globally.
Make sure it is there.


//mdx-components.tsx
import type { MDXComponents } from 'mdx/types';

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    // This allows us to use custom components or HTML tags globally
    ...components,
  };
}

Step 2: The "Code Interface" (Component Mapping)

This is the most powerful part of MDX. I created a file called mdx-style.tsx.

Instead of writing CSS for every blog post, I created a mapping. I told Next.js:

"Every time you see a standard <p> tag in Markdown, give it my 'Closmore' 20px typography."


//app/salesos/_components/mdx-style.tsx
import React from 'react';
import Image from 'next/image';

export const closmoreComponents = {

  h1: ({ children }: any) => <h1 className="text-3xl font-bold mb-8">{children}</h1>,
  h2: ({ children }: any) => <h2 className="text-2xl font-bold mb-6">{children}</h2>,
  h3: ({ children }: any) => <h3 className="text-xl font-bold mb-4">{children}</h3>,

  p: ({ children }: any) => <p className="text-muted-foreground mb-4 text-[20px]">{children}</p>,

  ul: ({ children }: any) => <ul className="text-muted-foreground mb-5 text-[20px] list-disc pl-5">{children}</ul>,
  ol: ({ children }: any) => <ol className="text-muted-foreground text-[20px] list-decimal pl-5">{children}</ol>,
  li: ({ children }: any) => <li className="pl-1">{children}</li>,

  a: ({ children, href }: any) => (
    <a href={href} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline font-medium">
      {children}
    </a>
  ),

  strong: ({ children }: any) => <strong className="font-bold text-zinc-900">{children}</strong>,
};

Step 3: The Content

Two files to render the blog page:

(1) The page.tsx - where you usually compare a page

(2) A markdown file - content.mdx - side by side with the page.tsx

3.1 The page.tsx

This is where the start of page rendering happens:

// app/salesos/momentum/[slug]/page.tsx
import Content from './content.mdx';  // the markdown
import { closmoreComponents } from '../../_components/mdx-style';

export default async function m5_what_drive_sales() {
  return (
    <div className="w-full salesos">

      <div className="max-w-4xl mx-auto mt-3 pt-5 md:pt-7 lg:pt-14 px-4 pb-20">
        <p className="text-xs uppercase tracking-wide text-muted-foreground mb-4">Momentum · Sales Performance</p>
        <Content components={closmoreComponents} />
      </div>
    </div>
  );
}

3.2 The Markdown Content File

Prepare your content in markdown format :

//app/salesos/momentum/[slug]/content.mdx

## Two Pipeline Reports

Since then, I "automatically" started keeping two sales pipeline reports.

One for my CEO.

One for myself — with a Commission Estimation column.

Every Monday morning, I would only open the second one and work on those qualified leads with the best commission potential. I fell into a sales career — by chance.

For clarify, it is the file structure:


Step 3: The "Final Hurdle" (The Figure Bug)

I hit a wall. In my design, I wanted images to have a border and a caption (the <figure> tag).

In Markdown, images are written like this: ![alt](url "caption").

The problem? Markdown automatically wraps images in a <p> tag.

The result was a Hydration Error: <p><figure>...</figure></p>.

In HTML, you cannot put a "box" <figure> inside a "paragraph" <p>. The browser tried to fix it, React got confused, and my console turned red.

The Solution:

I set up a new component figure.tsx.

//components/ui/figure.tsx
import Image from 'next/image';

interface FigureProps {
  src: string;
  alt: string;
  caption?: string;
  width?: number;
  height?: number;
}

export const Figure = ({ src, alt, caption, width = 1200, height = 675 }: FigureProps) => {
  return (
    <figure className="my-8">
      <Image src={src} alt={alt} width={width} height={height} className="w-full rounded-lg border border-border" />
      {caption && <figcaption className="mt-2 text-sm text-muted-foreground">{caption}</figcaption>}
    </figure>
  );
};

Use  <Figure> in app/salesos/_components/mdx-style.tsx

// app/salesos/_components/mdx-style.tsx
import { Figure } from '@/components/ui/figure';
...

export const closmoreComponents = {
  ...
  // IMAGES (Handles the Figure/Caption style)
  Figure: ({ src, alt, caption, width, height }: any) => <Figure src={src} alt={alt} caption={caption} width={width} height={height} />,
  ...
};


Quote it like that in the Markdown content.mdx

---

## Two Pipeline Reports

Since then, I "automatically" started keeping two sales pipeline reports.

One for my CEO.

One for myself — with a Commission Estimation column.

Every Monday morning, I would only open the second one and work on those qualified leads with the best commission potential. I fell into a sales career — by chance.

<Figure
  src="/salesos/momentum/m5-what-drives-sales-performance-closmore-commission.webp"
  alt="Two pipeline reports - one for the CEO, one for myself with commission estimation"
  caption="The second report — the one my boss never saw"
/>

And I was not alone.

---

The Result: Pure Signal

When I open "Chrome Inspect" now, I don't see rubbish. I see a clean, professional document that is ready for Google and ready for AI agents to index.

Feature         Before (Blocknote)         After (MDX)

DOM Depth 10+ layers of Divs          2 layers (Semantic)

Page Speed Database dependent  Static (Instant)

Control         Standard CSS                  Full React Power

Conclusion

MDX is a "piece of cake" for a techie, but for a Founder, it is a Strategic Advantage.

I’m not a professional software engineer, but I know that in the AI era, Technical Integrity = Search Authority. By building this "Code Interface," I’ve ensured that every Sales Lesson I write for Closmore is technically optimized for the next 10 years of search.

If you need help auditing your blog's "AI-Readability," reach out. I'm happy to share my full boilerplate.

Great job! Take a coffee break before reading more Amice's articles :P

⁠Simplicity is prerequisite for reliability_Amice_Dev
⁠Simplicity is prerequisite for reliability. ⁠Without clarity, systems become fragile and unpredictable.

Related blogs

Full-Stack Next.js with TypeScript & Shadcn - New API with Frontend Project Checklist
Full-Stack Next.js with TypeScript & Shadcn - New API with Frontend Project Checklist

By Amice Wong

Read more
Building a Chrome Extension from Scratch — My ClosMore AI Experiment
Building a Chrome Extension from Scratch — My ClosMore AI Experiment

By Amice Wong

Read more
Leveraging BlockNote to Build an Embedded Breathing App in Next.js
Leveraging BlockNote to Build an Embedded Breathing App in Next.js

By Amice Wong

Read more
Can AI Build Your Website? A Full-Stack Developer’s Perspective on Vibe Coding
Can AI Build Your Website? A Full-Stack Developer’s Perspective on Vibe Coding

By Amice Wong

Read more
SEO & AEO: Is Your Blog Technically Optimized for AI and Search Engine?
SEO & AEO: Is Your Blog Technically Optimized for AI and Search Engine?

By Amice Wong

Read more
(function () { if (!window.chatbase || window.chatbase('getState') !== 'initialized') { window.chatbase = (...arguments) => { if (!window.chatbase.q) { window.chatbase.q = []; } window.chatbase.q.push(arguments); }; window.chatbase = new Proxy(window.chatbase, { get(target, prop) { if (prop === 'q') { return target.q; } return (...args) => target(prop, ...args); }, }); } const onLoad = function () { const script = document.createElement('script'); script.src = 'https://www.chatbase.co/embed.min.js'; script.id = 'nJYgZ_-ZeZ-G1qbXzsm6j'; script.domain = 'www.chatbase.co'; document.body.appendChild(script); }; if (document.readyState === 'complete') { onLoad(); } else { window.addEventListener('load', onLoad); } })();