X Xerobit

Markdown to HTML Conversion — marked, markdown-it, and Showdown

Convert Markdown to HTML in JavaScript using marked, markdown-it, and Showdown. Includes syntax highlighting with highlight.js, sanitizing output, custom renderers, and...

Mian Ali Khalid · · 5 min read
Use the tool
Markdown Preview
Live Markdown preview with GitHub-flavored syntax. Tables, task lists, code blocks, strikethrough. Side-by-side editor and rendered output.
Open Markdown Preview →

Converting Markdown to HTML requires a parser library — the choice depends on whether you need speed, extensibility, security, or specific GFM features.

Use the Markdown Preview to render Markdown instantly in the browser.

marked — fast and simple

import { marked } from 'marked'; // npm install marked

const markdown = `# Hello World\n\nThis is **bold** and *italic*.`;

// Basic conversion:
const html = marked.parse(markdown);
// <h1>Hello World</h1><p>This is <strong>bold</strong> and <em>italic</em>.</p>

// Async version (if using extensions):
const html = await marked.parseInline(markdown);

markdown-it — extensible and safe

import MarkdownIt from 'markdown-it'; // npm install markdown-it

const md = new MarkdownIt({
  html: false,        // Disable raw HTML (safer)
  xhtmlOut: false,
  breaks: false,      // \n becomes <br>
  linkify: true,      // Auto-link URLs
  typographer: true,  // Smart quotes, em-dashes
});

const html = md.render('# Hello\n\nSome **text**.'); 

// Enable GFM tables and strikethrough:
import mdTaskLists from 'markdown-it-task-lists';
import mdMark from 'markdown-it-mark'; // ==highlight==

md.use(mdTaskLists, { enabled: true });
md.use(mdMark);

Syntax highlighting with highlight.js

import MarkdownIt from 'markdown-it';
import hljs from 'highlight.js'; // npm install highlight.js

const md = new MarkdownIt({
  highlight: function(code, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return '<pre><code class="hljs">' +
          hljs.highlight(code, { language: lang, ignoreIllegals: true }).value +
          '</code></pre>';
      } catch {}
    }
    return '<pre><code class="hljs">' +
      hljs.highlightAuto(code).value +
      '</code></pre>';
  },
});

// Add highlight.js CSS to your page:
// import 'highlight.js/styles/github.css';

Sanitizing HTML output (security)

Never render untrusted Markdown without sanitization — a user could inject <script> tags:

import { marked } from 'marked';
import DOMPurify from 'dompurify'; // npm install dompurify

// Client-side:
function safeMarkdown(markdown) {
  const rawHtml = marked.parse(markdown);
  return DOMPurify.sanitize(rawHtml, {
    ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
                   'strong', 'em', 'code', 'pre', 'blockquote',
                   'ul', 'ol', 'li', 'a', 'img', 'table',
                   'thead', 'tbody', 'tr', 'th', 'td', 'hr', 'br'],
    ALLOWED_ATTR: ['href', 'src', 'alt', 'class', 'title'],
  });
}

// Server-side: use 'sanitize-html' instead of DOMPurify
import sanitizeHtml from 'sanitize-html'; // npm install sanitize-html

function safeMarkdownSSR(markdown) {
  const rawHtml = marked.parse(markdown);
  return sanitizeHtml(rawHtml, {
    allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'h1', 'h2']),
    allowedAttributes: { 'a': ['href', 'title'], 'img': ['src', 'alt'] },
  });
}

Custom renderers in marked

import { marked } from 'marked';

// Override how specific elements render:
const renderer = {
  // Add target="_blank" and rel to external links:
  link(href, title, text) {
    const isExternal = href.startsWith('http');
    const attrs = isExternal
      ? ` target="_blank" rel="noopener noreferrer"`
      : '';
    return `<a href="${href}"${title ? ` title="${title}"` : ''}${attrs}>${text}</a>`;
  },
  
  // Add ID to headings for anchor links:
  heading(text, level) {
    const id = text.toLowerCase().replace(/[^a-z0-9]+/g, '-');
    return `<h${level} id="${id}">${text}</h${level}>`;
  },
  
  // Lazy-load images:
  image(href, title, text) {
    return `<img src="${href}" alt="${text}" loading="lazy"${title ? ` title="${title}"` : ''}>`;
  },
};

marked.use({ renderer });

React: render Markdown safely

import { marked } from 'marked';
import DOMPurify from 'dompurify';

function MarkdownRenderer({ content }) {
  const html = DOMPurify.sanitize(marked.parse(content));
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

// Or use react-markdown (no dangerouslySetInnerHTML needed):
// npm install react-markdown
import ReactMarkdown from 'react-markdown';

function BlogPost({ content }) {
  return <ReactMarkdown>{content}</ReactMarkdown>;
}

Node.js: convert markdown files

import { marked } from 'marked';
import { readFile, writeFile } from 'fs/promises';
import { glob } from 'glob';

async function convertAll() {
  const files = await glob('content/**/*.md');
  
  for (const file of files) {
    const markdown = await readFile(file, 'utf-8');
    const html = marked.parse(markdown);
    const outFile = file.replace('.md', '.html');
    await writeFile(outFile, `<!DOCTYPE html><html><body>${html}</body></html>`);
    console.log(`Converted: ${file} → ${outFile}`);
  }
}

convertAll();

Library comparison

LibrarySpeedGFMExtensionsSize
markedVery fastYesLimited~43KB
markdown-itFastWith pluginsExcellent~60KB
showdownModeratePartialGood~58KB
unified/remarkModerateYesExcellentLarge
micromarkFastestYesComposable~25KB

Related posts

Related tool

Markdown Preview

Live Markdown preview with GitHub-flavored syntax. Tables, task lists, code blocks, strikethrough. Side-by-side editor and rendered output.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.