17 KiB
title, description, date, slug, published, tags, readTime
| title | description | date | slug | published | tags | readTime | ||||
|---|---|---|---|---|---|---|---|---|---|---|
| Fork and Deploy Your Own Markdown Blog | Step-by-step guide to fork this blog, set up Convex backend, and deploy to Netlify in under 10 minutes. | 2025-01-14 | setup-guide | true |
|
8 min read |
Fork and Deploy Your Own Markdown Blog
This guide walks you through forking this markdown site, setting up your Convex backend, and deploying to Netlify. The entire process takes about 10 minutes.
How publishing works: Once deployed, you write posts in markdown, run npm run sync, 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.
Table of Contents
- Fork and Deploy Your Own Markdown Blog
- Table of Contents
- Prerequisites
- Step 1: Fork the Repository
- Step 2: Set Up Convex
- Step 3: Sync Your Blog Posts
- Step 4: Run Locally
- Step 5: Get Your Convex HTTP URL
- Step 6: Verify Edge Functions
- Step 7: Deploy to Netlify
- Step 8: Set Up Production Convex
- Writing Blog Posts
- Customizing Your Blog
- API Endpoints
- Troubleshooting
- Project Structure
- Next Steps
Prerequisites
Before you start, make sure you have:
- Node.js 18 or higher installed
- A GitHub account
- A Convex account (free at convex.dev)
- A Netlify account (free at netlify.com)
Step 1: Fork the Repository
Fork the repository to your GitHub account:
# Clone your forked repo
git clone https://github.com/waynesutton/markdown-site.git
cd markdown-site
# Install dependencies
npm install
Step 2: Set Up Convex
Convex is the backend that stores your blog posts and serves the API endpoints.
Create a Convex Project
Run the Convex development command:
npx convex dev
This will:
- Prompt you to log in to Convex (opens browser)
- Ask you to create a new project or select an existing one
- Generate a
.env.localfile with yourVITE_CONVEX_URL
Keep this terminal running during development. It syncs your Convex functions automatically.
Verify the Schema
The schema is already defined in convex/schema.ts:
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
posts: defineTable({
slug: v.string(),
title: v.string(),
description: v.string(),
content: v.string(),
date: v.string(),
published: v.boolean(),
tags: v.array(v.string()),
readTime: v.optional(v.string()),
lastSyncedAt: v.optional(v.number()),
})
.index("by_slug", ["slug"])
.index("by_published", ["published"]),
viewCounts: defineTable({
slug: v.string(),
count: v.number(),
}).index("by_slug", ["slug"]),
});
Step 3: Sync Your Blog Posts
Blog posts live in content/blog/ as markdown files. Sync them to Convex:
npm run sync
This reads all markdown files, parses the frontmatter, and uploads them to your Convex database.
Step 4: Run Locally
Start the development server:
npm run dev
Open http://localhost:5173 to see your blog.
Step 5: Get Your Convex HTTP URL
Your Convex deployment has two URLs:
- Client URL:
https://your-deployment.convex.cloud(for the React app) - HTTP URL:
https://your-deployment.convex.site(for API endpoints)
Find your deployment name in the Convex dashboard or check .env.local:
# Your .env.local contains something like:
VITE_CONVEX_URL=https://happy-animal-123.convex.cloud
The HTTP URL uses .convex.site instead of .convex.cloud:
https://happy-animal-123.convex.site
Step 6: Verify Edge Functions
The blog uses Netlify Edge Functions to dynamically proxy RSS, sitemap, and API requests to your Convex HTTP endpoints. No manual URL configuration is needed.
Edge functions in netlify/edge-functions/:
rss.ts- Proxies/rss.xmland/rss-full.xmlsitemap.ts- Proxies/sitemap.xmlapi.ts- Proxies/api/postsand/api/postbotMeta.ts- Serves Open Graph HTML to social media crawlers
These functions automatically read VITE_CONVEX_URL from your environment and convert it to the Convex HTTP site URL (.cloud becomes .site).
Step 7: Deploy to Netlify
For detailed Convex + Netlify integration, see the official Convex Netlify Deployment Guide.
Option A: Netlify CLI
# Install Netlify CLI
npm install -g netlify-cli
# Login to Netlify
netlify login
# Initialize site
netlify init
# Deploy
npm run deploy
Option B: Netlify Dashboard
- Go to app.netlify.com
- Click "Add new site" then "Import an existing project"
- Connect your GitHub repository
- Configure build settings:
- Build command:
npm ci --include=dev && npx convex deploy --cmd 'npm run build' - Publish directory:
dist
- Build command:
- Add environment variables:
CONVEX_DEPLOY_KEY: Generate from Convex Dashboard > Project Settings > Deploy KeyVITE_CONVEX_URL: Your production Convex URL (e.g.,https://your-deployment.convex.cloud)
- Click "Deploy site"
The CONVEX_DEPLOY_KEY deploys functions at build time. The VITE_CONVEX_URL is required for edge functions to proxy RSS, sitemap, and API requests at runtime.
Netlify Build Configuration
The netlify.toml file includes the correct build settings:
[build]
command = "npm ci --include=dev && npx convex deploy --cmd 'npm run build'"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
Key points:
npm ci --include=devforces devDependencies to install even whenNODE_ENV=production- The build script uses
npx vite buildto resolve vite from node_modules @types/nodeis required for TypeScript to recognizeprocess.env
Step 8: Set Up Production Convex
For production, deploy your Convex functions:
npx convex deploy
This creates a production deployment. Update your Netlify environment variable with the production URL if different.
Writing Blog Posts
Create new posts in content/blog/:
---
title: "Your Post Title"
description: "A brief description for SEO and social sharing"
date: "2025-01-15"
slug: "your-post-url"
published: true
tags: ["tag1", "tag2"]
readTime: "5 min read"
image: "/images/my-post-image.png"
---
Your markdown content here...
Frontmatter Fields
| Field | Required | Description |
|---|---|---|
title |
Yes | Post title |
description |
Yes | Short description for SEO |
date |
Yes | Publication date (YYYY-MM-DD) |
slug |
Yes | URL path (must be unique) |
published |
Yes | Set to true to publish |
tags |
Yes | Array of topic tags |
readTime |
No | Estimated reading time |
image |
No | Header/Open Graph image URL |
Adding Images
Place images in public/images/ and reference them in your posts:
Header/OG Image (in frontmatter):
image: "/images/my-header.png"
This image appears when sharing on social media. Recommended: 1200x630 pixels.
Inline Images (in content):

External Images:

Sync After Adding Posts
After adding or editing posts, sync to Convex.
Development sync:
npm run sync
Production sync:
First, create .env.production.local in your project root:
VITE_CONVEX_URL=https://your-prod-deployment.convex.cloud
Get your production URL from the Convex Dashboard by selecting your project and switching to the Production deployment.
Then sync:
npm run sync:prod
Environment Files
| File | Purpose | Created by |
|---|---|---|
.env.local |
Dev deployment URL | npx convex dev (automatic) |
.env.production.local |
Prod deployment URL | You (manual) |
Both files are gitignored. Each developer creates their own local environment files.
Customizing Your Blog
Change the Favicon
Replace public/favicon.svg with your own SVG icon. The default is a rounded square with the letter "m":
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<rect x="32" y="32" width="448" height="448" rx="96" ry="96" fill="#000000"/>
<text x="256" y="330" text-anchor="middle" font-size="300" font-weight="800" fill="#ffffff">m</text>
</svg>
To use a different letter or icon, edit the SVG directly or replace the file.
Change the Site Logo
The logo appears on the homepage. Edit src/pages/Home.tsx:
const siteConfig = {
logo: "/images/logo.svg", // Set to null to hide the logo
// ...
};
Replace public/images/logo.svg with your own logo file. Recommended: SVG format, 512x512 pixels.
Change the Default Open Graph Image
The default OG image is used when a post does not have an image field in its frontmatter. Replace public/images/og-default.svg with your own image.
Recommended dimensions: 1200x630 pixels. Supported formats: PNG, JPG, or SVG.
Update the reference in src/pages/Post.tsx:
const DEFAULT_OG_IMAGE = "/images/og-default.svg";
Update Site Configuration
Edit src/pages/Home.tsx to customize:
const siteConfig = {
name: "Your Name",
title: "Your Title",
intro: "Your introduction...",
bio: "Your bio...",
featuredEssays: [{ title: "Post Title", slug: "post-slug" }],
links: {
github: "https://github.com/waynesutton/markdown-site",
twitter: "https://twitter.com/yourusername",
},
};
Change the Default Theme
Edit src/context/ThemeContext.tsx:
const DEFAULT_THEME: Theme = "tan"; // Options: "dark", "light", "tan", "cloud"
Change the Font
The blog uses a serif font by default. To switch to sans-serif, edit src/styles/global.css:
body {
/* Sans-serif */
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/* Serif (default) */
font-family:
"New York",
-apple-system-ui-serif,
ui-serif,
Georgia,
serif;
}
Add Static Pages (Optional)
Create optional pages like About, Projects, or Contact. These appear as navigation links in the top right corner.
- Create a
content/pages/directory - Add markdown files with frontmatter:
---
title: "About"
slug: "about"
published: true
order: 1
---
Your page content here...
| Field | Required | Description |
|---|---|---|
title |
Yes | Page title (shown in nav) |
slug |
Yes | URL path (e.g., /about) |
published |
Yes | Set true to show |
order |
No | Display order (lower = first) |
- Run
npm run syncto sync pages
Pages appear automatically in the navigation when published.
Update SEO Meta Tags
Edit index.html to update:
- Site title
- Meta description
- Open Graph tags
- JSON-LD structured data
Update llms.txt and robots.txt
Edit public/llms.txt and public/robots.txt with your site information.
API Endpoints
Your blog includes these API endpoints for search engines and AI:
| Endpoint | Description |
|---|---|
/rss.xml |
RSS feed with descriptions |
/rss-full.xml |
RSS feed with full 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 raw markdown |
Troubleshooting
Posts not appearing
- Check that
published: truein frontmatter - Run
npm run syncto sync posts to development - Run
npm run sync:prodto sync posts to production - Verify posts exist in Convex dashboard
RSS/Sitemap not working
- Verify
VITE_CONVEX_URLis set in Netlify environment variables - Check that Convex HTTP endpoints are deployed (
npx convex deploy) - Test the Convex HTTP URL directly:
https://your-deployment.convex.site/rss.xml - Verify edge functions exist in
netlify/edge-functions/
Build failures on Netlify
Common errors and fixes:
"vite: not found" or "Cannot find package 'vite'"
Netlify sets NODE_ENV=production which skips devDependencies. Fix by using npm ci --include=dev in your build command:
[build]
command = "npm ci --include=dev && npx convex deploy --cmd 'npm run build'"
Also ensure your build script uses npx:
"build": "npx vite build"
"Cannot find name 'process'"
Add @types/node to devDependencies:
npm install --save-dev @types/node
General checklist:
- Verify
CONVEX_DEPLOY_KEYenvironment variable is set in Netlify - Check that
@types/nodeis in devDependencies - Ensure Node.js version is 20 or higher
- Verify build command includes
--include=dev
See netlify-deploy-fix.md for detailed troubleshooting.
Project Structure
markdown-site/
├── content/blog/ # Markdown blog posts
├── convex/ # Convex backend functions
│ ├── http.ts # HTTP endpoints
│ ├── posts.ts # Post queries/mutations
│ ├── rss.ts # RSS feed generation
│ └── schema.ts # Database schema
├── netlify/
│ └── edge-functions/ # Netlify edge functions
│ ├── rss.ts # RSS proxy
│ ├── sitemap.ts # Sitemap proxy
│ ├── api.ts # API proxy
│ └── botMeta.ts # OG crawler detection
├── public/ # Static assets
│ ├── robots.txt # Crawler rules
│ └── llms.txt # AI agent discovery
├── src/
│ ├── components/ # React components
│ ├── context/ # Theme context
│ ├── pages/ # Page components
│ └── styles/ # Global CSS
├── netlify.toml # Netlify configuration
└── package.json # Dependencies
Next Steps
After deploying:
- Add your own blog posts
- Customize the theme colors in
global.css - Update the featured essays list
- Submit your sitemap to Google Search Console
- Share your first post
Your blog is now live with real-time updates, SEO optimization, and AI-friendly APIs. Every time you sync new posts, they appear immediately without redeploying.