DEV Community

Cover image for How I Built a Privacy-First Markdown Editor with Astro + Cloudflare Pages
tzhkai
tzhkai

Posted on • Originally published at markdownmaster.site

How I Built a Privacy-First Markdown Editor with Astro + Cloudflare Pages

How I Built a Privacy-First Markdown Editor with Astro + Cloudflare Pages

I wanted a Markdown editor that loads instantly, previews in real time, and never asks for my email. So I built MarkdownMaster.

Here's the full story: the tech stack, the architecture decisions, and what I learned along the way.

The Problem with Existing Markdown Editors

Most online Markdown editors fall into one of three camps:

  1. Heavy WYSIWYG apps — HackMD, Notion, etc. Cool features, but slow to load and require accounts
  2. Ad-cluttered free tools — Works, but the UX is awful
  3. Developer-only CLI tools — Great for terminal users, useless for sharing with non-technical teammates

I wanted something different: a clean, fast, privacy-first editor that works the moment you open the URL. No loading spinners. No cookie walls. No "sign up to save" nag screens.

The Stack: Why Astro + Cloudflare

After evaluating several options (Next.js, SvelteKit, plain HTML), I landed on:

Concern Solution
Speed Astro static generation → zero JavaScript runtime
Global latency Cloudflare Pages edge network → <50ms TTFB worldwide
Markdown parsing marked.js → battle-tested GFM parser
Syntax highlighting Prism.js → 100+ languages, lightweight
Dynamic content Cloudflare D1 → edge SQLite for blog content
Analytics GA4 with cookie consent banner

Why Not Next.js?

Next.js is great, but for a content-heavy tool that doesn't need client-side state management, it's overkill. Every Next.js page ships 70-100KB of JavaScript even for static content. Astro ships zero.

Why Cloudflare Pages Over Vercel/Netlify?

  • Free tier includes 500 builds/month (vs Vercel's 100 on hobby)
  • D1 database at the edge (no cold starts like serverless)
  • Workers for lightweight backend logic (affiliate links, A/B tests)
  • Built-in analytics without third-party scripts

Architecture

┌─────────────────────────────────────────────────┐
│                    Cloudflare                     │
│  ┌──────────┐  ┌──────────┐  ┌───────────────┐  │
│  │  Pages   │  │  Worker  │  │      D1       │  │
│  │ (static) │──│(affiliate│──│(blog content, │  │
│  │  Astro   │  │ injection)│  │ link templates)│  │
│  └──────────┘  └──────────┘  └───────────────┘  │
└─────────────────────────────────────────────────┘
         │
         ▼
    User's Browser
    ┌─────────────────────────────┐
    │  marked.js (GFM parser)     │
    │  Prism.js (highlighting)    │
    │  GA4 (analytics)            │
    │  All client-side. Zero API  │
    └─────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Key architectural decisions:

1. Everything Runs Client-Side

The editor is pure HTML + CSS + JS. When you type on the left, marked.js converts it to HTML on the right — all in your browser. No server round-trips, no API calls, no latency.

This also means your content never leaves your device. I don't have a server to store it on even if I wanted to.

2. Astro for Static Generation

The blog, docs, and landing pages are all pre-built HTML. No SSR, no hydration, no client-side routing. Just pure HTML served from Cloudflare's edge.

---
// src/pages/index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout
  title="Free Online Markdown Editor — MarkdownMaster"
  description="Free online Markdown editor with real-time live preview..."
>
  <section class="hero">
    <h1>Write Markdown. See It Instantly.</h1>
    <a href="/editor/">Start Writing Now →</a>
  </section>
  <!-- Features, etc -->
</BaseLayout>
Enter fullscreen mode Exit fullscreen mode

3. Cloudflare Worker for Lightweight Backend

The only server-side code is a Worker that proxies requests from markdownmaster.site to the Pages deployment. It injects an affiliate button on HTML pages and passes everything else through untouched.

// Simplified Worker logic
export default {
  async fetch(request, env) {
    const response = await fetch(origin + url.pathname);

    // Only inject into HTML responses
    if (response.headers.get('content-type')?.includes('text/html')) {
      let html = await response.text();
      const links = await env.DB.prepare(
        "SELECT links_json FROM link_templates WHERE site_id = ?"
      ).bind("tool-markdown").first();

      html = html.replace('</body>', affiliateButton(links) + '</body>');
      return new Response(html, response);
    }

    return response; // Static assets pass through untouched
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Cookie Consent That Actually Respects Privacy

GDPR compliance isn't optional if you use GA4. But I hate those full-screen cookie walls that darken the page and force you to click through 12 toggles.

My solution: a slim bottom banner with one button.

// No framework, no npm dependency. Just vanilla JS.
(function(){
  if (document.cookie.indexOf('mm_cookie_consent=1') !== -1) return;
  document.getElementById('cookie-banner').style.display = 'flex';
  document.getElementById('cookie-accept').onclick = function(){
    document.cookie = 'mm_cookie_consent=1;max-age=31536000;path=/;SameSite=Lax';
    document.getElementById('cookie-banner').style.display = 'none';
  };
})();
Enter fullscreen mode Exit fullscreen mode

The Blog: 17 Articles and Counting

I wrote 17 in-depth Markdown tutorials — from beginner cheat sheets to advanced topics like Mermaid diagrams and LaTeX math formulas. Each article is 2,500-3,500 words with real code examples.

Article Topic
Markdown Cheat Sheet Complete syntax reference
How to Write Markdown Step-by-step beginner guide
Markdown for Developers README, docs, wikis
Advanced Markdown Footnotes, Mermaid, Math
Build a Blog with Markdown + Astro Full tutorial

The blog serves two purposes:

  1. SEO — long-tail keywords like "how to create markdown tables" bring organic traffic
  2. Trust — Google's EEAT guidelines favor sites that demonstrate expertise with substantial content

SEO: What I Learned

Launching a new domain is humbling. You ship, you submit your sitemap, and... crickets. For weeks.

Here's what actually moved the needle:

1. Fix Canonical URLs Immediately

My blog pages had a canonical dead loop: the sitemap linked to /blog/post-name/ but the page's canonical tag pointed to /blog/post-name.html, which 301-redirected back to the clean URL. Google saw this and refused to index anything.

Fix: Align canonical with sitemap URLs. This single fix took my site from 0 to 7 organic search clicks in 24 hours.

2. EEAT Signals Matter

Google looks for "Experience, Expertise, Authoritativeness, Trustworthiness" signals. I added:

  • About page with my name, GitHub profile, and technical background
  • Footer links to the open-source repository
  • Privacy policy that honestly describes GA4 cookie usage
  • Cookie consent (GDPR compliance matters in 2026)

3. .site TLDs Have a Trust Problem

I learned this the hard way: Google's algorithms are skeptical of .site domains because they're commonly used by phishing sites. If I were starting over, I'd choose .com or .io. If you're stuck with .site like me, the EEAT signals above become even more critical.

What's Next

  • Open-source the editor component as a standalone package
  • Export formats: PDF, DOCX, HTML zip
  • Keyboard shortcuts: Full VSCode-style command palette
  • Collaboration: Real-time multiplayer editing via WebRTC (no server, still privacy-first)

Try It

markdownmaster.site — No sign-up, no tracking, no ads. Just open and write.

GitHub — Star the repo if you find it useful!


Built by KK Tian. I build fast, privacy-first web tools. Follow me on GitHub for more.

Top comments (0)