All posts
javascript6 min read

Automating Content Publishing: Building an MDX to Dev.to Converter Script

Learn how I automated the process of converting MDX blog posts to dev.to format, saving hours of manual work and ensuring consistency across platforms.

Introduction

As a developer who maintains a personal blog, I found myself constantly republishing content across multiple platforms. My main blog runs on Next.js with MDX, but I also wanted to share my posts on dev.to to reach a broader audience.

The problem? Each platform has different formatting requirements. My MDX posts use custom React components like LgList, Accent, HighlightBox, and Link, which dev.to doesn't support. Manually converting each post was time-consuming and error-prone.

So I built an automated conversion script that transforms my MDX posts into dev.to-compatible Markdown. In this post, I'll walk you through the solution, the implementation, and how you can use it for your own content.

The Problem

When you maintain a blog on multiple platforms, you face several challenges:

  • Formatting differences - Each platform supports different markdown features and HTML tags
  • Time consumption - Manually converting posts takes hours, especially for longer articles
  • Consistency issues - It's easy to miss converting some components or make formatting mistakes
  • Maintenance overhead - Every time you update a post, you need to update it on all platforms

In my case, I had custom MDX components that needed to be converted to standard HTML/Markdown:

Build output

  • LgList → standard <ul> lists
  • Link → markdown links text
  • Accent → bold text text
  • HighlightBox → styled <div>
  • EmbeddedLink → markdown links
  • Adjusted → inline code

The Solution

I created a Node.js script that:

  1. Parses MDX frontmatter - Extracts metadata using gray-matter
  2. Converts custom components - Replaces all custom components with standard HTML/Markdown
  3. Transforms tags - Converts tag arrays to comma-separated strings for dev.to
  4. Adds footer - Automatically appends a footer with links to my personal blog
  5. Generates output - Creates a new .md file ready for dev.to

Implementation

Let's dive into the code. The script uses Node.js with the gray-matter package for parsing frontmatter:

const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');

const DEVTO_FOOTER = `---

**If you found this post useful, check out more articles on my [personal blog](https://www.jageticbojan.com/routes/blog) where I write about Nodejs, React, TypeScript, and web development in general!**

You can also check out my [GitHub](https://github.com/basskibo) for more projects and open source contributions.`;

Component Conversion

The core of the script is the convertComponents function that uses regex to replace custom components:

function convertComponents(content) {
  let converted = content;

  // Replace <LgList> with <ul>
  converted = converted.replace(/<LgList>/g, '<ul>');
  converted = converted.replace(/<\/LgList>/g, '</ul>');

  // Replace <Link href="url">text</Link> with [text](url)
  converted = converted.replace(
    /<Link\s+href=["']([^"']+)["']>([^<]+)<\/Link>/g, 
    '[$2]($1)'
  );

  // Replace <Accent>text</Accent> with **text** (bold)
  converted = converted.replace(
    /<Accent([^>]*)>([^<]+)<\/Accent>/g, 
    '**$2**'
  );

  // Replace <HighlightBox> with styled div
  converted = converted.replace(
    /<HighlightBox>/g,
    '<div style="margin: 1rem 0; padding: 1rem; border: 1px solid #333; border-radius: 0.5rem; background: #1a1a1a;">'
  );
  converted = converted.replace(/<\/HighlightBox>/g, '</div>');

  // Replace <EmbeddedLink url="url" /> with markdown link
  converted = converted.replace(
    /<EmbeddedLink\s+url=["']([^"']+)["']\s*\/>/g, 
    '[$1]($1)'
  );

  // Replace <Adjusted>text</Adjusted> with inline code
  converted = converted.replace(
    /<Adjusted>([^<]+)<\/Adjusted>/g, 
    '`$1`'
  );

  return converted;
}

Frontmatter Parsing

The script uses gray-matter to parse frontmatter and convert tags:

