Want a blog that builds itself from plain text, no CMS or messy admin panels?
In this guide you’ll build a static blog generator with Node.js that reads Markdown files and turns them into clean HTML pages.
Step by step we’ll show how to load files, parse Markdown, inject templates, sort posts by date, and write the output to a public folder with a single npm command.
By the end you’ll have a reusable tool and real Node.js skills like fs, library integration, and build automation.
Ready to build?
Overview of the Markdown Blog Generator

You’re going to build a working static blog generator that reads Markdown files and turns them into clean HTML pages. This project lives in Node.js and automates the entire process. No manual copy-paste, no CMS, just source files and a build command.
Static site generators are perfect for blogs because they’re fast, secure, and easy to host. You write posts in Markdown, run a script, and your blog’s ready to deploy. Node.js handles all the file reading, converting, and writing behind the scenes.
By the end of this project, you’ll have a generator that reads all Markdown files from a content folder, converts Markdown into HTML using a parser library, injects that HTML into reusable templates, writes finished pages to a public or dist folder, sorts posts by date so the newest appears first, and runs automatically with a single npm command.
This generator teaches you real-world Node.js skills. You’ll work with the file system (fs module), string manipulation, library integration, and build automation. You’ll see how static site generators work under the hood, which helps whether you’re customizing one later or building tools for other projects.
Node.js is a natural fit for this kind of automation because it’s built for reading and writing files quickly. You can process hundreds of Markdown files in a second, and you can run the same script locally or in a CI pipeline. Once you understand the flow (read, parse, template, write) you can adapt it to documentation sites, project portfolios, or any content that starts as text and ends as a website.
Project Setup and Environment Preparation

Before you write any code, you need Node.js installed and a clean project folder. Check your Node version by opening a terminal and running node -v. You should see something like v18 or higher. If Node isn’t installed, download it from the official site.
Here’s how to set up the project.
Create a new folder called markdown-blog-generator (or any name you like). Open a terminal in that folder and run npm init -y. This creates a package.json file with default settings. Open package.json and add "type": "module" so you can use modern import statements instead of require. Verify everything works by running node --version again. If you see a version number, you’re ready.
Now organize your project with a clear directory structure. You’ll need a content/ folder that stores all your Markdown posts (e.g., my-first-post.md, another-post.md). A templates/ folder holds HTML template files for headers, footers, and page layouts. The public/ or dist/ folder is the output folder where finished HTML files will be written. An optional src/ folder can hold your build script and helper functions. Your package.json tracks dependencies and npm scripts. And build.js or index.js is the main script that runs the generator.
A consistent structure keeps things simple as your project grows. When you know exactly where posts live and where output goes, you can write reliable build scripts. It also makes debugging faster because you’re not hunting through random folders for a file. If you share this project or come back to it months later, a clear layout means you can start working immediately.
Reading and Loading Markdown Files

Your generator needs to find every Markdown file in the content folder and read the raw text. Node.js includes the fs (file system) module for this. You’ll use fs.readdirSync to list all files in a directory and fs.readFileSync to grab the contents of each one.
Loading Files with the fs Module
Start by importing the fs module at the top of your build script. import fs from 'fs'. Then call fs.readdirSync('content/') to get an array of filenames. Loop through that array and use fs.readFileSync to read each file as a string. Filter the list to only include files ending in .md so you skip any stray files that aren’t posts.
When working with files, keep these things in mind. Always check that the content folder exists before reading it (use fs.existsSync). Filter filenames by extension so you only process .md files. Handle encoding explicitly by passing 'utf-8' to readFileSync. Store each file’s content in an object along with its filename for later use. Normalize line endings if you’re working across Windows and macOS. Consider sorting the file list alphabetically or by date to control output order.
Once you’ve loaded all the Markdown files, you’ll have an array of objects that might look like [{ filename: 'post-one.md', content: '# My Post\nHello world...' }, ...]. That array becomes the input for the next step. Each filename can be used to generate a clean URL (strip the .md and create a folder), and the content is what you’ll parse into HTML. If you want to sort posts by date, you’ll need to extract a date from frontmatter (a metadata block at the top of each file) and compare those values before rendering.
Parsing Markdown into HTML

