Generate Multiple Pretty URL Presentations with Marp and JavaScript
Learn how to generate multiple Marp presentations from multiple Markdown files at once using marp-cli and a custom Node.js build script to produce HTML website with pretty URL, just like a static site generator.

I freaking love Markdown. I write almost everything in Markdown.
I write this blog post in Markdown. I chat with LLMs in Markdown. I write docs in Markdown. Everything is Markdown.
One time, I was invited as a guest speaker, and I was thinking, can I write a keynote presentation in Markdown? The answer is yes. That’s when I found out about Marp.
Now, I won’t go into the details of how to create a Markdown presentation with Marp. If you’re very familiar with Markdown, the official docs are actually pretty easy to follow.
What I want to write about today is how to generate multiple presentation files from multiple Markdown files in one go and output them as pretty URLs.
You see, Marp actually already provides a way to generate multiple HTML presentation files in one go with the following command:
npx @marp-team/marp-cli@latest \
--input-dir input_directory \
--output output_directory
But there’s something bothering me about this command: it doesn’t produce pretty URLs.
For example, if the input file is presentation.md, then the output file is presentation.html.
What I want is something similar to most static site generators like Jekyll or Hugo, where I have multiple Markdown files and it generates a ready-to-use website where every Markdown file is converted to HTML with pretty URLs.
So if the input is presentation.md, then the output should be presentation/index.html.
Then if I host it on my website, users won’t have to visit example.com/presentation.html, but instead they will visit example.com/presentation/.
See what I mean? So, here’s how I did it.
Generate Multiple Slides from Multiple Markdown Files with Marp
Let’s get started by creating our directory.
mkdir -p presentations
cd presentations
Now, because we use Markdown files for the presentations, why not use git too:
git init
Then let’s initialize with npm:
npm init
Let’s add our dev dependencies. We just need marp-cli for now.
npm install --save-dev @marp-team/marp-cli
Once the packages are installed, let’s update the package.json by changing the type to module and adding a scripts section:
{
/* ... */
"type": "module",
"scripts": {
"publish": "node build_marp_slides.js",
"serve": "npx http-server dist/"
},
/* ... */
}
Now before we create our script, let’s structure our Markdown files.
Create a new directory named content and put all your Markdown files there.
Here’s the structure from my GitHub Marp demo repo.
.
├── build_marp_slides.js
├── content
│ ├── basic
│ │ └── intro.md
│ ├── index.md
│ ├── marp.md
│ ├── oop.md
│ └── portfolio.md
├── package-lock.json
└── package.json
I recommend creating one index.md file at the root of content/ and linking all your presentations there.
Now, for the important part, create a new JavaScript file named build_marp_slides.js. This script will work like this:
- Build all Markdown files inside
content/into HTML presentations - Scan the
contentdirectory recursively - For each HTML file found, check its file name, create a directory, and rename the file to
index.htmlinside thedist/directory - The
dist/directory structure will follow thecontent/directory structure
And so, here’s the script. Just copy and paste:
#!/usr/bin/env node
import { marpCli } from '@marp-team/marp-cli';
import { existsSync, rmSync, mkdirSync, readdirSync, renameSync } from 'fs';
import { join, relative, dirname, basename } from 'path';
const SOURCE_DIR = './content';
const OUTPUT_DIR = './dist';
async function buildSlides() {
// Clean and prepare output
console.log('🧹 Cleaning output directory...');
if (existsSync(OUTPUT_DIR)) {
rmSync(OUTPUT_DIR, { recursive: true, force: true });
}
mkdirSync(OUTPUT_DIR, { recursive: true });
console.log(`🎞️ Generating all slides from '${SOURCE_DIR}'...`);
// Generate all HTML slides using marpCli with input/output directories
try {
const exitCode = await marpCli([
'--input-dir', SOURCE_DIR,
'--output', SOURCE_DIR
]);
if (exitCode !== 0) {
throw new Error(`Marp CLI exited with code ${exitCode}`);
}
} catch (error) {
console.error('❌ Error generating slides:', error.message);
process.exit(1);
}
// Find all generated .html files
function findHtmlFiles(dir) {
const files = [];
const items = readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = join(dir, item.name);
if (item.isDirectory()) {
files.push(...findHtmlFiles(fullPath));
} else if (item.isFile() && item.name.endsWith('.html')) {
files.push(fullPath);
}
}
return files;
}
const htmlFiles = findHtmlFiles(SOURCE_DIR);
// Move generated .html files into dist/
for (const htmlFile of htmlFiles) {
// Get relative path from SOURCE_DIR
const relPath = relative(SOURCE_DIR, htmlFile);
const dir = dirname(relPath);
const base = basename(relPath);
const name = basename(base, '.html');
// Normalize dir: if it's "." then make it empty
const normalizedDir = dir === '.' ? '' : dir;
let outDir;
if (name === 'index') {
// Keep index in the same directory inside dist
outDir = normalizedDir ? join(OUTPUT_DIR, normalizedDir) : OUTPUT_DIR;
} else {
// Create directory dist/<dir>/<name>/ and move there as index.html
outDir = normalizedDir
? join(OUTPUT_DIR, normalizedDir, name)
: join(OUTPUT_DIR, name);
}
mkdirSync(outDir, { recursive: true });
const destPath = join(outDir, 'index.html');
console.log(`🧩 Moving: ${htmlFile} → ${destPath}`);
renameSync(htmlFile, destPath);
}
console.log(`✅ All slides built successfully in '${OUTPUT_DIR}'`);
}
buildSlides().catch(error => {
console.error('❌ Build failed:', error);
process.exit(1);
});
Once the script and Markdown files are ready, you can build your presentations using this command:
npm run publish
A new dist/ directory will be generated containing all your presentations in HTML.
You can view them in your browser—just start the HTTP server with:
npm run serve
Here’s a demo I created and published to GitHub Pages.
Conclusion
That’s it for today.
If you have a Git repository with all of your Markdown presentation files, you can now build them all in a single go and produce a website with pretty URLs, just like a static site generator.
Now you can focus on writing and worry less about building and distributing.
As usual, if you have any questions or a better method, leave a comment below. Thanks for reading, and see you next time!