diff --git a/README.md b/README.md index 1487317..8c5411b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Markdown Site +# markdown "sync" site A minimalist markdown site built with React, Convex, and Vite. Optimized for SEO, AI agents, and LLM discovery. diff --git a/content/blog/about-this-blog.md b/content/blog/about-this-blog.md index 1362533..8aa0011 100644 --- a/content/blog/about-this-blog.md +++ b/content/blog/about-this-blog.md @@ -72,9 +72,11 @@ The setup takes about 10 minutes: 1. Fork the repo 2. Run `npx convex dev` to set up your backend -3. Run `npm run sync` to upload posts +3. Run `npm run sync` to upload posts (development) or `npm run sync:prod` (production) 4. Deploy to Netlify +**Development vs Production:** Use `npm run sync` when testing locally against your dev Convex deployment. Use `npm run sync:prod` when deploying content to your live production site. + Read the [setup guide](/setup-guide) for detailed steps. ## Customization diff --git a/content/blog/markdown-with-code-examples.md b/content/blog/markdown-with-code-examples.md index 55f81b6..d725b4f 100644 --- a/content/blog/markdown-with-code-examples.md +++ b/content/blog/markdown-with-code-examples.md @@ -85,9 +85,12 @@ npm install # Start development server npm run dev -# Sync posts to Convex +# Sync posts to Convex (development) npm run sync +# Sync posts to Convex (production) +npm run sync:prod + # Deploy to production npm run deploy ``` @@ -114,12 +117,13 @@ Reference files with inline code: `convex/schema.ts`, `src/pages/Home.tsx`. ## Tables -| Command | Description | -| ---------------- | ------------------------ | -| `npm run dev` | Start development server | -| `npm run build` | Build for production | -| `npm run sync` | Sync markdown to Convex | -| `npx convex dev` | Start Convex dev server | +| Command | Description | +| -------------------- | ------------------------------ | +| `npm run dev` | Start development server | +| `npm run build` | Build for production | +| `npm run sync` | Sync markdown to Convex (dev) | +| `npm run sync:prod` | Sync markdown to Convex (prod) | +| `npx convex dev` | Start Convex dev server | ## Lists @@ -177,5 +181,5 @@ content/blog/ 1. Keep slugs URL-friendly (lowercase, hyphens) 2. Set `published: false` for drafts -3. Run `npm run sync` after adding posts +3. Run `npm run sync` after adding posts (or `npm run sync:prod` for production) 4. Use descriptive titles for SEO diff --git a/content/blog/setup-guide.md b/content/blog/setup-guide.md index acc065c..b7cd5f6 100644 --- a/content/blog/setup-guide.md +++ b/content/blog/setup-guide.md @@ -12,7 +12,7 @@ readTime: "8 min read" This guide walks you through forking [this markdown site](https://github.com/waynesutton/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. +**How publishing works:** Once deployed, you 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. ## Table of Contents @@ -47,6 +47,7 @@ This guide walks you through forking [this markdown site](https://github.com/way - [Add Static Pages (Optional)](#add-static-pages-optional) - [Update SEO Meta Tags](#update-seo-meta-tags) - [Update llms.txt and robots.txt](#update-llmstxt-and-robotstxt) + - [Real-time Stats](#real-time-stats) - [API Endpoints](#api-endpoints) - [Troubleshooting](#troubleshooting) - [Posts not appearing](#posts-not-appearing) diff --git a/convex/stats.ts b/convex/stats.ts index d4586d8..6b3920f 100644 --- a/convex/stats.ts +++ b/convex/stats.ts @@ -104,6 +104,7 @@ export const getStats = query({ uniqueVisitors: v.number(), publishedPosts: v.number(), publishedPages: v.number(), + trackingSince: v.union(v.number(), v.null()), pageStats: v.array( v.object({ path: v.string(), @@ -133,8 +134,15 @@ export const getStats = query({ .map(([path, count]) => ({ path, count })) .sort((a, b) => b.count - a.count); - // Get all page views - const allViews = await ctx.db.query("pageViews").collect(); + // Get all page views ordered by timestamp to find earliest + const allViews = await ctx.db + .query("pageViews") + .withIndex("by_timestamp") + .order("asc") + .collect(); + + // Get tracking start date (earliest view timestamp) + const trackingSince = allViews.length > 0 ? allViews[0].timestamp : null; // Aggregate views by path and count unique sessions const viewsByPath: Record = {}; @@ -197,6 +205,7 @@ export const getStats = query({ uniqueVisitors: uniqueSessions.size, publishedPosts: posts.length, publishedPages: pages.length, + trackingSince, pageStats, }; }, diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index a1bbb3a..c8ba380 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -4,7 +4,7 @@ import PostList from "../components/PostList"; // Site configuration - customize this for your site const siteConfig = { - name: "Markdown Site", + name: 'markdown "sync" site', title: "Real-time Site with Convex", // Optional logo/header image (place in public/images/, set to null to hide) logo: "/images/logo.svg" as string | null, diff --git a/src/pages/Stats.tsx b/src/pages/Stats.tsx index e222608..88672ce 100644 --- a/src/pages/Stats.tsx +++ b/src/pages/Stats.tsx @@ -10,6 +10,25 @@ import { Activity, } from "lucide-react"; +// Site launched Dec 14, 2025 at 1:00 PM (v1.0.0), stats added same day (v1.2.0) +const SITE_LAUNCH_DATE = "Dec 14, 2025 at 1:00 PM"; + +// Format tracking start date with time +function formatTrackingDate(timestamp: number | null): string { + if (!timestamp) return "No data yet"; + const date = new Date(timestamp); + const dateStr = date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); + const timeStr = date.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + }); + return `${dateStr} at ${timeStr}`; +} + export default function Stats() { const navigate = useNavigate(); const stats = useQuery(api.stats.getStats); @@ -56,7 +75,10 @@ export default function Stats() { Total Views
{stats.totalPageViews}
-
All-time page views
+
+ Since {formatTrackingDate(stats.trackingSince)} +
+
Site launched {SITE_LAUNCH_DATE}
{/* Unique visitors card */} @@ -131,4 +153,3 @@ export default function Stats() { ); } - diff --git a/src/styles/global.css b/src/styles/global.css index 26c7c75..a83248d 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1025,6 +1025,13 @@ body { color: var(--text-secondary); } +.stat-card-note { + font-size: 11px; + color: var(--text-secondary); + opacity: 0.7; + margin-top: 4px; +} + /* Stats sections */ .stats-section { margin-bottom: 40px;