1999 Blogging Platform — Post editor, publish flow, archive, RSS feed, comments. Blogger launched in 1999 and put publishing power in everyone's hands — no HTML required, no FTP needed, just a browser and something to say. It democratized publishing and gave rise to the blogosphere. This clone builds a complete blogging platform from HTML structure through to an interactive PWA with localStorage persistence.

01

HTML Structure

Complete

Why learn this?

Blogs were the original content management system — a simple content hierarchy that every site on the web still follows. The layout pattern — a branded header, navigation bar, main content column with a sidebar, and footer — is the most common page structure in web development. Blogger's original 1999 interface was almost this simple: a header with "Blogger", a navigation row, a list of posts in reverse-chronological order, and sidebar widgets. Building this HTML structure teaches semantic HTML5 elements (<header>, <nav>, <main>, <article>, <aside>, <footer>), the article-and-sidebar two-column layout, and the post listing pattern that powers every blog, news site, and social media feed on the web.

Design decisions & tradeoffs

Semantic HTML5 elements vs generic divs. The page uses <article> for each post, <aside> for the sidebar, and <nav> for navigation. The alternative — using <div> elements with class names — would be visually identical but semantically meaningless. Screen readers and search engines use HTML5 landmarks to navigate content — an <article> is announced as "article" and can be jumped to directly. The semantic approach adds zero CSS complexity while providing meaningful structure.

Two-column layout: 1fr sidebar vs fixed width. The main layout uses grid-template-columns: 1fr 240px. The content column takes all remaining space while the sidebar is fixed at 240px — wide enough for archive links, about text, and blogroll entries without being so wide that it competes with the main content. The alternative — a percentage-based sidebar (e.g., 70% 30%) — would scale the sidebar too wide on large screens or too narrow on small screens.

Blue header color. Blogger's original brand used a distinctive blue color. The #1a73e8 blue (Google Blue) is used here — it's the same blue Google later adopted for its Material Design system. The alternative — a generic dark or light header — wouldn't visually evoke the "blog" feel. The blue header with white text provides ~6.5:1 contrast, exceeding WCAG AA requirements.

Key concepts

  • <article> — Self-contained content item. Each post is an independent article
  • grid-template-columns: 1fr 240px — Two-column layout: content fills space, sidebar is fixed
  • reverse-chronological — Blog posts ordered newest first, the default content pattern
  • <time> element — Machine-readable publication date. Used for semantic dating

Next up

Step 2 adds full visual styling with gradients, card-based post layouts, refined typography, and sidebar design.

HTML Structure ▶ Run
<header>
  <h1>Blogger</h1>
  <p>Publish what you think</p>
</header>
<nav>
  <a href="#">Home</a>
  <a href="#">New Post</a>
  <a href="#">Archive</a>
</nav>

<div class="layout">
  <main>
    <article class="post">
      <h2>Post Title</h2>
      <p>Content...</p>
    </article>
  </main>
  <aside>
    Sidebar widgets
  </aside>
</div>
Layout CSS
.layout {
  display: grid;
  grid-template-columns:
    1fr 240px;
  gap: 1.5rem;
}
.post {
  background: #fff;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
  padding: 1.25rem;
  margin-bottom: 1rem;
}
📐 1999 layout mindset: Blogger's original interface was text-heavy with minimal styling. The focus was on content — post titles, dates, and body text. We use semantic HTML5 elements, but the visual hierarchy (header, nav, content, sidebar) matches the original Blogger pattern exactly. The post list is the same reverse-chronological feed that every blog and social platform still uses today.
02

Styling & Layout

Complete

Why learn this?

Visual design transforms a functional HTML layout into a brand. The Google-inspired gradient header (blue, green, yellow, red — the Google Colors), card-based post layout with hover effects, tag badges, archive counters, and refined typography teach the CSS patterns that make a blog feel like a real platform. This step covers linear gradients with multiple stops, card shadows and transform effects on hover, flexbox for meta information layout, inline SVG patterns for background texture, pseudo-element decorations, and custom tag/badge styling.

Design decisions & tradeoffs

Google Colors gradient vs pure blue. The header uses a 3-stop gradient: #1a73e8 → #4285f4 → #34a853. This references the Google brand colors (blue, blue-green, green) with the header's bottom border adding yellow and red stripes. The alternative — a flat blue header — would be simpler but less distinctive. The gradient creates visual interest and makes the header feel modern while still evoking the original Blogger brand. The diagonal angle (135deg) creates a natural light-from-top-left effect.

Card-based posts with hover lift. Each post is a white card with a subtle border, border-radius: 10px, and a hover effect that lifts the card 2px and adds a shadow. The alternative — flat rows without borders — would be a more "1999" look (Blogger was simple), but the card approach improves scanability and makes each post feel like a distinct piece of content. The translateY(-2px) on hover provides tactile feedback without being overly animated.

