Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 210f91a

Browse files
committed
docs: add blog MVP specification for roocode.com/blog
1 parent 17d3456 commit 210f91a

1 file changed

Lines changed: 336 additions & 0 deletions

File tree

apps/web-roo-code/docs/blog.md

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# Blog MVP Specification
2+
3+
> **Canonical URL:** `https://roocode.com/blog`
4+
5+
This document specifies the MVP implementation for the Roo Code marketing blog.
6+
7+
---
8+
9+
## Table of Contents
10+
11+
1. [Overview](#overview)
12+
2. [Folder Structure & Naming Conventions](#folder-structure--naming-conventions)
13+
3. [Frontmatter Schema](#frontmatter-schema)
14+
4. [Publish Gating Rules](#publish-gating-rules)
15+
5. [Rendering Strategy](#rendering-strategy)
16+
6. [Display Rules](#display-rules)
17+
7. [Markdown Rendering Constraints](#markdown-rendering-constraints)
18+
8. [Slug Rules](#slug-rules)
19+
9. [Substack Syndication Checklist](#substack-syndication-checklist)
20+
10. [Containment Rules](#containment-rules)
21+
22+
---
23+
24+
## Overview
25+
26+
The blog lives at `https://roocode.com/blog` as the canonical source. Substack (`https://blog.roocode.com`) serves as a syndication channel with shorter excerpts and links back to the canonical posts.
27+
28+
### Routes
29+
30+
| Route | Description |
31+
| -------------- | ---------------------------------------- |
32+
| `/blog` | Blog index page (lists published posts) |
33+
| `/blog/[slug]` | Individual post page (Article page type) |
34+
35+
---
36+
37+
## Folder Structure & Naming Conventions
38+
39+
### Content Location
40+
41+
```
42+
apps/web-roo-code/
43+
├── content/
44+
│ └── blog/
45+
│ ├── my-first-post.md
46+
│ ├── another-great-article.md
47+
│ └── ...
48+
```
49+
50+
### File Naming
51+
52+
- **Format:** `{slug}.md`
53+
- **Rules:**
54+
- Use lowercase letters, numbers, and hyphens only
55+
- No underscores, spaces, or special characters
56+
- The filename (without `.md`) becomes the URL slug
57+
- Example: `introducing-roo-code-cloud.md``/blog/introducing-roo-code-cloud`
58+
59+
---
60+
61+
## Frontmatter Schema
62+
63+
All blog posts require YAML frontmatter at the top of the Markdown file.
64+
65+
### Required Fields
66+
67+
| Field | Type | Description |
68+
| ----------------- | -------------------------- | ------------------------------------------------ |
69+
| `title` | `string` | Post title (displayed in UI and meta tags) |
70+
| `description` | `string` | Post excerpt/summary (used for SEO and previews) |
71+
| `author` | `string` | Author name |
72+
| `publish_date` | `YYYY-MM-DD` | Publication date in ISO format |
73+
| `publish_time_pt` | `h:mmam/pm` | Publication time in Pacific Time |
74+
| `status` | `"draft"` \| `"published"` | Publication status |
75+
| `slug` | `string` | URL slug (must match filename) |
76+
77+
### Optional Fields
78+
79+
| Field | Type | Description |
80+
| --------------- | ---------- | ------------------------------------------- |
81+
| `cover_image` | `string` | Path to cover image (relative to `/public`) |
82+
| `tags` | `string[]` | List of topic tags |
83+
| `canonical_url` | `string` | Override canonical URL (if cross-posting) |
84+
85+
### Example Frontmatter
86+
87+
```yaml
88+
---
89+
title: "Introducing Roo Code Cloud: AI-Powered Code Review"
90+
description: "Learn how Roo Code Cloud brings intelligent code review to your team's workflow with automated PR analysis and suggestions."
91+
author: "Matt Rubens"
92+
publish_date: 2026-01-29
93+
publish_time_pt: 9:00am
94+
status: published
95+
slug: introducing-roo-code-cloud
96+
cover_image: /images/blog/introducing-roo-code-cloud-cover.png
97+
tags:
98+
- roo-code-cloud
99+
- product-launch
100+
- code-review
101+
---
102+
```
103+
104+
### Time Format Requirements
105+
106+
- **Allowed format:** `h:mmam/pm` (12-hour format)
107+
- **Examples:**
108+
-`9:00am`
109+
-`12:30pm`
110+
-`11:59pm`
111+
-`09:00` (24-hour format not allowed)
112+
-`9:00 AM` (space not allowed)
113+
-`9:00a` (must be `am/pm`, not `a/p`)
114+
115+
---
116+
117+
## Publish Gating Rules
118+
119+
Posts are gated by both date and time in **Pacific Time (PT)**.
120+
121+
### Visibility Logic
122+
123+
A post is **publicly visible** when ALL of the following are true:
124+
125+
1. `status` is `"published"`
126+
2. Current PT date/time is at or past the scheduled publish moment
127+
128+
```typescript
129+
// Pseudocode for publish gating
130+
function isPostVisible(post: BlogPost): boolean {
131+
if (post.status !== "published") return false
132+
133+
const now = getCurrentTimePT()
134+
const publishMoment = parsePublishMoment(post.publish_date, post.publish_time_pt)
135+
136+
return now >= publishMoment
137+
}
138+
139+
// More explicitly:
140+
// now_pt_date > publish_date
141+
// OR (now_pt_date === publish_date AND now_pt_minutes >= publish_time_pt_minutes)
142+
```
143+
144+
### Key Behavior
145+
146+
- **No deploy required for time-gating:** Once a post is merged and deployed with `status: published` and a future `publish_date`/`publish_time_pt`, it will automatically become visible at the scheduled time.
147+
- **Adding new posts still requires deploy:** Creating a brand-new post file requires merge and deploy to make it available.
148+
- **Draft posts never visible:** Posts with `status: "draft"` are not rendered on any public page.
149+
150+
---
151+
152+
## Rendering Strategy
153+
154+
### Dynamic SSR (Required)
155+
156+
Blog routes **must** use Server-Side Rendering (SSR) to evaluate publish gating at request time.
157+
158+
```typescript
159+
// In Next.js App Router
160+
export const dynamic = "force-dynamic"
161+
export const runtime = "nodejs" // Required for filesystem access
162+
```
163+
164+
### Why Dynamic?
165+
166+
- Enables time-based publish gating without redeployment
167+
- Posts automatically appear at their scheduled publish time
168+
- No ISR/static generation (would require revalidation)
169+
170+
### Runtime Requirements
171+
172+
- **Node.js runtime** (not Edge) - required for filesystem reads of Markdown content
173+
- Configure in route files:
174+
```typescript
175+
export const runtime = "nodejs"
176+
```
177+
178+
---
179+
180+
## Display Rules
181+
182+
### Date Display
183+
184+
- **Format:** `Posted YYYY-MM-DD` (date only, no time)
185+
- **Timezone:** Pacific Time (PT)
186+
- **Example:** `Posted 2026-01-29`
187+
188+
### No Time Display
189+
190+
The publication time is used internally for gating but is **never shown to users**.
191+
192+
---
193+
194+
## Markdown Rendering Constraints
195+
196+
### Markdown Only
197+
198+
-Standard Markdown syntax
199+
-GFM (GitHub Flavored Markdown) extensions
200+
-**No raw HTML** - HTML tags in content are stripped/escaped
201+
202+
### Allowed Syntax
203+
204+
- Headings (`#`, `##`, etc.)
205+
- Paragraphs and line breaks
206+
- Bold, italic, strikethrough
207+
- Links and images (Markdown syntax only)
208+
- Code blocks and inline code
209+
- Ordered and unordered lists
210+
- Blockquotes
211+
- Tables (GFM)
212+
- Horizontal rules
213+
214+
### Disallowed
215+
216+
- `<div>`, `<span>`, `<script>`, or any HTML tags
217+
- Inline styles
218+
- Custom components (unless explicitly supported)
219+
220+
---
221+
222+
## Slug Rules
223+
224+
### Format
225+
226+
Slugs must match the pattern: `^[a-z0-9]+(?:-[a-z0-9]+)*$`
227+
228+
- Lowercase letters and numbers only
229+
- Words separated by single hyphens
230+
- No leading/trailing hyphens
231+
- No consecutive hyphens
232+
233+
### Examples
234+
235+
| Valid | Invalid |
236+
| --------------- | -------------------------------------- |
237+
| `hello-world` | `Hello-World` (uppercase) |
238+
| `post-123` | `post_123` (underscore) |
239+
| `a` | `-hello` (leading hyphen) |
240+
| `my-great-post` | `my--great-post` (consecutive hyphens) |
241+
242+
### Uniqueness
243+
244+
- Slugs must be unique across all posts
245+
- Duplicate slugs must fail fast with a clear error during build/dev
246+
- Error message should identify both conflicting files
247+
248+
---
249+
250+
## Substack Syndication Checklist
251+
252+
When publishing a new post, follow this checklist for Substack syndication:
253+
254+
### Before Publishing
255+
256+
- [ ] Post is live on `roocode.com/blog/[slug]`
257+
- [ ] All images are accessible and loading correctly
258+
- [ ] Meta tags and OG image are rendering
259+
260+
### Substack Post Creation
261+
262+
- [ ] Create new post on Substack (`blog.roocode.com`)
263+
- [ ] Use shortened excerpt (1-2 paragraphs max)
264+
- [ ] Add prominent link back to canonical: `Read the full article at roocode.com/blog/[slug]`
265+
- [ ] Include canonical URL in Substack post settings (if available)
266+
- [ ] Match publish date with canonical post
267+
268+
### Post-Publish Verification
269+
270+
- [ ] Substack post links correctly to canonical
271+
- [ ] Email notification (if enabled) includes canonical link
272+
- [ ] Social preview shows canonical URL
273+
274+
---
275+
276+
## Containment Rules
277+
278+
Changes for the blog feature should be **contained** to minimize impact on existing site functionality.
279+
280+
### In Scope
281+
282+
| Area | Details |
283+
| ----------- | -------------------------------------------------- |
284+
| Blog routes | `app/blog/` directory (index + dynamic slug) |
285+
| Content | `content/blog/` directory |
286+
| Navigation | Add blog link to nav bar and footer |
287+
| Sitemap | Add blog URLs to sitemap generation |
288+
| Analytics | Track blog page views (existing PostHog/GTM setup) |
289+
| SEO | Add structured data for Article page type |
290+
291+
### Out of Scope
292+
293+
| Area | Notes |
294+
| -------------- | ------------------------------ |
295+
| Homepage | No blog preview widget in MVP |
296+
| Other routes | No changes to existing pages |
297+
| Authentication | Blog is fully public |
298+
| Comments | Not in MVP |
299+
| Search | Not in MVP |
300+
| RSS feed | Not in MVP (consider post-MVP) |
301+
302+
### Glue Code
303+
304+
Minimal integration points:
305+
306+
1. **Navigation:** Add "Blog" link to `components/chromes/nav-bar.tsx` and `components/chromes/footer.tsx`
307+
2. **Constants:** Update `EXTERNAL_LINKS.BLOG` to internal `/blog` path (or add `INTERNAL_LINKS.BLOG`)
308+
3. **Sitemap:** Add blog URLs to `app/robots.ts` and sitemap generation
309+
4. **Structured Data:** Add Article schema to blog post pages
310+
311+
---
312+
313+
## Implementation Checklist
314+
315+
For implementers, ensure the following:
316+
317+
- [ ] Create `content/blog/` directory
318+
- [ ] Create `app/blog/page.tsx` (index)
319+
- [ ] Create `app/blog/[slug]/page.tsx` (post)
320+
- [ ] Implement Markdown parsing with frontmatter
321+
- [ ] Implement publish gating logic (PT timezone)
322+
- [ ] Add slug validation with duplicate detection
323+
- [ ] Configure dynamic SSR with Node.js runtime
324+
- [ ] Strip/escape HTML from Markdown content
325+
- [ ] Add navigation links
326+
- [ ] Update sitemap
327+
- [ ] Add Article structured data
328+
- [ ] Write tests for publish gating logic
329+
330+
---
331+
332+
## References
333+
334+
- **Issue:** MKT-66
335+
- **Current external blog:** `EXTERNAL_LINKS.BLOG` in `src/lib/constants.ts`
336+
- **First post content:** See MKT-73 for source content

0 commit comments

Comments
 (0)