Why Firestore?
I already had Firestore powering projects, experience and the contact form on my portfolio. Adding a blog was a natural fit: one auth model, one set of security rules, one bill.
The data shape
A post is just a document in the posts collection:
interface Post {
id: string;
slug: string;
title: string;
excerpt: string;
content: string; // raw markdown
tags: string[];
published: boolean;
featured: boolean;
publishedAt?: string;
updatedAt?: string;
}
Security rules
The single most important rule:
match /posts/{id} {
allow read: if resource.data.published == true || isAdmin();
allow write: if isAdmin();
}
This keeps drafts private without needing a separate collection.
Rendering
Public reads use react-markdown with remark-gfm, rehype-slug and
rehype-autolink-headings so every <h2> and <h3> gets a stable id —
which the table-of-contents component then observes with IntersectionObserver.
SEO
A small scripts/prerender.ts runs after vite build and emits one static
HTML file per published post, with title, description, canonical, Open Graph
and a BlogPosting JSON-LD block injected into <head>. Crawlers see
fully-rendered HTML; users still get the SPA experience.
Takeaway
You don't need Contentful, Sanity or Notion-as-CMS to ship a blog. If you already have Firestore in your stack, a few hundred lines of code gets you all the way there.
