Welcome to this comprehensive guide on how to host your own database-free blog using JavaScript and Markdown files. This tutorial is designed for non-technical users who want a simple and efficient way to start blogging without dealing with complex setups.
Introduction
This guide will help you create a simple blogging platform using JavaScript and Markdown. You’ll write your blog posts in Markdown files, and a custom Node.js script will generate a static website. We’ll use GitHub for storing your code and content, and Cloudflare Pages for hosting your site. This solution requires minimal technical knowledge and is suitable for non-technical users.
Prerequisites
- GitHub Account: Sign up at GitHub if you don’t have one.
- Node.js and npm (Optional): Required if you plan to run the generation script locally. Download from nodejs.org.
- Cloudflare Account: Sign up at Cloudflare for hosting.
Project Overview
-
Tools Used:
- JavaScript (Node.js): For running the script that generates the site.
- Markdown: For writing blog posts.
- GitHub: To store your site’s code and content.
- Cloudflare Pages: To host your site.
-
Project Structure:
plaintextmy-blog/ ├── authors/ │ └── (author markdown files) ├── posts/ │ └── (blog post markdown files) ├── public/ │ └── (static assets like CSS and images) ├── src/ │ └── templates/ │ ├── index.html │ ├── post.html │ ├── author.html │ ├── tag.html │ ├── category.html │ └── 404.html ├── config.json ├── generate.js ├── package.json └── README.md`
Step-by-Step Guide
Step 1: Set Up GitHub Repository
1.1 Create a GitHub Account
- Go to GitHub and sign up for an account if you haven’t already.
1.2 Create a New Repository
- Log in to GitHub.
- Click on the “+” icon in the top-right corner and select “New repository”.
- Repository Name:
my-blog - Description: “A simple blog built with JavaScript and Markdown.”
- Public: Select Public.
- Initialize Repository: Check “Add a README file”.
- Click “Create repository”.
Step 2: Prepare the Project Structure
We’ll create the necessary folders and files directly in GitHub.
2.1 Create Folders
-
In your repository, click on “Add file” > “Create new file”.
-
To create a folder, type the folder name followed by a
/and then the file name. -
Create the following folders by adding a placeholder file (
.gitkeepor a README):authors/.gitkeepposts/.gitkeeppublic/.gitkeepsrc/templates/.gitkeep
2.2 Create Essential Files
-
config.json-
File Path:
config.json -
Content:
json{ "siteTitle": "My Blog", "siteDescription": "A simple blog built with JavaScript and Markdown", "footerText": "© 2023 My Blog", "siteUrl": "https://your-site.pages.dev" // Update after deploying }
-
-
generate.js- File Path:
generate.js - Content: We’ll add the code in Step 6.
- File Path:
-
package.json-
File Path:
package.json -
Content:
-
{
"name": "my-blog",
"version": "1.0.0",
"description": "A simple blog built with JavaScript and Markdown",
"main": "generate.js",
"scripts": {
"generate": "node generate.js"
},
"dependencies": {
"lunr": "^2.3.9",
"markdown-it": "^12.0.6",
"markdown-it-anchor": "^8.4.1",
"markdown-it-table-of-contents": "^0.4.4",
"rss": "^1.2.2"
}
}
Step 3: Write a Sample Blog Post
We’ll create a sample blog post with the provided header.
3.1 Create a Blog Post
-
Navigate to the
postsfolder. -
Click “Add file” > “Create new file”.
-
File Name:
step-by-step-guide.md -
Content:
title: Step BY STEP guide for hosting database free blog
date: 2023-11-13
tags: [Introduction, tutorial, Technology, guide]
categories: [Personal, Blogging]
author: Unknown
excerpt: "Learn how to host a simple, database-free blog using JavaScript and Markdown."
# Step BY STEP Guide for Hosting a Database-Free Blog
Welcome to this comprehensive guide on how to host your own database-free blog using JavaScript and Markdown files. This tutorial is designed for non-technical users who want a simple and efficient way to start blogging without dealing with complex setups.
## Introduction
In this guide, we'll walk you through setting up a static blog that uses Markdown files for content and a simple Node.js script to generate your site. You'll use GitHub for content management and Cloudflare Pages for hosting.
## Prerequisites
- A GitHub account
- Basic understanding of Markdown (optional)
## Steps Overview
1. **Set Up a GitHub Repository**
- Create a new repository to store your blog's code and content.
2. **Prepare the Project Structure**
- Organize your files and directories as required.
3. **Write Your Blog Posts**
- Use Markdown to create content for your blog.
4. **Deploy to Cloudflare Pages**
- Host your static site for free with Cloudflare Pages.
## Detailed Steps
(Include the detailed steps here or refer to the guide.)
## Conclusion
By following this guide, you'll have a fully functional, database-free blog up and running in no time. Happy blogging!`
5. Click **"Commit new file"** to save.
Step 4: Create Templates
Templates are HTML files with placeholders that will be replaced by actual content during site generation.
4.1 Create index.html Template
-
File Path:
src/templates/index.html -
Content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ siteTitle }}</title>
<meta name="description" content="{{ siteDescription }}">
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<header>
<h1>{{ siteTitle }}</h1>
</header>
<main>
<ul>
{{ postList }}
</ul>
</main>
<footer>
{{ footerText }}
</footer>
</body>
</html>
4.2 Create post.html Template
-
File Path:
src/templates/post.html -
Content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }} - {{ siteTitle }}</title>
<meta name="description" content="{{ excerpt }}" />
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header>
<h1><a href="/">{{ siteTitle }}</a></h1>
</header>
<main>
<article>
<h2>{{ title }}</h2>
<p>By <a href="/authors/{{ authorSlug }}.html">{{ author }}</a> on {{ date }}</p>
<div>{{ content }}</div>
<p>Tags: {{ tags }}</p>
<p>Categories: {{ categories }}</p>
</article>
<nav>
{{ prevPost }}
{{ nextPost }}
</nav>
</main>
<footer>
{{ footerText }}
</footer>
</body>
</html>
4.3 Create Additional Templates
-
author.html-
File Path:
src/templates/author.html -
Content:
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>About {{authorName}}</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
<main>
<h1>{{authorName}}</h1>
<img src="{{authorImage}}" alt="{{authorName}}">
<p>{{authorBio}}</p>
<h2>Posts by {{authorName}}</h2>
<ul>
{{postList}}
</ul>
</main>
<footer>
<p>© 2024 Lava Kumar</p>
</footer>
</body>
</html>
-
tag.html-
File Path:
src/templates/tag.html -
Content:
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Posts tagged "{{tag}}"</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<header>
<h1>Posts tagged "{{tag}}"</h1>
<nav>
<a href="/">Home</a>
</nav>
</header>
<main>
<ul>
{{postList}}
</ul>
</main>
<footer>
<p>© 2024 Lava Kumar</p>
</footer>
</body>
</html>
-
category.html-
File Path:
src/templates/category.html -
Content:
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Posts in category "{{category}}"</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<header>
<h1>Posts in category "{{category}}"</h1>
<nav>
<a href="/">Home</a>
</nav>
</header>
<main>
<ul>
{{postList}}
</ul>
</main>
<footer>
<p>© 2024 Lava Kumar</p>
</footer>
</body>
</html>
-
404.html-
File Path:
src/templates/404.html -
Content:
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Page Not Found</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<main>
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<a href="/">Back to Home</a>
</main>
</body>
</html>
Step 5: Add Configuration File
We already added config.json in Step 2.2.
Step 6: Add the Generation Script
We’ll now add the generate.js script that reads the Markdown files, processes them, and generates the static site.
6.1 Create generate.js
-
File Path:
generate.js -
Content:
const fs = require('fs');
const path = require('path');
const MarkdownIt = require('markdown-it');
const mdAnchor = require('markdown-it-anchor');
const mdTOC = require('markdown-it-table-of-contents');
const lunr = require('lunr');
const RSS = require('rss');
// Load configuration
const config = JSON.parse(fs.readFileSync('config.json', 'utf-8'));
const md = new MarkdownIt()
.use(mdAnchor)
.use(mdTOC);
// Directories
const postsDir = './posts';
const authorsDir = './authors';
const outputDir = './dist';
const templatesDir = './src/templates';
const publicDir = './public';
const siteUrl = config.siteUrl;
// Helper Functions
function slugify(text) {
return text.toLowerCase().replace(/\\s+/g, '-').replace(/[^\w\\-]+/g, '');
}
function parseMetadata(content) {
const lines = content.split('\\n');
const metadata = { tags: [], categories: [] };
let i = 0;
// Parse metadata lines
while (lines[i] && lines[i].includes(':')) {
const [key, value] = lines[i].split(':').map((s) => s.trim());
if (key === 'tags' || key === 'categories') {
metadata[key] = JSON.parse(value);
} else {
metadata[key] = value;
}
i++;
}
// Extract the remaining content
const remainingContent = lines.slice(i).join('\\n');
metadata.content = remainingContent;
// If title is missing, extract from the first heading
if (!metadata.title) {
const match = remainingContent.match(/^#\\s+(.*)/m);
metadata.title = match ? match[1] : 'Untitled';
}
return metadata;
}
function generatePostList(postsArray) {
return postsArray
.map(
(post) => `
<li>
<a href="\${post.url}">\${post.title}</a> - \${post.date}
<p>\${post.excerpt}</p>
</li>
`
)
.join('\\n');
}
function copyStaticAssets() {
fs.mkdirSync(outputDir, { recursive: true });
if (fs.existsSync(publicDir)) {
fs.cpSync(publicDir, outputDir, { recursive: true });
}
}
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Copy static assets
copyStaticAssets();
// Parse Posts
const posts = [];
const tagsSet = new Set();
const categoriesSet = new Set();
fs.readdirSync(postsDir).forEach((file) => {
if (file.endsWith('.md')) {
const content = fs.readFileSync(path.join(postsDir, file), 'utf-8');
const metadata = parseMetadata(content);
metadata.slug = file.replace('.md', '');
metadata.url = `/posts/\${metadata.slug}.html`;
metadata.htmlContent = md.render(metadata.content);
posts.push(metadata);
metadata.tags.forEach((tag) => tagsSet.add(tag));
metadata.categories.forEach((category) => categoriesSet.add(category));
}
});
// Now Parse Authors
const authors = {};
if (fs.existsSync(authorsDir)) {
fs.readdirSync(authorsDir).forEach((file) => {
if (file.endsWith('.md')) {
const content = fs.readFileSync(path.join(authorsDir, file), 'utf-8');
const metadata = parseMetadata(content);
const slug = slugify(metadata.name);
metadata.slug = slug;
authors[metadata.name] = metadata;
// Generate Author Page
const authorTemplate = fs.readFileSync(path.join(templatesDir, 'author.html'), 'utf-8');
const authorPosts = posts.filter((post) => post.author === metadata.name);
const authorContent = authorTemplate
.replace(/\\{\\{authorName\\}\\}/g, metadata.name)
.replace(/\\{\\{postList\\}\\}/g, generatePostList(authorPosts))
.replace(/\\{\\{siteTitle\\}\\}/g, config.siteTitle)
.replace(/\\{\\{footerText\\}\\}/g, config.footerText);
const authorDir = path.join(outputDir, 'authors');
if (!fs.existsSync(authorDir)) fs.mkdirSync(authorDir, { recursive: true });
fs.writeFileSync(path.join(authorDir, `\${slug}.html`), authorContent);
}
});
}
// Generate Individual Posts
posts.sort((a, b) => new Date(b.date) - new Date(a.date));
posts.forEach((post, index) => {
let postContent = fs.readFileSync(path.join(templatesDir, 'post.html'), 'utf-8');
// Previous and Next Posts
const prevPost = posts[index + 1];
const nextPost = posts[index - 1];
const tagsLinks = post.tags
.map((tag) => `<a href="/tags/\${slugify(tag)}.html">\${tag}</a>`)
.join(', ');
const categoriesLinks = post.categories
.map((cat) => `<a href="/categories/\${slugify(cat)}.html">\${cat}</a>`)
.join(', ');
postContent = postContent
.replace(/\\{\\{title\\}\\}/g, post.title)
.replace(/\\{\\{date\\}\\}/g, post.date)
.replace(/\\{\\{author\\}\\}/g, post.author)
.replace(/\\{\\{authorSlug\\}\\}/g, slugify(post.author || 'unknown'))
.replace(/\\{\\{content\\}\\}/g, post.htmlContent)
.replace(/\\{\\{url\\}\\}/g, post.url)
.replace(/\\{\\{slug\\}\\}/g, post.slug)
.replace(/\\{\\{tags\\}\\}/g, tagsLinks)
.replace(/\\{\\{categories\\}\\}/g, categoriesLinks)
.replace(/\\{\\{excerpt\\}\\}/g, post.excerpt || '')
.replace(/\\{\\{siteTitle\\}\\}/g, config.siteTitle)
.replace(/\\{\\{footerText\\}\\}/g, config.footerText);
if (prevPost) {
const prevLink = `<a href="\${prevPost.url}">Previous: \${prevPost.title}</a>`;
postContent = postContent.replace(/\\{\\{prevPost\\}\\}/g, prevLink);
} else {
postContent = postContent.replace(/\\{\\{prevPost\\}\\}/g, '');
}
if (nextPost) {
const nextLink = `<a href="\${nextPost.url}">Next: \${nextPost.title}</a>`;
postContent = postContent.replace(/\\{\\{nextPost\\}\\}/g, nextLink);
} else {
postContent = postContent.replace(/\\{\\{nextPost\\}\\}/g, '');
}
// Create directories and write the post file
const postOutputPath = path.join(outputDir, post.url);
fs.mkdirSync(path.dirname(postOutputPath), { recursive: true });
fs.writeFileSync(postOutputPath, postContent);
});
console.log('Site generated successfully!');
Step 7: Install Dependencies
Since this guide is for non-technical users, we’ll automate dependency installation using Cloudflare Pages. However, if you want to run the script locally, you’ll need to install Node.js and the required packages.
7.1 Install Node.js and npm (Optional)
- Download and install Node.js from nodejs.org.
7.2 Install Dependencies Locally (Optional)
-
Open a terminal in your project directory.
-
Run the following commands:
bashnpm install
Step 8: Generate the Static Site
8.1 Run the Generation Script Locally (Optional)
-
In your terminal, run:
bashnpm run generate -
The generated site will be in the
distfolder.
8.2 Preview the Site Locally (Optional)
-
Install a simple HTTP server:
bashnpm install -g serve -
Serve the
distdirectory:
serve dist
- Open your browser at
http://localhost:5000.
Step 9: Deploy to Cloudflare Pages
We’ll use Cloudflare Pages to build and deploy the site automatically whenever you push changes to GitHub.
9.1 Sign Up for Cloudflare
- Go to Cloudflare and sign up for an account.
9.2 Create a Cloudflare Pages Project
-
Log in to Cloudflare Dashboard.
-
Select “Pages” from the sidebar.
-
Click “Create a project”.
-
Connect to GitHub:
- Authorize Cloudflare to access your GitHub repositories.
- Select your
my-blogrepository.
-
Configure Project:
- Project name:
my-blog(or your preference) - Production branch:
main
- Project name:
-
Set Up Build Settings:
- Framework preset: Select “None”.
- Build command:
npm install && npm run generate - Build output directory:
dist - Root directory: Leave as
/
-
Environment Variables:
- No additional variables needed.
-
Click “Save and Deploy”.
9.3 Wait for Deployment
- Cloudflare Pages will clone your repository, install dependencies, run the build command, and deploy your site.
- Once deployed, you’ll receive a URL like
https://my-blog.pages.dev.
9.4 Update siteUrl in config.json
- After deployment, update the
siteUrlinconfig.jsonwith your Cloudflare Pages URL. - Commit the change to GitHub, and Cloudflare Pages will redeploy your site.
Step 10: Managing Content
10.1 Adding New Posts
-
Navigate to the
postsfolder in your GitHub repository. -
Click “Add file” > “Create new file”.
-
File Name:
your-new-post.md -
Content:
title: Your Post Title
date: YYYY-MM-DD
tags: [tag1, tag2]
categories: [category1]
author: Your Name
excerpt: "A brief summary of your post."
# Your Post Title
Your post content goes here.
- Click “Commit new file”.
10.2 Automatic Deployment
- Every time you push changes to the
mainbranch, Cloudflare Pages will rebuild and redeploy your site.
Additional Features
1. Styling with CSS
1.1 Create a Stylesheet
-
File Path:
public/styles.css -
Content:
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1, h2, h3 {
color: #333;
}
a {
color: #007acc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header, footer {
text-align: center;
margin-bottom: 20px;
}
1.2 The public Folder
- Any files in the
publicfolder will be copied to the root of your site during generation.
2. Enabling Comments (Optional)
- Use a third-party service like Disqus or Commento.
- Add the provided script to your
post.htmltemplate where you want the comments to appear.
3. Search Functionality (Optional)
- The script generates a
search-index.json. - Implement client-side search using Lunr.js.
- Create a
search.htmlin thepublicfolder and include the search script.
Conclusion
You’ve now set up a simple, database-free blogging platform using JavaScript and Markdown. This guide provided all the necessary code and project structure, making it accessible for non-technical users. With GitHub and Cloudflare Pages, managing content and deploying updates is straightforward.
Resources
- Markdown Guide: Basic Syntax
- GitHub Help: Creating New Files
- Cloudflare Pages Documentation: Getting Started
If you have any questions or need further assistance with any of the steps, feel free to ask!