Docusaurus Markdown Source Plugin
A lightweight Docusaurus plugin that exposes your markdown files as raw .md URLs, perfect for LLMs and documentation tools.
Features
- 🔗 Direct
.mdURL Access: Append.mdto any docs URL to view raw markdown - 📋 Copy to Clipboard: One-click copy of markdown content
- 🎨 Clean UI: Dropdown menu in article headers
- 🤖 LLM-Friendly: Clean markdown output optimized for AI assistants
- 🖼️ Image Support: Handles images with absolute paths
- 🔒 SEO-Safe: Proper headers prevent duplicate content indexing
- ⚡ Zero Config: Works out-of-the-box with sensible defaults
Live Example
See it in action at flynumber.com/docs/ - try clicking the "Open Markdown" dropdown next to any page title!
Screenshot
Installation
npm install docusaurus-markdown-source-plugin
Or with yarn:
yarn add docusaurus-markdown-source-plugin
Quick Start
1. Add the Plugin
Edit your docusaurus.config.js (or .ts):
module.exports = { // ... your existing config plugins: [ 'docusaurus-markdown-source-plugin', // ... your other plugins ], };
That's it! The plugin automatically provides all necessary components. No manual file copying required.
2. Add Dropdown Styles (Optional)
Add these styles to your src/css/custom.css:
/* Markdown Actions Dropdown Styles */ /* Style the article header as flexbox */ article .markdown header { display: flex; align-items: center; flex-wrap: wrap; gap: 1rem; overflow: visible; } /* Allow h1 to grow and take available space */ article .markdown header h1 { flex: 1 1 auto; margin: 0; } /* Container for the markdown actions dropdown */ .markdown-actions-container { flex-shrink: 0; margin-left: auto; position: relative; } /* Ensure dropdown wrapper has proper positioning */ .markdown-actions-container .dropdown { position: relative; } /* Base dropdown menu styles */ .markdown-actions-container .dropdown__menu { z-index: 1000; min-width: 220px; right: auto; left: 0; } /* Add hover effect for dropdown items */ .dropdown__link:hover { background-color: var(--ifm-hover-overlay); } /* Responsive adjustments for mobile */ @media (max-width: 768px) { .markdown-actions-container { margin-right: clamp(0px, 0.5rem, 1rem); margin-bottom: 1rem; } .markdown-actions-container .button { font-size: 0.875rem; padding: 0.375rem 0.75rem; } /* Right-align menu on mobile to prevent cutoff */ .markdown-actions-container .dropdown__menu { right: 0; left: auto; min-width: min(220px, calc(100vw - 2rem)); max-width: calc(100vw - 2rem); padding-bottom: 0.75rem; } } /* RTL language support */ [dir="rtl"] .markdown-actions-container { margin-left: 0; margin-right: auto; } [dir="rtl"] .markdown-actions-container .dropdown__menu { right: auto; left: 0; } @media (max-width: 768px) { [dir="rtl"] .markdown-actions-container .dropdown__menu { left: 0; right: auto; } }
3. Build and Test
npm run build npm run serve
Visit any docs page - you should see the "Open Markdown" dropdown in the header!
How It Works
-
Build Time: The plugin processes all markdown files in
docs/during build:- Removes Docusaurus-specific syntax (front matter, imports, MDX components)
- Converts HTML elements back to markdown
- Converts relative image paths to absolute paths
- Copies image directories to build output
-
Runtime: The React component adds a dropdown menu to each docs page with two actions:
- View as Markdown: Opens the raw markdown file in a new tab
- Copy Page as Markdown: Copies the markdown source to clipboard
-
Server: Proper HTTP headers prevent search engines from indexing .md files while allowing AI assistants to access them
Deployment Configuration
To prevent duplicate content SEO issues and ensure proper content delivery, configure your server to send appropriate headers for .md files.
Vercel
Create or edit vercel.json in your project root:
{
"headers": [
{
"source": "/(.*)\\.md",
"headers": [
{
"key": "Content-Type",
"value": "text/plain; charset=utf-8"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Robots-Tag",
"value": "googlebot: noindex, nofollow, bingbot: noindex, nofollow"
},
{
"key": "Cache-Control",
"value": "public, max-age=3600, must-revalidate"
}
]
},
{
"source": "/docs/(.*)/img/(.*)",
"headers": [
{
"key": "X-Robots-Tag",
"value": "googlebot: noindex, nofollow, bingbot: noindex, nofollow"
},
{
"key": "Cache-Control",
"value": "public, max-age=86400, immutable"
}
]
}
]
}Netlify
Create or edit netlify.toml in your project root:
[[headers]] for = "/*.md" [headers.values] Content-Type = "text/plain; charset=utf-8" X-Content-Type-Options = "nosniff" X-Robots-Tag = "googlebot: noindex, nofollow, bingbot: noindex, nofollow" Cache-Control = "public, max-age=3600, must-revalidate" [[headers]] for = "/docs/*/img/*" [headers.values] X-Robots-Tag = "googlebot: noindex, nofollow, bingbot: noindex, nofollow" Cache-Control = "public, max-age=86400, immutable"
Cloudflare Pages
Create _headers file in your build directory (or configure build to copy it):
/*.md
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
X-Robots-Tag: googlebot: noindex, nofollow, bingbot: noindex, nofollow
Cache-Control: public, max-age=3600, must-revalidate
/docs/*/img/*
X-Robots-Tag: googlebot: noindex, nofollow, bingbot: noindex, nofollow
Cache-Control: public, max-age=86400, immutable
Apache
Create or edit .htaccess in your build directory:
<FilesMatch "\\.md$"> Header set Content-Type "text/plain; charset=utf-8" Header set X-Content-Type-Options "nosniff" Header set X-Robots-Tag "googlebot: noindex, nofollow, bingbot: noindex, nofollow" Header set Cache-Control "public, max-age=3600, must-revalidate" </FilesMatch> <LocationMatch "^/docs/.*/img/.*"> Header set X-Robots-Tag "googlebot: noindex, nofollow, bingbot: noindex, nofollow" Header set Cache-Control "public, max-age=86400, immutable" </LocationMatch>
Nginx
Add to your nginx.conf or site configuration:
location ~* \.md$ { add_header Content-Type "text/plain; charset=utf-8"; add_header X-Content-Type-Options "nosniff"; add_header X-Robots-Tag "googlebot: noindex, nofollow, bingbot: noindex, nofollow"; add_header Cache-Control "public, max-age=3600, must-revalidate"; } location ~* ^/docs/.*/img/.* { add_header X-Robots-Tag "googlebot: noindex, nofollow, bingbot: noindex, nofollow"; add_header Cache-Control "public, max-age=86400, immutable"; }
Why These Headers Matter
| Header | Purpose |
|---|---|
Content-Type: text/plain |
Tells browsers to display as plain text, not HTML |
X-Content-Type-Options: nosniff |
Prevents MIME type sniffing for security |
X-Robots-Tag: noindex, nofollow |
Prevents search engines from indexing (avoids duplicate content SEO issues) while allowing AI assistants to access |
Cache-Control |
Balances performance (caching) with content freshness |
CSS Customization
You can customize the dropdown appearance by overriding these CSS classes in your custom.css:
/* Change button style */ .markdown-actions-container .button { /* Your custom styles */ } /* Change dropdown menu style */ .markdown-actions-container .dropdown__menu { /* Your custom styles */ } /* Change dropdown item hover color */ .dropdown__link:hover { background-color: your-color; }
Troubleshooting
Dropdown Not Appearing
-
Check plugin installation: Ensure the plugin is in your
docusaurus.config.jsplugins array. -
Rebuild your site: After installing, run
npm run buildto ensure the plugin is loaded. -
Check browser console: Look for any errors that might indicate component loading issues.
-
Verify path configuration: The default path is
/docs/. If your docs use a different path (e.g.,/documentation/), you may need to swizzle the component and customize it. -
Check DOM structure: Open DevTools and run:
document.querySelector('article .markdown header')
If it returns
null, your theme has a different structure and may require swizzling for customization.
404 When Accessing .md URLs
-
Verify plugin execution: Check build logs for:
[markdown-source-plugin] Copying markdown source files... -
Check build output: Run:
find build -name "*.md" -type fIf no files appear, the plugin didn't run.
-
Verify plugin registration: Ensure the plugin is in the
pluginsarray indocusaurus.config.js.
Headers Not Being Sent
-
Verify configuration file: Ensure it's in the correct location for your platform.
-
Clear CDN cache: After deploying header changes, you may need to purge your CDN cache.
-
Test headers: Use curl to verify:
curl -I https://yourdomain.com/docs/page.md
Copy to Clipboard Not Working
-
Requires HTTPS: The Clipboard API only works on HTTPS or localhost.
-
Check browser support: Modern browsers support the Clipboard API, but very old browsers may not.
-
Check permissions: Some browsers require user permission for clipboard access.
Advanced Configuration
Custom Paths and Blog Support
The plugin currently supports the default /docs/ path out of the box.
For blog support or custom paths, you can swizzle the components if needed:
npm run swizzle docusaurus-markdown-source-plugin Root -- --eject
Note: Swizzling means you'll manually maintain these files and won't receive automatic updates. Native configuration support for custom paths is planned for a future release.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
MIT © FlyNumber
Support
- Issues: GitHub Issues
- Documentation: Implementation Guide
- Live Example: flynumber.com/docs
Related
- Docusaurus - The documentation framework this plugin extends
- FlyNumber - Cloud phone system and documentation platform
Made with ❤️ by FlyNumber
