All notes

How to Export Metadata from MDX for Next.js SEO

MDX metadata is how each article tells Next.js — and search engines — its title, description, and canonical URL.

TechnicalJan 15, 20264 min read

Exporting metadata from MDX means declaring a metadata object in the file so Next.js can drive SEO tags from the content itself. One source of truth for humans reading the note and machines indexing it.


The method

Add an exported metadata object at the top of your MDX file:

<span class="sh__line"><span class="sh__token--keyword" style="color:var(--sh-keyword)">export</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--keyword" style="color:var(--sh-keyword)">const</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">metadata</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">=</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">{</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">title</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--string" style="color:var(--sh-string)">My Article Title</span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">description</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--string" style="color:var(--sh-string)">A brief description of the article</span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">authors</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">[</span><span class="sh__token--sign" style="color:var(--sh-sign)">{</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">name</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--string" style="color:var(--sh-string)">Author</span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">url</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--string" style="color:var(--sh-string)">https://example.com</span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">}</span><span class="sh__token--sign" style="color:var(--sh-sign)">]</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">alternates</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">{</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">    </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">canonical</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--string" style="color:var(--sh-string)">/blog/my-article</span><span class="sh__token--string" style="color:var(--sh-string)">&#039;</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--sign" style="color:var(--sh-sign)">}</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span></span>
<span class="sh__line"><span class="sh__token--sign" style="color:var(--sh-sign)">}</span></span>
<span class="sh__line"></span>

What Next.js does

When the MDX page renders, that export feeds the App Router metadata pipeline. You get dynamic <title>, description, and related head tags without a separate database row for every field.

Practical pattern

Keep content and SEO fields together. Use generateMetadata or the file export so layout and page stay aligned:

<span class="sh__line"><span class="sh__token--keyword" style="color:var(--sh-keyword)">export</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--keyword" style="color:var(--sh-keyword)">async</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--keyword" style="color:var(--sh-keyword)">function</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">generateMetadata</span><span class="sh__token--sign" style="color:var(--sh-sign)">(</span><span class="sh__token--sign" style="color:var(--sh-sign)">{</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">params</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">}</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--class" style="color:var(--sh-class)">Props</span><span class="sh__token--sign" style="color:var(--sh-sign)">)</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">{</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--keyword" style="color:var(--sh-keyword)">const</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">post</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">=</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--keyword" style="color:var(--sh-keyword)">await</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">getPost</span><span class="sh__token--sign" style="color:var(--sh-sign)">(</span><span class="sh__token--identifier" style="color:var(--sh-identifier)">params</span><span class="sh__token--sign" style="color:var(--sh-sign)">)</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--keyword" style="color:var(--sh-keyword)">return</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--sign" style="color:var(--sh-sign)">{</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">    </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">title</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">post</span><span class="sh__token--sign" style="color:var(--sh-sign)">.</span><span class="sh__token--property" style="color:var(--sh-property)">metadata</span><span class="sh__token--sign" style="color:var(--sh-sign)">.</span><span class="sh__token--property" style="color:var(--sh-property)">title</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">    </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">description</span><span class="sh__token--sign" style="color:var(--sh-sign)">:</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--identifier" style="color:var(--sh-identifier)">post</span><span class="sh__token--sign" style="color:var(--sh-sign)">.</span><span class="sh__token--property" style="color:var(--sh-property)">metadata</span><span class="sh__token--sign" style="color:var(--sh-sign)">.</span><span class="sh__token--property" style="color:var(--sh-property)">description</span><span class="sh__token--sign" style="color:var(--sh-sign)">,</span></span>
<span class="sh__line"><span class="sh__token--space" style="color:var(--sh-space)">  </span><span class="sh__token--sign" style="color:var(--sh-sign)">}</span></span>
<span class="sh__line"><span class="sh__token--sign" style="color:var(--sh-sign)">}</span></span>
<span class="sh__line"></span>
FieldWhy it matters
titleSERP and tab label
descriptionSnippet / social context
canonicalDuplicate URL control
publishedTimeFreshness signal for articles
JSON-LDRicher machine understanding

Metadata is table stakes. Clear writing and real usefulness are the strategy.

Checklist

  1. Unique description per post
  2. Canonical URL for every article
  3. Structured data (Article, FAQ when you have FAQs)
  4. Open Graph / Twitter fields for shares
  5. publishedTime on article-type pages

Exporting metadata from MDX is simple and worth doing on every note. It keeps SEO next to the words — which is where it belongs.

Frequently asked questions

  • An exported metadata object in an MDX file that Next.js can use for title, description, canonical URL, authors, and Open Graph fields — the same shape as the App Router Metadata API.

About the author

mosh

mosh is a product designer and design engineer working with design systems, LLM-powered prototypes, agent-safe interfaces, production UI, and automated workflows.

Keep reading