Tag badges with rounded pills. Post tags use border-radius: 100px to create pill-shaped badges with a light blue background. The alternative — square badges or simple text — would be less recognizable as interactive tags. The pill shape is the standard tag UI pattern on the modern web, and the light blue on white provides clear visual separation from the post content.

Key concepts

  • linear-gradient(135deg, ...) — Multi-stop gradient. Direction, color stops at percentages
  • translateY(-2px) — CSS transform for hover lift. Hardware-accelerated, performant
  • border-radius: 100px — Pill-shaped badge. Large radius fully rounds a short element
  • text-transform: uppercase + letter-spacing — All-caps heading style. Used for sidebar section titles
  • ::before pseudo-element — Sidebar section markers. Adds visual flourishes without extra HTML

Next up

Step 3 adds JavaScript interactivity: post editor, publish flow, localStorage persistence, comment system, and tag filtering.

Gradient header ▶ Run
header {
  background: linear-gradient(
    135deg,
    #1a73e8 0%,
    #4285f4 50%,
    #34a853 100%
  );
  position: relative;
  overflow: hidden;
}
header::before {
  /* SVG dot pattern overlay */
  content: '';
  position: absolute;
  top: 0; left: 0;
  right: 0; bottom: 0;
  background: url(
    "data:image/svg+xml,..."
  );
  pointer-events: none;
}
header::after {
  content: '';
  height: 4px;
  background: linear-gradient(
    90deg,
    #1a73e8, #34a853,
    #fbbc04, #ea4335
  );
}
Post card with hover
.post {
  background: #fff;
  border: 1px solid #e0d6c8;
  border-radius: 10px;
  padding: 1.5rem;
  transition:
    box-shadow 0.2s,
    transform 0.2s;
}
.post:hover {
  box-shadow:
    0 6px 20px
    rgba(0,0,0,0.08);
  transform: translateY(-2px);
}
.tags span {
  background: #f0f4ff;
  color: #1a73e8;
  padding: 0.15em 0.5em;
  border-radius: 100px;
  border: 1px solid #d6e4ff;
}
🎨 Color system: The header uses the four Google brand colors (blue, green, yellow, red) laid out as a gradient. This isn't historically accurate to 1999 Blogger (which used a simpler blue header), but it creates a recognizable brand feel that connects the clone to Google's design language. The warm paper background (#f4f1ea) is a cream tone that makes the white post cards stand out — it's the same subtle warmth used by Medium and other modern publishing platforms.
03

Post Editor & Interactivity

Complete

Why learn this?

The post editor is the heart of any blogging platform. This step adds the complete CRUD flow — creating posts with a title, content, and tags; storing them in localStorage so they survive page refreshes; rendering them dynamically in reverse-chronological order; and adding interactive features like expand/collapse, tag filtering, and a comment display. This teaches localStorage for persistent client-side data, dynamic DOM rendering from a data array, event delegation, the create-read-delete (CRD) pattern that powers every CMS, and the editor/publish flow that's the core interaction of any content creation tool.

Design decisions & tradeoffs

localStorage vs in-memory only. All posts are stored in localStorage as a JSON array. The alternative — keeping data only in memory — would lose posts on page refresh. localStorage provides persistence with zero backend infrastructure. The tradeoff: localStorage is synchronous (blocks the main thread during read/write) and has a ~5MB storage limit. For a blog demo with text-only content, neither is a concern. The save() function is called after every mutation to ensure data is never lost.

Inline editor vs modal vs separate page. The editor is an inline section that slides into the page below the nav when "New Post" is clicked. The alternative — a separate editor page — would require routing or URL state management. The inline approach keeps the editor visually connected to the blog — you can see the post list below as you write. The editor switches between visible and hidden via CSS class toggle, making it fast (no page navigation) and simple to implement.

seed data for first-time experience. The app starts with three pre-written blog posts about blogging itself (meta!). The alternative — starting with an empty blog — would be a better demonstration of the "new user" experience but would make the initial visit feel empty. The seed data demonstrates the post rendering, tags, and comments features on first load. Users can delete posts or write their own to replace them.

Key concepts

  • localStorage.setItem/getItem — Browser persistent storage. Data survives page reloads
  • JSON.parse/stringify — Serialize JavaScript objects to strings for storage
  • Array.unshift() — Add item to the beginning of array. Newest posts appear first
  • template literals ($) — Build HTML strings dynamically from data. The virtual-DOM-free approach
  • classList.toggle() — Show/hide elements. Used for editor visibility and post expansion

Next up

Step 4 completes the PWA with archive view, comment adding, RSS feed generation, about/stats page, and full responsive polish.

Post editor ▶ Run
const posts = JSON.parse(
  localStorage.getItem('posts')
) || [];

const save = () =>
  localStorage.setItem('posts',
    JSON.stringify(posts));

// Publish flow
document.getElementById('publishBtn')
  .addEventListener('click', () => {
    const title = titleInput.value.trim();
    const content = contentInput.value.trim();
    if (!title || !content) return;
    posts.unshift({
      id: nextId++,
      title, content,
      tags: parseTags(tagInput.value),
      date: formatDate(),
      author: 'admin',
      comments: []
    });
    save();
    renderPosts();
  });
Dynamic post rendering
const renderPosts = () => {
  let html = '';
  posts.forEach(p => {
    html += `
      <article class="post">
        <h2>${p.title}</h2>
        <div class="meta">
          <span>${p.date}</span>
        </div>
        <p>${excerpt}</p>
        <div class="tags">
          ${p.tags.map(t =>
            `<span>${t}</span>`
          ).join('')}
        </div>
      </article>`;
  });
  main.innerHTML = html;
};
💾 localStorage tip: localStorage is synchronous — avoid writing large datasets or writing frequently in loops. For this blog app with small text posts, it's perfect. For production apps handling images or high-frequency updates, consider IndexedDB (async, larger storage) or a backend API. The data structure is a flat array of post objects — each with id, title, content, tags, date, author, and comments array. This schema is simple enough to demo CRUD without a database.
04

Full Blog PWA

Complete

Why learn this?

The final step ties everything together into a Progressive Web App: a complete, installable blogging platform that works offline. This adds the archive view (browse posts by month), interactive comment system (add comments to any post), RSS feed generation (view the RSS XML any blog would produce), about page with stats (total posts, comments, tags), and PWA features (manifest, service worker, theme color). This step teaches the PWA pattern (manifest.json + service worker for offline support), RSS as a data format, archive navigation by date grouping, comment CRUD as a sub-resource of posts, and the stats/metrics pattern that every platform needs.

Design decisions & tradeoffs

View switching vs SPA routing. The app uses simple showView() calls that toggle visibility of sections (home, editor, archive, about). The alternative — a full SPA with hash-based routing — would allow deep linking to specific views but adds complexity without proportional benefit for a 4-section app. The current approach uses data attributes (data-view="archive") and hides/shows sections via CSS classes. Each view is a section in the HTML, with the home view rendered dynamically from the posts array.

RSS feed as viewable XML. The RSS button generates and displays the RSS XML directly in the browser. The alternative — serving an actual /rss.xml file — is better for feed readers but requires a server. The inline display approach is educational — users can see exactly what an RSS feed looks like internally. The XML follows RSS 2.0 spec with title, link, description, pubDate, and a custom tags element for each post.

Comment form inline vs modal. Each expanded post shows a comment form at the bottom of the comments section. The alternative — a separate modal or page for commenting — would interrupt the reading flow. The inline form is minimal and immediately available when the user reads the full post. The name and text fields are simple inputs with a "Post" button — no validation beyond non-empty text, keeping the friction of commenting very low.

PWA manifest + service worker. The app includes a manifest.json (with name, icons, theme color, display: standalone) and a service worker that caches the app shell. The alternative — no manifest or SW — would still work but wouldn't be installable or work offline. The PWA features make this a genuine "app" that users can add to their home screen, bridging the gap between a demo and a real product.

Key concepts

  • PWA: manifest.json + service worker + offline caching + installable
  • RSS 2.0: XML format for feed syndication. <channel> with <item> elements
  • Archive grouping: Array.reduce() to group posts by month/year
  • Comment CRUD: Each post's comments array is a sub-collection. Push new comments, render inline
  • Stats: Computed properties (post count, comment count, unique tag count) from the data store
Archive grouping ▶ Run
const updateArchive = () => {
  const months = {};
  posts.forEach(p => {
    const m = p.date.split(',')[0];
    months[m] = (months[m] || 0) + 1;
  });
  archiveList.innerHTML =
    Object.entries(months)
      .map(([m, c]) =>
        `<li>${m}
           <span>${c} posts</span>
         </li>`
      ).join('');
};
PWA Manifest
{
  "name": "Blogger Clone",
  "short_name": "Blogger",
  "start_url": "/apps/blogger-clone/step4/",
  "display": "standalone",
  "background_color": "#f4f1ea",
  "theme_color": "#1a73e8",
  "icons": [
    {"src": "icon-192.png",
     "sizes": "192x192"},
    {"src": "icon-512.png",
     "sizes": "512x512"}
  ]
}
📡 PWA + RSS: The final app is a complete Progressive Web App — installable on mobile and desktop, cacheable offline via service worker. The RSS feed button is a bonus feature showing the underlying XML format that made blog syndication possible. Combined with localStorage persistence, the app works fully offline — you can write posts, add comments, and browse content without a network connection. This is the same pattern used by Blogger's early desktop publishing tools.