How to Export Metadata from MDX for Next.js SEO
When using Next.js with MDX, you need a way to export metadata from your MDX files for SEO.
#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)">'</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)">'</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)">'</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)">'</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)">'</span><span class="sh__token--string" style="color:var(--sh-string)">Author</span><span class="sh__token--string" style="color:var(--sh-string)">'</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)">'</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)">'</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)">'</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)">'</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 is rendered, you can access the exported metadata data in your layout.tsx. This allows you to create dynamic <head> elements using Next.js's generateMetadata API.
#Practical Example
In your blog layout.tsx, you can read the metadata and dynamically set the page title:
<span class="sh__line"><span class="sh__token--keyword" style="color:var(--sh-keyword)">import</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--identifier" style="color:var(--sh-identifier)">useMDXComponent</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)">from</span><span class="sh__token--space" style="color:var(--sh-space)"> </span><span class="sh__token--string" style="color:var(--sh-string)">'</span><span class="sh__token--string" style="color:var(--sh-string)">next-contentlayer/hooks</span><span class="sh__token--string" style="color:var(--sh-string)">'</span></span>
<span class="sh__line"></span>
<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>#Additional SEO Tips for MDX
- Always include a unique
descriptionfor each post - Set the
canonicalURL to avoid duplicate content issues - Use structured data (JSON-LD) for rich search results
- Add OpenGraph and Twitter card metadata for social sharing
- Include
publishedTimefor article-type content to help Google understand freshness
#Conclusion
Exporting metadata from MDX is straightforward and is an excellent practice for SEO. It allows Next.js to dynamically set your page title and description, improving your visibility in search results.