Raw Markdown is easy for humans to write but browsers need HTML. A parser library reads Markdown syntax like # Heading or **bold** and outputs the equivalent HTML tags. The most common library for this in Node.js is called marked.
How Markdown Parsers Work
Markdown parsers scan text line by line, looking for patterns. When they see # Heading, they return <h1>Heading</h1>. When they see a list with dashes, they return <ul><li> tags. Some parsers also handle code blocks, tables, and links automatically.
Here’s how to install and use marked.
Install marked by running npm install marked in your terminal. Import it at the top of your build script: import { marked } from 'marked'. Pass your Markdown content string into marked: const html = marked(content). The return value is a complete HTML string ready to inject into a template. If you want syntax highlighting for code blocks, install highlight.js and configure marked to use it (check the marked docs for the renderer option). Test the output by logging html to the console and checking that headings, lists, and links render correctly. Store the HTML in your post object so you can access it later when building pages.
Marked is fast and handles most standard Markdown features. If your posts include code snippets, you can wire up highlight.js so code blocks get color syntax highlighting. You create a small configuration file (like marked.js) where you tell marked to use highlight.js whenever it sees a fenced code block. That config might look like this:
import { marked } from 'marked';
import hljs from 'highlight.js';
marked.setOptions({
highlight: function(code, lang) {
return hljs.highlightAuto(code).value;
}
});
Once you’ve converted Markdown to HTML, you have a string of tags that can be dropped into a template. The parser doesn’t care about page layout or styling. It just translates syntax. You’ll handle layout in the next step by wrapping this HTML in header, footer, and navigation elements. Keep the parser focused on one job: turning Markdown into HTML. That separation makes your build script easier to read and test.
Creating HTML Templates

