Welcome to your frontend blogging application! This is a clean, modern, and responsive blog platform built with vanilla HTML, CSS, and JavaScript. Perfect for learning frontend development, content management, and building a personal blog without backend complexity!
- Frontend application architecture and organization
- Dynamic content rendering with vanilla JavaScript
- Local storage for data persistence
- Content management and CRUD operations
- Rich text editing and markdown support
- Search and filtering functionality
- Responsive design for content-heavy applications
- Client-side routing (Single Page Application concepts)
The Simple Blogging App provides a complete blogging experience:
- Article Creation: Rich text editor for writing and formatting posts
- Content Management: Create, read, update, and delete blog posts
- Category System: Organize posts by categories and tags
- Search Functionality: Find posts by title, content, or tags
- Reading Experience: Clean, readable layout for blog posts
- Local Storage: All data persisted in browser's local storage
- Responsive Design: Optimized for reading on all devices
Before you start, make sure you have these tools:
- A modern web browser (Chrome, Firefox, Safari, Edge)
- A code editor like VS Code
- Git for version control
- Optional: Live Server extension for VS Code
- Optional: Markdown extension for markdown editing
Get your copy of this project:
git clone https://github.com/[your-username]/simple-blogging-app.git
cd simple-blogging-app
Or download the ZIP file and extract it to your desired location.
Open the project folder in your preferred code editor:
code .
You can open the project in several ways:
Option 1: Direct Browser Opening
- Simply double-click
index.html
to open in your default browser
Option 2: Live Server (Recommended)
- If using VS Code with Live Server extension
- Right-click on
index.html
and select "Open with Live Server" - Your app will open at
http://localhost:5500
with auto-reload
Option 3: Python Simple Server
python -m http.server 8000
Then visit http://localhost:8000
blogging/
βββ index.html # Main HTML structure and app shell
βββ style.css # Styling for blog interface and content
βββ script.js # Blog functionality and data management
βββ Readme.md # This documentation
index.html
: Single-page app structure with blog interfacestyle.css
: Modern CSS for blog layout, typography, and responsive designscript.js
: Complete blog functionality including post management and UI interactions
Create a fully functional blogging platform that runs entirely in the browser:
- Create new blog posts with title, content, and metadata
- Display all posts in a clean, organized list
- Read individual posts in a distraction-free layout
- Edit existing posts with inline editing
- Delete posts with confirmation dialogs
- Basic search functionality by title and content
- Category and tag system for organizing posts
- Advanced search with filters (category, date, tags)
- Rich text editor with formatting options
- Draft system for saving work-in-progress
- Post statistics (word count, reading time)
- Export/import functionality for backup
- Dark/light theme toggle
- Markdown support with live preview
- Image upload and management (using FileReader API)
- Comment system with local storage
- Social sharing buttons
- RSS feed generation
- Full-text search with highlighting
- Progressive Web App (PWA) features
- Offline functionality with service workers
// Blog post structure
const blogPost = {
id: "unique-post-id",
title: "Post Title",
content: "Post content in HTML or markdown",
summary: "Brief description of the post",
author: "Author name",
categories: ["Web Development", "JavaScript"],
tags: ["tutorial", "frontend", "beginner"],
createdDate: "2025-06-02T10:30:00Z",
modifiedDate: "2025-06-02T11:45:00Z",
status: "published", // draft, published, archived
readingTime: 5, // estimated reading time in minutes
featured: false,
};
class BlogStorage {
constructor() {
this.storageKey = "simpleBlogPosts";
this.posts = this.loadPosts();
}
loadPosts() {
const stored = localStorage.getItem(this.storageKey);
return stored ? JSON.parse(stored) : [];
}
savePosts() {
localStorage.setItem(this.storageKey, JSON.stringify(this.posts));
}
createPost(postData) {
const newPost = {
id: this.generateId(),
...postData,
createdDate: new Date().toISOString(),
modifiedDate: new Date().toISOString(),
};
this.posts.unshift(newPost);
this.savePosts();
return newPost;
}
}
class BlogSearch {
constructor(posts) {
this.posts = posts;
}
search(query, filters = {}) {
return this.posts.filter((post) => {
const matchesQuery = this.matchesSearchQuery(post, query);
const matchesCategory = this.matchesCategory(post, filters.category);
const matchesTags = this.matchesTags(post, filters.tags);
const matchesDateRange = this.matchesDateRange(post, filters.dateRange);
return matchesQuery && matchesCategory && matchesTags && matchesDateRange;
});
}
matchesSearchQuery(post, query) {
if (!query) return true;
const searchText =
`${post.title} ${post.content} ${post.summary}`.toLowerCase();
return searchText.includes(query.toLowerCase());
}
}
:root {
/* Reading-optimized typography */
--font-reading: "Georgia", "Times New Roman", serif;
--font-interface: "system-ui", "-apple-system", sans-serif;
--font-code: "Monaco", "Menlo", monospace;
/* Reading-friendly sizes */
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
}
/* Blog post content styling */
.blog-content {
font-family: var(--font-reading);
font-size: var(--text-lg);
line-height: 1.7;
max-width: 65ch; /* Optimal reading width */
margin: 0 auto;
}
:root {
--primary-color: #2563eb;
--secondary-color: #64748b;
--accent-color: #059669;
--background-light: #ffffff;
--background-gray: #f8fafc;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-muted: #94a3b8;
--border-light: #e2e8f0;
--success-color: #10b981;
--warning-color: #f59e0b;
--error-color: #ef4444;
}
/* Blog layout grid */
.blog-layout {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
@media (min-width: 768px) {
.blog-layout {
grid-template-columns: 2fr 1fr;
}
}
/* Post card styling */
.post-card {
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
margin-bottom: 1.5rem;
transition: box-shadow 0.2s ease;
}
.post-card:hover {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
class SimpleEditor {
constructor(element) {
this.element = element;
this.setupToolbar();
this.bindEvents();
}
setupToolbar() {
const toolbar = document.createElement("div");
toolbar.className = "editor-toolbar";
toolbar.innerHTML = `
<button data-command="bold" title="Bold">B</button>
<button data-command="italic" title="Italic">I</button>
<button data-command="underline" title="Underline">U</button>
<button data-command="createLink" title="Link">π</button>
<button data-command="insertOrderedList" title="Numbered List">1.</button>
<button data-command="insertUnorderedList" title="Bullet List">β’</button>
`;
this.element.parentNode.insertBefore(toolbar, this.element);
}
executeCommand(command, value = null) {
document.execCommand(command, false, value);
this.element.focus();
}
}
class AutoSave {
constructor(blogStorage, interval = 30000) {
// 30 seconds
this.storage = blogStorage;
this.interval = interval;
this.timeoutId = null;
}
scheduleAutoSave(postId, getPostData) {
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
const postData = getPostData();
this.storage.saveDraft(postId, postData);
this.showAutoSaveIndicator();
}, this.interval);
}
showAutoSaveIndicator() {
const indicator = document.getElementById("autosave-indicator");
indicator.textContent = `Draft saved at ${new Date().toLocaleTimeString()}`;
indicator.style.opacity = "1";
setTimeout(() => (indicator.style.opacity = "0"), 2000);
}
}
// Virtual scrolling for large post lists
class VirtualScrolling {
constructor(container, itemHeight, renderItem) {
this.container = container;
this.itemHeight = itemHeight;
this.renderItem = renderItem;
this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2;
}
render(items, scrollTop) {
const startIndex = Math.floor(scrollTop / this.itemHeight);
const endIndex = Math.min(startIndex + this.visibleItems, items.length);
this.container.innerHTML = "";
for (let i = startIndex; i < endIndex; i++) {
const element = this.renderItem(items[i], i);
element.style.position = "absolute";
element.style.top = `${i * this.itemHeight}px`;
this.container.appendChild(element);
}
}
}
// Lazy loading for post images
function setupLazyLoading() {
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove("lazy");
imageObserver.unobserve(img);
}
});
});
document.querySelectorAll("img[data-src]").forEach((img) => {
imageObserver.observe(img);
});
}
class BlogSearch {
constructor() {
this.searchIndex = new Map();
this.buildSearchIndex();
}
buildSearchIndex() {
// Create inverted index for faster searching
this.posts.forEach((post) => {
const words = this.extractWords(post.title + " " + post.content);
words.forEach((word) => {
if (!this.searchIndex.has(word)) {
this.searchIndex.set(word, new Set());
}
this.searchIndex.get(word).add(post.id);
});
});
}
extractWords(text) {
return text
.toLowerCase()
.replace(/[^\w\s]/g, "")
.split(/\s+/)
.filter((word) => word.length > 2);
}
search(query) {
const queryWords = this.extractWords(query);
const postIds = queryWords.map(
(word) => this.searchIndex.get(word) || new Set()
);
// Find intersection of all word matches
return this.intersectSets(postIds);
}
}
const filterOptions = {
sortBy: ["date", "title", "readingTime"],
sortOrder: ["asc", "desc"],
categories: ["Web Development", "Design", "Tutorials"],
status: ["published", "draft", "archived"],
featured: [true, false],
};
function applyFilters(posts, filters) {
let filtered = [...posts];
// Apply category filter
if (filters.category) {
filtered = filtered.filter((post) =>
post.categories.includes(filters.category)
);
}
// Apply tag filter
if (filters.tags && filters.tags.length) {
filtered = filtered.filter((post) =>
filters.tags.some((tag) => post.tags.includes(tag))
);
}
// Apply sorting
filtered.sort((a, b) => {
const key = filters.sortBy || "createdDate";
const order = filters.sortOrder === "asc" ? 1 : -1;
return a[key] > b[key] ? order : -order;
});
return filtered;
}
// Simple testing framework for blog functions
class BlogTester {
constructor() {
this.tests = [];
this.results = { passed: 0, failed: 0 };
}
test(description, testFunction) {
try {
testFunction();
this.results.passed++;
console.log(`β
${description}`);
} catch (error) {
this.results.failed++;
console.error(`β ${description}: ${error.message}`);
}
}
assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
}
}
// Example tests
const tester = new BlogTester();
tester.test("Should create a new post", () => {
const storage = new BlogStorage();
const post = storage.createPost({
title: "Test Post",
content: "Test content",
});
tester.assert(post.id, "Post should have an ID");
tester.assert(post.title === "Test Post", "Post should have correct title");
});
// Feature detection for browser compatibility
function checkBrowserSupport() {
const features = {
localStorage: typeof Storage !== "undefined",
flexbox: CSS.supports("display", "flex"),
grid: CSS.supports("display", "grid"),
es6: () => {
try {
eval("const x = () => {};");
return true;
} catch (e) {
return false;
}
},
};
const unsupported = Object.entries(features)
.filter(([name, supported]) => !supported)
.map(([name]) => name);
if (unsupported.length) {
console.warn("Unsupported features:", unsupported);
}
return unsupported.length === 0;
}
rem Enable GitHub Pages in repository settings
rem Your blog will be available at: https://username.github.io/repository-name
rem Drag and drop your folder to Netlify
rem Or connect your GitHub repository for automatic deployments
npm install -g vercel
vercel --prod
- Upload files via FTP to your hosting provider
- Ensure
index.html
is in the root directory - Configure custom domain if needed
rem Validate HTML
npx html-validate index.html
rem Check CSS
npx stylelint style.css
rem Minify CSS for production
npx clean-css-cli -o style.min.css style.css
rem Minify JavaScript for production
npx terser script.js -o script.min.js
rem Check accessibility
npx pa11y http://localhost:5500
rem Generate sitemap (if using build tools)
npx sitemap-generator http://localhost:5500
- Content First: Focus on creating a great writing and reading experience
- Performance: Optimize for fast loading, especially with many posts
- Accessibility: Ensure your blog is readable by screen readers
- Mobile Experience: Test thoroughly on mobile devices
- Data Backup: Implement export functionality to prevent data loss
- User Experience: Make navigation intuitive and search powerful
// Pagination for performance
class PostPagination {
constructor(posts, postsPerPage = 10) {
this.posts = posts;
this.postsPerPage = postsPerPage;
this.currentPage = 1;
}
getCurrentPosts() {
const start = (this.currentPage - 1) * this.postsPerPage;
const end = start + this.postsPerPage;
return this.posts.slice(start, end);
}
getTotalPages() {
return Math.ceil(this.posts.length / this.postsPerPage);
}
}
// Sanitize HTML content to prevent XSS
function sanitizeHTML(html) {
const temp = document.createElement("div");
temp.innerHTML = html;
// Remove script tags and event handlers
const scripts = temp.querySelectorAll("script");
scripts.forEach((script) => script.remove());
// Remove dangerous attributes
const allElements = temp.querySelectorAll("*");
allElements.forEach((el) => {
const attrs = [...el.attributes];
attrs.forEach((attr) => {
if (attr.name.startsWith("on")) {
el.removeAttribute(attr.name);
}
});
});
return temp.innerHTML;
}
// Export blog data as JSON
function exportBlogData() {
const data = {
posts: blogStorage.getAllPosts(),
categories: blogStorage.getCategories(),
tags: blogStorage.getAllTags(),
exportDate: new Date().toISOString(),
version: "1.0",
};
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `blog-export-${new Date().toISOString().split("T")[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}
- Tutorial Posts: Step-by-step guides with code examples
- Opinion Pieces: Thoughts on industry trends and technologies
- Project Showcases: Detailed breakdowns of your projects
- Learning Notes: Document your learning journey
- Reviews: Tools, books, courses, or conferences
- Development: Programming tutorials and tips
- Design: UI/UX insights and inspiration
- Career: Professional development and advice
- Tools: Reviews and recommendations
- Personal: Life updates and reflections
- Content Strategy Guide - Learn effective content creation
- Typography on the Web - Make your blog beautiful to read
- Web Content Accessibility Guidelines - Ensure accessibility
- JavaScript Patterns - Better code organization
- Writing for the Web - User-focused writing
- Check browser Console for JavaScript errors
- Test localStorage functionality in private/incognito mode
- Validate your HTML and CSS with online tools
- Test your blog with different content lengths
- Use Lighthouse for performance auditing
Once you complete the basic blogging app, try these enhancements:
- Markdown Editor: Implement live markdown preview
- Theme System: Multiple visual themes for readers
- Analytics Dashboard: Track post views and popular content
- Comment System: Reader engagement with local storage
- Newsletter: Export post lists for email marketing
- RSS Feed: Generate RSS XML for feed readers
- Social Integration: Share buttons and Open Graph meta tags
- Offline Reading: Service worker for offline post access
// Example blog post data
const samplePosts = [
{
id: "getting-started-with-javascript",
title: "Getting Started with JavaScript in 2025",
summary: "A comprehensive guide for beginners learning JavaScript",
content: `
<h2>Introduction</h2>
<p>JavaScript is the programming language of the web...</p>
<h2>Setting Up Your Environment</h2>
<p>Before we start coding, let's set up our development environment...</p>
`,
author: "John Doe",
categories: ["JavaScript", "Tutorials"],
tags: ["beginner", "web-development", "programming"],
createdDate: "2025-06-01T10:00:00Z",
readingTime: 8,
featured: true,
status: "published",
},
];
Happy blogging! βοΈ
"A blog is a digital garden where ideas grow and readers discover new perspectives!"