Markdown Site
A minimalist markdown site built with React, Convex, and Vite. Optimized for SEO, AI agents, and LLM discovery.
Features
- Markdown-based blog posts with frontmatter
- Syntax highlighting for code blocks
- Four theme options: Dark, Light, Tan (default), Cloud
- Real-time data with Convex
- Fully responsive design
SEO and Discovery
- RSS feeds at
/rss.xmland/rss-full.xml(with full content) - Dynamic sitemap at
/sitemap.xml - JSON-LD structured data for Google rich results
- Open Graph and Twitter Card meta tags
robots.txtwith AI crawler rulesllms.txtfor AI agent discovery
AI and LLM Access
/api/posts- JSON list of all posts for agents/api/post?slug=xxx- Single post JSON or markdown/rss-full.xml- Full content RSS for LLM ingestion- Copy Page dropdown for sharing to ChatGPT, Claude, Cursor, VS Code
Getting Started
Prerequisites
- Node.js 18 or higher
- A Convex account
Setup
- Install dependencies:
npm install
- Initialize Convex:
npx convex dev
This will create your Convex project and generate the .env.local file.
- Start the development server:
npm run dev
Writing Blog Posts
Create markdown files in content/blog/ with frontmatter:
Static Pages (Optional)
Create optional pages like About, Projects, or Contact in content/pages/:
---
title: "About"
slug: "about"
published: true
order: 1
---
Your page content here...
Pages appear as navigation links in the top right, next to the theme toggle. The order field controls display order (lower numbers first).
---
title: "Your Post Title"
description: "A brief description"
date: "2025-01-15"
slug: "your-post-slug"
published: true
tags: ["tag1", "tag2"]
readTime: "5 min read"
image: "/images/my-header.png"
---
Your markdown content here...
Images
Open Graph Images
Add an image field to frontmatter for social media previews:
image: "/images/my-header.png"
Recommended dimensions: 1200x630 pixels. Images can be local (/images/...) or external URLs.
Inline Images
Add images in markdown content:

Place image files in public/images/. The alt text displays as a caption.
Site Logo
Edit src/pages/Home.tsx to set your site logo:
const siteConfig = {
logo: "/images/logo.svg", // Set to null to hide
// ...
};
Replace public/images/logo.svg with your own logo file.
Favicon
Replace public/favicon.svg with your own icon. The default is a rounded square with the letter "m". Edit the SVG to change the letter or style.
Default Open Graph Image
The default OG image is used when posts do not have an image field. Replace public/images/og-default.svg with your own image (1200x630 recommended).
Update the reference in src/pages/Post.tsx:
const DEFAULT_OG_IMAGE = "/images/og-default.svg";
Syncing Posts
Posts are synced to Convex at build time. To manually sync:
npm run sync
Deployment
Netlify
- Connect your repository to Netlify
- Set environment variables:
VITE_CONVEX_URL- Your Convex deployment URL
- Update
netlify.tomlwith your Convex HTTP URL (replaceYOUR_CONVEX_DEPLOYMENT) - Deploy with:
npm run deploy
Project Structure
personal-blog/
├── content/blog/ # Markdown blog posts
├── convex/ # Convex backend
│ ├── http.ts # HTTP endpoints (sitemap, API, RSS)
│ ├── posts.ts # Post queries and mutations
│ ├── rss.ts # RSS feed generation
│ └── schema.ts # Database schema
├── netlify/ # Netlify edge functions
├── public/ # Static assets
│ ├── images/ # Blog images and OG images
│ ├── robots.txt # Crawler rules
│ └── llms.txt # AI agent discovery
├── scripts/ # Build scripts
└── src/
├── components/ # React components
├── context/ # Theme context
├── pages/ # Page components
└── styles/ # Global CSS
Tech Stack
- React 18
- TypeScript
- Vite
- Convex
- react-markdown
- react-syntax-highlighter
- date-fns
- lucide-react
- Netlify
API Endpoints
| Endpoint | Description |
|---|---|
/rss.xml |
RSS feed with post descriptions |
/rss-full.xml |
RSS feed with full post content |
/sitemap.xml |
Dynamic XML sitemap |
/api/posts |
JSON list of all posts |
/api/post?slug=xxx |
Single post as JSON |
/api/post?slug=xxx&format=md |
Single post as markdown |
/meta/post?slug=xxx |
Open Graph HTML for crawlers |
How Blog Post Slugs Work
Slugs are defined in the frontmatter of each markdown file:
---
slug: "my-post-slug"
---
The slug becomes the URL path: yourdomain.com/my-post-slug
Rules:
- Slugs must be unique across all posts
- Use lowercase letters, numbers, and hyphens
- The sync script reads the
slugfield from frontmatter - Posts are queried by slug using a Convex index
Theme Configuration
The default theme is Tan. Users can cycle through themes using the toggle:
- Dark (Moon icon)
- Light (Sun icon)
- Tan (Half icon) - default
- Cloud (Cloud icon)
To change the default theme, edit src/context/ThemeContext.tsx:
const DEFAULT_THEME: Theme = "tan"; // Change to "dark", "light", or "cloud"
Font Configuration
The blog uses a serif font (New York) by default. To switch fonts, edit src/styles/global.css:
body {
/* Sans-serif option */
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif;
/* Serif option (default) */
font-family:
"New York",
-apple-system-ui-serif,
ui-serif,
Georgia,
Cambria,
"Times New Roman",
Times,
serif;
}
Replace the font-family property with your preferred font stack.