mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
378 lines
11 KiB
Markdown
378 lines
11 KiB
Markdown
# markdown "sync" site
|
|
|
|
A minimalist markdown site built with React, Convex, and Vite. Optimized for SEO, AI agents, and LLM discovery.
|
|
|
|
**How publishing works:** Write posts in markdown, run `npm run sync` for development or `npm run sync:prod` for production, and they appear on your live site immediately. No rebuild or redeploy needed. Convex handles real-time data sync, so all connected browsers update automatically.
|
|
|
|
## 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
|
|
- Real-time analytics at `/stats`
|
|
- Full text search with Command+K shortcut
|
|
|
|
### SEO and Discovery
|
|
|
|
- RSS feeds at `/rss.xml` and `/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.txt` with AI crawler rules
|
|
- `llms.txt` for 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
|
|
|
|
## Getting Started
|
|
|
|
### Prerequisites
|
|
|
|
- Node.js 18 or higher
|
|
- A Convex account
|
|
|
|
### Setup
|
|
|
|
1. Install dependencies:
|
|
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
2. Initialize Convex:
|
|
|
|
```bash
|
|
npx convex dev
|
|
```
|
|
|
|
This will create your Convex project and generate the `.env.local` file.
|
|
|
|
3. Start the development server:
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
4. Open http://localhost:5173
|
|
|
|
## 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/`:
|
|
|
|
```markdown
|
|
---
|
|
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).
|
|
|
|
```markdown
|
|
---
|
|
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:
|
|
|
|
```yaml
|
|
image: "/images/my-header.png"
|
|
```
|
|
|
|
Recommended dimensions: 1200x630 pixels. Images can be local (`/images/...`) or external URLs.
|
|
|
|
### Inline Images
|
|
|
|
Add images in markdown content:
|
|
|
|
```markdown
|
|

|
|
```
|
|
|
|
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:
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```typescript
|
|
const DEFAULT_OG_IMAGE = "/images/og-default.svg";
|
|
```
|
|
|
|
## Syncing Posts
|
|
|
|
Posts are synced to Convex. The sync script reads markdown files from `content/blog/` and `content/pages/`, then uploads them to your Convex database.
|
|
|
|
### Environment Files
|
|
|
|
| File | Purpose |
|
|
| ----------------------- | -------------------------------------------------------- |
|
|
| `.env.local` | Development deployment URL (created by `npx convex dev`) |
|
|
| `.env.production.local` | Production deployment URL (create manually) |
|
|
|
|
Both files are gitignored. Each developer creates their own.
|
|
|
|
### Sync Commands
|
|
|
|
| Command | Target | When to use |
|
|
| ------------------- | ----------- | --------------------------- |
|
|
| `npm run sync` | Development | Local testing, new posts |
|
|
| `npm run sync:prod` | Production | Deploy content to live site |
|
|
|
|
**Development sync:**
|
|
|
|
```bash
|
|
npm run sync
|
|
```
|
|
|
|
**Production sync:**
|
|
|
|
First, create `.env.production.local` with your production Convex URL:
|
|
|
|
```
|
|
VITE_CONVEX_URL=https://your-prod-deployment.convex.cloud
|
|
```
|
|
|
|
Then sync:
|
|
|
|
```bash
|
|
npm run sync:prod
|
|
```
|
|
|
|
## Deployment
|
|
|
|
### Netlify
|
|
|
|
[](https://app.netlify.com/projects/markdowncms/deploys)
|
|
|
|
For detailed setup, see the [Convex Netlify Deployment Guide](https://docs.convex.dev/production/hosting/netlify).
|
|
|
|
1. Deploy Convex functions to production:
|
|
|
|
```bash
|
|
npx convex deploy
|
|
```
|
|
|
|
Note the production URL (e.g., `https://your-deployment.convex.cloud`).
|
|
|
|
2. Connect your repository to Netlify
|
|
3. Configure build settings:
|
|
- Build command: `npm ci --include=dev && npx convex deploy --cmd 'npm run build'`
|
|
- Publish directory: `dist`
|
|
4. Add environment variables in Netlify dashboard:
|
|
- `CONVEX_DEPLOY_KEY` - Generate from [Convex Dashboard](https://dashboard.convex.dev) > Project Settings > Deploy Key
|
|
- `VITE_CONVEX_URL` - Your production Convex URL (e.g., `https://your-deployment.convex.cloud`)
|
|
|
|
The `CONVEX_DEPLOY_KEY` deploys functions at build time. The `VITE_CONVEX_URL` is required for edge functions (RSS, sitemap, API) to proxy requests at runtime.
|
|
|
|
**Build issues?** Netlify sets `NODE_ENV=production` which skips devDependencies. The `--include=dev` flag fixes this. See [netlify-deploy-fix.md](./netlify-deploy-fix.md) for detailed troubleshooting.
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
markdown-site/
|
|
├── 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
|
|
│ └── edge-functions/
|
|
│ ├── rss.ts # RSS feed proxy
|
|
│ ├── sitemap.ts # Sitemap proxy
|
|
│ ├── api.ts # API endpoint proxy
|
|
│ └── botMeta.ts # OG crawler detection
|
|
├── 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
|
|
```
|
|
|
|
## Scripts Reference
|
|
|
|
| Script | Description |
|
|
| --------------------- | -------------------------------------------- |
|
|
| `npm run dev` | Start Vite dev server |
|
|
| `npm run dev:convex` | Start Convex dev backend |
|
|
| `npm run sync` | Sync posts to dev deployment |
|
|
| `npm run sync:prod` | Sync posts to production deployment |
|
|
| `npm run build` | Build for production |
|
|
| `npm run deploy` | Sync + build (for manual deploys) |
|
|
| `npm run deploy:prod` | Deploy Convex functions + sync to production |
|
|
|
|
## Tech Stack
|
|
|
|
- React 18
|
|
- TypeScript
|
|
- Vite
|
|
- Convex
|
|
- react-markdown
|
|
- react-syntax-highlighter
|
|
- date-fns
|
|
- lucide-react
|
|
- @phosphor-icons/react
|
|
- Netlify
|
|
|
|
## Search
|
|
|
|
Press `Command+K` (Mac) or `Ctrl+K` (Windows/Linux) to open the search modal. The search uses Convex full text search to find posts and pages by title and content.
|
|
|
|
Features:
|
|
|
|
- Real-time results as you type
|
|
- Keyboard navigation (arrow keys, Enter, Escape)
|
|
- Result snippets with context around matches
|
|
- Distinguishes between posts and pages
|
|
- Works with all four themes
|
|
|
|
The search icon appears in the top navigation bar next to the theme toggle.
|
|
|
|
## Real-time Stats
|
|
|
|
The `/stats` page shows real-time analytics powered by Convex:
|
|
|
|
- **Active visitors**: Current visitors on the site with per-page breakdown
|
|
- **Total page views**: All-time view count
|
|
- **Unique visitors**: Based on anonymous session IDs
|
|
- **Views by page**: List of all pages sorted by view count
|
|
|
|
Stats update automatically via Convex subscriptions. No page refresh needed.
|
|
|
|
How it works:
|
|
|
|
- Page views are recorded as event records (not counters) to avoid write conflicts
|
|
- Active sessions use heartbeat presence (30s interval, 2min timeout)
|
|
- A cron job cleans up stale sessions every 5 minutes
|
|
- No PII stored (only anonymous session UUIDs)
|
|
|
|
## API Endpoints
|
|
|
|
| Endpoint | Description |
|
|
| ------------------------------ | ------------------------------- |
|
|
| `/stats` | Real-time site analytics |
|
|
| `/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:
|
|
|
|
```markdown
|
|
---
|
|
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 `slug` field 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`:
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```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.
|
|
|
|
## Source
|
|
|
|
Fork this project: [github.com/waynesutton/markdown-site](https://github.com/waynesutton/markdown-site)
|