Templates are the skeleton of your blog. They define the header, navigation, footer, and the spot where your post content gets injected. You can use a full template engine like EJS or Pug, or you can keep it simple with JavaScript template literals (backtick strings with ${} placeholders).
A typical blog template needs a <head> section with page title, meta tags, and a link to your CSS file. A header with your blog name or logo. A navigation bar or menu. A main content area where the post HTML will be inserted. A footer with copyright, links, or author info. And an optional sidebar for archives, tags, or an about section.
Simple String-Based Templating
If you want to avoid extra dependencies, use template literals. Create a function that takes a post object and returns a complete HTML string. Inside that function, write your HTML structure using backticks and insert post data with ${post.title} or ${post.html}. For example:
function postTemplate(post) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${post.title}</title>
<link rel="stylesheet" href="/assets/styles/main.css">
</head>
<body>
<header>
<h1>My Blog</h1>
</header>
<main>
<article>
<h2>${post.title}</h2>
<time>${post.date}</time>
${post.html}
</article>
</main>
<footer>
<p>© 2025 My Blog</p>
</footer>
</body>
</html>
`;
}
That function takes a post object with properties like title, date, and html (the converted Markdown). It returns a full HTML page as a string. You can call this function for every post and write the result to a file.
Templates improve maintainability because you write the layout once and reuse it everywhere. If you decide to change the header design, you update one template function instead of editing every HTML file. Template literals are simple and built into JavaScript, so you don’t need to learn a new syntax or install another library. If your project grows and you want more features like partials or loops, you can switch to a library like lit-html or mustache later without rewriting your content.
Building the Static Blog Generator

Now you’ll wire everything together into a build script that reads Markdown files, converts them to HTML, injects them into templates, and writes the final pages to a public folder. This is the core automation that turns your content into a working site.
Start by creating a file called build.js in your project root. Import the modules you need (fs, marked, and any helper functions). Then define the main build flow.
Combining Markdown Conversion and Templates
Read all the Markdown files from your content folder using fs.readdirSync. For each file, read the content with fs.readFileSync, parse it with marked, and pass the resulting HTML into your template function. If your posts have frontmatter (metadata at the top of the file between --- markers), use a library like gray-matter to extract that first. Frontmatter might include a title, date, or description. Gray-matter splits the file into two parts: attributes (the metadata) and body (the Markdown content). You convert the body to HTML and pass both the attributes and HTML to your template.
Generating Output Files
Once you have a complete HTML string for a post, you need to write it to the public folder. Create a folder for each post (e.g., public/my-first-post/) and write an index.html file inside it. That way, the URL becomes yoursite.com/my-first-post/ instead of yoursite.com/my-first-post.html. Use fs.mkdirSync to create the folder (pass { recursive: true } so it won’t error if the folder already exists) and fs.writeFileSync to write the HTML file. Repeat this for every post.
You’ll also want to generate a homepage that lists all your posts. Create a separate template for the homepage that loops through your post array and renders a list of titles, dates, and links. Write that to public/index.html.
Automating the Build Script
Add an npm script to package.json so you can run the generator with a simple command. Open package.json and add this to the “scripts” section:
"scripts": {
"build": "node build.js"
}
Now you can run npm run build in your terminal and the entire site will be generated. If you want to watch for changes and rebuild automatically, you can use a tool like nodemon. Install it with npm install --save-dev nodemon, then add a “dev” script that runs nodemon build.js. Every time you save a file, the site rebuilds.
The complete generation flow looks like this: read all Markdown files, parse frontmatter to extract metadata, convert Markdown body to HTML, inject HTML and metadata into a template, write each post to its own folder as index.html, generate a homepage with a list of all posts, copy static assets (CSS, images) to the public folder.
Once the build finishes, you have a public folder full of HTML files ready to deploy. You can open public/index.html in a browser to see the homepage and click through to individual posts. If everything looks right, you’re done. If something’s broken, check the console for errors and verify that your paths and filenames are correct.
Example Output and Testing

After running your build script, you should see a public or dist folder with a clear structure. Each post gets its own directory containing an index.html file, and the homepage lives at the root.
| File Name | Purpose | Status |
|---|---|---|
| public/index.html | Homepage with list of all posts | ✓ Generated |
| public/my-first-post/index.html | Individual post page | ✓ Generated |
| public/another-post/index.html | Individual post page | ✓ Generated |
| public/assets/styles/main.css | Stylesheet for all pages | ✓ Copied |
Open public/index.html in a browser. You should see your blog name at the top, a list of post titles with dates, and clickable links. Click one of the links. The post page should load with the full content rendered as HTML. Headings, paragraphs, code blocks, links, all formatted correctly.
Check the browser console for errors. If you see 404s, your asset paths might be wrong (make sure CSS links start with /assets/ not assets/). If the Markdown didn’t convert, check that marked is installed and imported correctly.
Verify that post dates appear in the right format and that the newest post is listed first. If your build script sorts posts by date, confirm that the order matches what you expect. Open the HTML files in a text editor and scan for broken tags or missing closing tags. Browsers are forgiving but it’s better to catch malformed HTML early.
If everything renders correctly, test a few edge cases. Posts with code blocks, posts with images, posts with very long titles. If the layout breaks, adjust your CSS or template structure. Once all your test posts look right, you’re ready to add real content or deploy the site.
Using and Expanding the Blog Generator

You’ve built a working generator, but it’s intentionally minimal. Now you can add features that fit your needs. The core structure stays the same (read, parse, template, write) but you can layer on extra functionality.
Here are common enhancements to consider. Add a tags or categories system by parsing frontmatter and creating tag pages that list related posts. Implement pagination so the homepage shows 10 posts per page instead of all of them. Generate an RSS feed by looping through posts and writing an XML file. Create a sitemap.xml for search engines by listing every post URL. Add a search feature using a client-side library like Fuse.js. Support multiple layouts by letting each post specify a layout in frontmatter. Include post excerpts or summaries on the homepage (grab the first paragraph or a custom excerpt field). Add social share buttons or Open Graph meta tags for better link previews.
Each feature starts small. For tags, you’d read the tags field from frontmatter, group posts by tag, and generate a page per tag. For pagination, you’d slice your post array into chunks and write multiple index pages. For RSS, you’d loop through posts and write an XML file following the RSS spec. None of these require changing the core build script. You add new functions and call them after the main generation step.
Start with one feature at a time. Build tags before pagination. Build pagination before search. Test each addition before moving to the next. If something breaks, you’ll know exactly which change caused it. Keep the build script organized with helper functions. One function to generate posts, one for the homepage, one for tags, one for RSS. That way you can test each part independently and swap pieces out later if you want to try a different approach. The best static site generator is one you actually use and maintain, so pick features that matter for your blog and skip the rest.
Final Words
You wired up a simple pipeline: read Markdown, convert it to HTML, inject content into templates, and write the final files to /dist. You set up Node.js, used fs to load files, picked a parser, and automated the build script.
You also practiced testing and saw how to add features like tags, pagination, or RSS. If anything feels fuzzy, rerun the steps and check the small sections one at a time.
Now go build a markdown blog generator with Node.js step by step — you’ve got a working foundation and room to make it yours.
FAQ
Q: What is Markdown Blog?
A: The Markdown Blog is a website built from plain-text Markdown files converted to HTML, letting you write posts in Markdown, automate builds, and publish lightweight static pages.
Q: What is an alternative to Jekyll static site generator?
A: An alternative to Jekyll is Eleventy or Hugo; Eleventy is JavaScript-friendly and Markdown-first, while Hugo is Go-based and very fast for large sites.
Q: Does Netflix still use NodeJS? Is Node.js good for websites?
A: Netflix still uses Node.js for parts of its stack, and Node.js is good for websites that need non-blocking I/O, fast build tooling, real-time features, or server-side JavaScript.