function parseFrontmatter(content) {
  try {
    const parsed = matter(content);
    const frontmatter = parsed.data || {};
    const body = parsed.content;

    // Convert tags array to comma-separated string
    if (frontmatter.tags && Array.isArray(frontmatter.tags)) {
      const tagNames = frontmatter.tags
        .map(tag => {
          if (typeof tag === 'object' && tag.name) {
            return tag.name;
          } else if (typeof tag === 'object' && tag.slug) {
            return tag.slug;
          } else if (typeof tag === 'string') {
            return tag;
          }
          return null;
        })
        .filter(Boolean);
      frontmatter.tags = tagNames.join(', ');
    }

    return { frontmatter, body };
  } catch (error) {
    console.warn('Error parsing frontmatter:', error.message);
    return { frontmatter: {}, body: content };
  }
}

Main Conversion Function

The main function ties everything together:

function convertToDevTo(inputPath, outputPath = null) {
  try {
    // Read the MDX file
    const content = fs.readFileSync(inputPath, 'utf-8');

    // Parse frontmatter and body
    const { frontmatter, body } = parseFrontmatter(content);

    // Convert components in body
    const convertedBody = convertComponents(body);

    // Generate dev.to frontmatter
    const devtoFrontmatter = {
      title: frontmatter.title || '',
      published: 'false', // Set to true when ready to publish
      tags: frontmatter.tags || '',
    };

    // Format frontmatter as YAML
    const frontmatterYaml = formatFrontmatter(devtoFrontmatter);

    // Combine everything
    const devtoContent = [
      frontmatterYaml,
      '',
      convertedBody,
      DEVTO_FOOTER
    ].join('\n');

    // Determine output path
    const finalOutputPath = outputPath || 
      inputPath.replace(/\.mdx$/, '-devto.md');

    // Write the converted file
    fs.writeFileSync(finalOutputPath, devtoContent, 'utf-8');

    console.log(`✅ Successfully converted: ${inputPath}`);
    console.log(`📄 Output: ${finalOutputPath}`);
    return finalOutputPath;
  } catch (error) {
    console.error(`❌ Error converting ${inputPath}:`, error.message);
    throw error;
  }
}

Usage

The script can be used in two ways:

Command Line

Add it to your package.json:

{
  "scripts": {
    "convert-to-devto": "node scripts/convert-to-devto.js"
  }
}

Then run:

npm run convert-to-devto posts/blog/my-post.mdx

Or directly:

node scripts/convert-to-devto.js posts/blog/my-post.mdx

API Endpoint

I also created a Next.js API endpoint for web-based conversion:

// pages/api/convert-to-devto.js
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const { postPath } = req.body;
  const resultPath = convertToDevTo(postPath);
  const convertedContent = fs.readFileSync(resultPath, 'utf-8');

  return res.status(200).json({
    success: true,
    outputPath: resultPath,
    content: convertedContent,
  });
}

Results

This automation has saved me hours of manual work. Here's what I gained:

  • Time savings - Converting a post now takes seconds instead of 15-30 minutes
  • Consistency - All posts are converted the same way, reducing errors
  • Maintainability - When I update the script, all future conversions benefit
  • Scalability - I can convert multiple posts at once with a simple loop

Extending the Script

The script is easily extensible. You can add support for:

  • More platforms - Add converters for Medium, Hashnode, or other platforms
  • Custom transformations - Add platform-specific formatting rules
  • Batch processing - Convert multiple posts at once
  • Validation - Check for missing images or broken links before publishing

Here's an example of how to extend it for batch processing:

function convertMultiplePosts(postsDir) {
  const files = fs.readdirSync(postsDir);
  const mdxFiles = files.filter(f => f.endsWith('.mdx'));

  mdxFiles.forEach(file => {
    const inputPath = path.join(postsDir, file);
    convertToDevTo(inputPath);
  });

  console.log(`✅ Converted ${mdxFiles.length} posts`);
}

Conclusion

Automating repetitive tasks like content conversion is one of the best investments you can make as a developer. This script not only saves time but also ensures consistency and reduces errors.

The key takeaways:

  • Identify repetitive tasks in your workflow
  • Start with a simple script that solves 80% of the problem
  • Iterate and improve based on your needs
  • Share your solutions - others might find them useful too!

If you maintain a blog on multiple platforms, I highly recommend building a similar automation. The time investment pays off quickly, and you'll have more time to focus on writing great content instead of manual formatting.

You can find the complete script in my GitHub repository. Feel free to fork it, modify it for your needs, or use it as inspiration for your own automation projects.

Happy automating! 🚀