Welcome to the markdown to html blog documentation.
Credit where it’s due
This template uses Showdown, a JavaScript Markdown to HTML converter. I initially built a client-side project, which runs directly in the browser without a server.
While I completed that, it had some limitations, like the lack of dynamic link previews when a blog post link is shared on social media, as the data only renders in the browser. To address this, I created a Markdown-to-HTML converter script using Node.js, based on a templated design.
Before we begin, let me clarify a few things. I created all the JavaScript for this project with help from LLMs. It works, but it may not be up to the level of code written by an experienced developer. I'm sharing this as a personal blog template. Coders can cross-check and fix any problems. Non-coders can copy the JavaScript code into any LLMs to verify it themselves. If you need a different approach use any LLMs and make the appropriate changes. It is simple, if I can create this without any coding knowledge, you can too.
Resources needed
- VS Code
- GitHub
- GitHub Desktop
- Node.js
- Showdown library
npm i showdown
- js-yaml YAML 1.2 parser
npm i js-yaml
- Chokidar to see the live demo of drafts (optional)
npm i chokidar
- xml2js to cross check the files (optional)
npm i xml2js
- And of course Obsidian.
So, you need a blogging setup, right?
You should decide whether this is a good alternative or a starting point for your blogging journey. It can be a bit technical, but it's not difficult; if you read this documentation, you'll understand it.
Of course, you can skip all this hassle and just start a blog on any of the existing platforms, like Substack, Beehiiv, WordPress, or even Framer.
Every solution and alternative has its pros and cons. All we need to do is find what we can control, knowing we’re okay even without everything else.
The project is structured as follows:
blogspot/
├── assets/
│ ├── posts.json
│ └── favicon.svg
│ └── . . .
│ └── default-social-image.png
│ └── social-image.png
│ ├── 404.gif
├── posts/
│ ├── media
| │ ├── images
| │ ├── videos
| │ ├── audios
│ ├── category
| │ ├── post.md
| │ ├── post.md
│ ├── category
| │ ├── post.md
| │ ├── post.md
├── blog/
│ ├── media
| │ ├── images
| │ ├── videos
| │ ├── audios
│ ├── post.html
│ └── . . .
├── js/
│ └── copyLink.js
│ └── openExternalLinks.js
│ └── script.js
├── styles/
│ ├── main.css
│ └── post.css
│ └── icons.css
├── feed.xml
├── index.html
├── templates/
│ └── post.html
└── demo.js
└── build.js
└── sync.js
└── terms-and-policies.html
└── 404.html
└── robots.txt
└── build.log
How it works
You write your blog posts in Obsidian, which saves the files in md
format. These files should be stored in the posts
folder of your project which means open that folder as a vault. After writing your blog post, you need to edit the front matter metadata, which will help with your SEO. The front matter is the data enclosed in the format shown below. It is available in the markdown template I attached.
---
title: Your post title [Character Limit: 50-60 characters]
description: Your post description. Keep it within 160 characters for optimal visibility, as longer descriptions may be cut off in search results.
social_image: blog/media/images/image.png
author: Your name
published: 2024-12-27T16:00:00
updated:
category: doc
---
Leave the updated property empty if there’s nothing to add. If no social image is attached to a post, the default-social-image.png
will be applied automatically. You can replace it with your own image, but ensure it has a size of 1200x630 pixels or a 1.91:1 ratio.
Front matter
If you’re using Obsidian, you can switch between source mode and preview mode (While writing a post, go to top right, there's a more option
). In preview mode, editing the meta properties is more user-friendly. You can cross-check by switching to source mode to see it in pure YAML format.
npm install js-yaml
Installing js-yaml to accurately handle special characters in your blog post settings (like titles and descriptions), ensuring they appear correctly on your website and in your RSS feed.
Draft status
If you're drafting or working on multiple files, use _
before the filename to mark it as a draft (for eg; _documentation.md
). The script will recognize it as a draft and skip it, meaning it won’t be converted or added to the XML and JSON files.
Category
Create proper categories (folder structure inside the posts folder) and update in your front matter. And also make sure it matches the navigation menu. If it matches it will filter the posts in the all posts section. By default everything will be available at Blogging
tab. It is like All posts
. If you like to change this name, you need to edit the scripts too. Just use Ctr+F
in the JavaScript files or chat with any LLMs. Because I have not created that as a variable.
Live demo
In the root directory, run
node demo js
This will create a demo.html
file in the root directory, which you can open with the Live Server extension in VS Code. When done editing, type Ctrl+C
in the terminal to stop live watching, which will delete the demo.html
file. If you force close the terminal , demo.html
will remain.
Building
After finishing your posts, open a terminal in the blog’s root directory and run.
node build js
This will create a blog
folder containing HTML versions of all your Markdown files and copy the media folder as is. It will also generate posts.json
in the assets
folder and feed.xml
file (for RSS feed) in the root directory.
To pin a post, open posts.json
in a text editor and change "post_type": "regular"
to "post_type": "pinned"
. Only one pinned post is supported. From this point on, each build will retain the pin status, while other updates will be reflected.
If you need to delete an HTML post, manually delete the HTML file and remove its entry from both feed.xml
and posts.json
(both in the staging and production folders). This step is intentional to avoid accidental deletions. Running this script will also create a log file (build.log
) for cross checking with future builds.
Cross check and production sync
After you run the building script, you'll have all the necessary items to move to the local production folder. For making the process a little more easy I created one more script, but it is optional. You can either do everything manually or be careful and use the script.
This script performs a cross-check between JSON, XML, and HTML files to ensure consistency in blog post data. It also allows for synchronization of files and folders between a staging environment and a production environment.
- Cross check posts: Verifies that the number of posts and their titles match across JSON, XML, and HTML files.
- File and folder hashing: Calculates SHA-256 hashes for specified files and folders to detect changes.
- Files:
index.html
,404.html
,feed.xml
,terms-and-policies.html
,robots.txt
- Folders:
blog
,assets
,js
,styles
- Files:
- Production sync: Compares staging files and folders with production and provides an option to copy new or updated files to production.
Dependency: - xml2js
package.
npm install xml2js
After installing the above, run the script with this command in the root directory.
node sync js
This is the process for using this blog template. I’ve tried it several times, and it’s not as complicated as it sounds in the documentation.
Still here?
Think you can handle it? It’s not too tough! Then let’s get started.
To install essential libraries for Markdown, YAML, and XML processing: Showdown, JS-YAML, Chokidar, and XML2JS, run the below command.
npm install showdown js-yaml chokidar xml2js
- Create a staging folder for your blog and open it in VS Code:
File > Open Folder
. - Extract the project files from the downloaded zip and paste them into this folder.
- Open
index.html
in your code editor and launch it on the live server to see your blog. - Update the blog name, favicon, social images, navigation menu list, Telegram link, social links, and other personal details in
index.html
,post.html
,404.html
, andterms-and-policies.html
. Do not change file names and the paths. If you are comfortable with the code, go crazy. - If needed, customize the design. Fonts and colors are defined as variables.
- Set up your posts vault:
Open Obsidian > Manage vault > Open folder as vault
and select theposts
folder. - Create folders as categories and files as posts in Obsidian. This will help the script to build the JSON file appropriately.
- Ensure the front matter is correct before deciding a post is finished. It's essential for loading posts, listings, and SEO.
- Use the
supported-styles.md
file in the docs folder as a template. - Name your Markdown files clearly, as they’ll become your blog post URLs. Spaces will be replaced with hyphens, and all characters will be in lowercase. (Do not use any special characters in the file name because they may cause errors.)
- Place any images for your posts in the
posts/media/images
directory. Reference the images in your Markdown files usingmedia/images/image.avif
. Read why avif or webp?. But for social images, usejpg
orpng
. - You can also add buttons, iframe, video and audio. Place files in the appropriate folders inside the media folder and check the template for the code snippet. Be mindful of file sizes.
- Modify the
terms-and-policies.md
file in thelegal
folder before your first commit. You can build it separately and use the draft status to exclude it from future builds. Then, cut that file and replaceterms-and-policies.html
in the root folder. Make sure to delete this item's entry from your JSON and XML files. - Only change the the
updated
property if you're modifying a post. If you do, use the same format as the published date. GitHub tracks commit history, so use this feature carefully. - Each post needs a social image. If you don’t have one, create a default image (1200x630 or 1.91:1 ratio), compress it using Squoosh, convert it to
png
, and replacedefault-social-image.png
in theassets
folder. Do not rename the file. - In the
build.js
file, add your details for proper XML file creation. Open it, search forconst channelInfo
, and replace your details in all those fields (three separate blocks in total). - Also replace
const SOCIAL_IMAGE_BASE_URL
with the media/images folder of your live. This is to make sure the live preview loads the social image. - After the script generates
posts.json
andfeed.xml
, open them in VS Code. Pin your important post and ensure the RSS feed displays correct data and the latest post at the top. - Now you're ready to push this blog to your GitHub repository or anywhere you like.
- This step is optional, but it’s something I do for safety. Consider using separate folders for staging and production. After you’ve set up your blog, copy the folders
blog
,assets
,js
,styles
and files such asindex.html
,404.html
,feed.xml
,terms-and-policies.html
,robots.txt
. Create a new folder with your blog name and paste the copied project there. Or just run the scriptsync.js
in the root directory. - The original folder can be your staging area, containing all other build, demo scripts, posts folders and all other dependencies.
- Finally, open GitHub Desktop, verify the changes (especially if any metadata was updated), and push the commit.
- Make sure the repository is public to enable GitHub Pages.
- After your commit, go to the GitHub repository
Settings > Pages > Branch > Root
>Save
. - If the project is directly in the repository, your blog will be live at
username.github.io/repository/index.html
or add the remaining folders as created. - When you need to update or publish a new post, copy the new or updated HTML files (include the media folder if there are new files),
posts.json
, andfeed.xml
files to the production folder. Or simply run the script I mentioned above. - It’s a good practice to check the JSON and XML files—no bugs yet, but just in case.
- You can also sync the staging folder with any cloud using Syncthing. This way, you’ll have a local backup, a cloud backup, and a GitHub backup, which is handy if you’re working on draft files.
- You can change this to anything, just give the scripts and html files to any LLM and describe your needs, test and commit!
- In closing, my thanks to Google AI Studio for the majority of the code, Meta AI and ChatGPT for a few bug fixes, and Perplexity for the dummy text.
Enjoy your blog!
You can now start writing and publishing your blog posts. The template provides a clean design, along with the flexibility to customize it to your liking.
Important notes:
- I am a non-coder who uses LLMs (Claude, Mistral, Meta AI, ChatGPT, and Gemini) to generate scripts tailored to my needs. These resources are shared openly for anyone to use.
- Please keep in mind that I don't have formal coding knowledge, so the code structure, syntax, or best practices might not be optimal or conventional.
- I appreciate any feedback, suggestions, or contributions from experienced coders to improve these scripts and make them more efficient, readable, and maintainable.
I like working at start-ups and helping with open-source projects. I'm also excited about the future of digital creation - using natural language and visual development with emerging no-code tools. That'll be nice, I think. - Romio
Why markdown?
Read this https://stephango.com/file-over-app
Full disclosure
I built this with one goal in mind: users should have control over their files. I started thinking about this a long time ago. I believe users should own and control their content. Owning the code means you’re free to move whenever and wherever you like.
I currently run my blog on Substack, but one day, they accidentally disabled it due to a bug, saying my posts didn’t meet their policies. When I reached out, they said it was a bug and restored it in a few hours. But still, I lost that trust. If it happened once, it will happen again.
I just lost my Substack publication due to their spam and phishing policy. And I have no clue what policy I violated. Anyway, I raised a ticket. This is really a bad day. All those hours I spent writing all my thoughts and it all lost just like that!
— Romio (@romio.substack.com) June 18, 2024 at 1:31 PM
Since then, I’ve been taking backups every time I publish new posts. However, their export isn’t user-friendly; it’s provided only in HTML, which isn’t ideal and not styled. So, I wrote a script (with the help of LLMs) to convert that HTML to Markdown. You can access it here. The reason I’m still on Substack, despite having built this template, is that their export doesn’t include video and audio—only text and embedded images. Moving everything feels like a task. But if something like that happens again, I’d seriously consider leaving.
I specifically built this for GitHub Pages because I love the idea that anyone can code and share ideas in a simple format. Read 10,000 hours.
Explore more works in my GitHub repository or visit this link