Workshop: Blog Platform (2 hours) β
Build a complete blog platform with content management, user authentication, comments, and SEO optimization. This hands-on workshop demonstrates real-world application development with Orchestre.
Workshop Overview β
Duration: 2 hours Difficulty: Intermediate Result: Production-ready blog platform
You'll build:
- π Rich text editor with markdown support
- π€ User authentication and profiles
- π¬ Nested commenting system
- π SEO optimization
- π Analytics dashboard
- π Deployed to production
Prerequisites β
- Orchestre installed
- Basic web development knowledge
- 2 hours of focused time
Part 1: Project Setup (15 minutes) β
Initialize the Blog β
bash
/create techblog makerkit-nextjs
cd techblogInitial Planning β
bash
/orchestrate "Build a modern tech blog with:
- Markdown-based writing with live preview
- Categories and tags
- User profiles with author pages
- Comments with moderation
- SEO optimization for all pages
- RSS feed
- Search functionality"Configure Environment β
bash
# Copy environment template
cp .env.example .env.local
# Update with your values
# DATABASE_URL=your-database-url
# NEXTAUTH_SECRET=generate-secret-keyPart 2: Content Management (30 minutes) β
Data Model β
bash
/execute-task "Create blog data model with posts, categories, tags, and authors"This creates:
prisma
model Post {
id String @id @default(cuid())
slug String @unique
title String
excerpt String?
content String @db.Text
published Boolean @default(false)
publishedAt DateTime?
authorId String
author User @relation(fields: [authorId], references: [id])
category Category? @relation(fields: [categoryId], references: [id])
categoryId String?
tags Tag[]
comments Comment[]
views Int @default(0)
readTime Int? // in minutes
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([slug])
@@index([published, publishedAt])
@@index([authorId])
}
model Category {
id String @id @default(cuid())
name String @unique
slug String @unique
posts Post[]
}
model Tag {
id String @id @default(cuid())
name String @unique
slug String @unique
posts Post[]
}
model Comment {
id String @id @default(cuid())
content String
postId String
post Post @relation(fields: [postId], references: [id])
authorId String
author User @relation(fields: [authorId], references: [id])
parentId String?
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
replies Comment[] @relation("CommentReplies")
createdAt DateTime @default(now())
}Rich Text Editor β
bash
/execute-task "Create markdown editor with live preview and image upload"Key features implemented:
- Markdown toolbar
- Live preview pane
- Image drag-and-drop
- Code syntax highlighting
- Auto-save draft
Publishing Workflow β
bash
/execute-task "Implement post publishing workflow with draft, scheduled, and published states"Part 3: User Features (30 minutes) β
Author Profiles β
bash
/execute-task "Create author profile pages with bio, social links, and post list"Comment System β
bash
/execute-task "Create nested comment system with real-time updates"Features:
- Nested replies
- Markdown support
- Edit/delete own comments
- Moderation queue
- Real-time updates via WebSocket
User Dashboard β
bash
/execute-task "Create author dashboard with post management and analytics"Dashboard includes:
- Post list with status
- View statistics
- Comment notifications
- Draft management
Part 4: SEO & Performance (30 minutes) β
SEO Optimization β
bash
/execute-task "Implement comprehensive SEO with meta tags, schema.org, and sitemap"Implementation includes:
typescript
// Dynamic meta tags
export async function generateMetadata({ params }) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.featuredImage],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author.name]
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.featuredImage]
},
alternates: {
canonical: `/blog/${post.slug}`
}
}
}
// Schema.org structured data
const articleSchema = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: post.featuredImage,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: {
'@type': 'Person',
name: post.author.name,
url: `/authors/${post.author.username}`
}
}Performance Optimization β
bash
/performance-check
/execute-task "Optimize images, implement lazy loading, and add caching"Search Implementation β
bash
/implement-search "Full-text search across posts with filters"Part 5: Advanced Features (30 minutes) β
RSS Feed β
bash
/execute-task "Generate RSS feed for blog posts"Analytics β
bash
/execute-task "Add view tracking and reading time analytics"Categories & Tags β
bash
/execute-task "Build category and tag pages with post filtering"Related Posts β
bash
/execute-task "Implement related posts algorithm based on tags and content"Part 6: Deployment (15 minutes) β
Production Checklist β
bash
/validate-implementation "blog platform production readiness"Deploy β
bash
/deploy-productionPost-Deployment β
bash
# Verify deployment
/execute-task "Create post-deployment checklist and monitoring"Complete Code Examples β
1. Markdown Editor Component β
typescript
// components/MarkdownEditor.tsx
'use client'
import { useState, useCallback } from 'react'
import dynamic from 'next/dynamic'
import { Button } from '@/components/ui/button'
import { ImageUpload } from './ImageUpload'
const MDEditor = dynamic(
() => import('@uiw/react-md-editor').then(mod => mod.default),
{ ssr: false }
)
export function MarkdownEditor({
initialValue = '',
onChange,
onSave
}) {
const [content, setContent] = useState(initialValue)
const [saving, setSaving] = useState(false)
const handleImageUpload = useCallback(async (file: File) => {
const formData = new FormData()
formData.append('image', file)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
})
const { url } = await response.json()
const imageMarkdown = ``
setContent(prev => prev + '\n' + imageMarkdown + '\n')
}, [])
const handleSave = async () => {
setSaving(true)
await onSave(content)
setSaving(false)
}
return (
<div className="markdown-editor">
<div className="toolbar">
<ImageUpload onUpload={handleImageUpload} />
<Button
onClick={handleSave}
disabled={saving}
>
{saving ? 'Saving...' : 'Save Draft'}
</Button>
</div>
<MDEditor
value={content}
onChange={(val) => {
setContent(val || '')
onChange?.(val || '')
}}
preview="live"
height={500}
/>
</div>
)
}2. Comment System β
typescript
// components/Comments.tsx
export function Comments({ postId }) {
const [comments, setComments] = useState<Comment[]>([])
const { user } = useAuth()
// Real-time updates
useEffect(() => {
const ws = new WebSocket(process.env.NEXT_PUBLIC_WS_URL)
ws.on(`comments:${postId}`, (comment) => {
setComments(prev => [...prev, comment])
})
return () => ws.close()
}, [postId])
const handleSubmit = async (content: string, parentId?: string) => {
const comment = await createComment({
postId,
content,
parentId
})
// Optimistic update
setComments(prev => [...prev, comment])
}
return (
<div className="comments">
<h3>Comments ({comments.length})</h3>
{user && (
<CommentForm onSubmit={handleSubmit} />
)}
<CommentList
comments={comments}
onReply={handleSubmit}
/>
</div>
)
}3. SEO Component β
typescript
// components/SEO.tsx
export function BlogSEO({ post }: { post: Post }) {
const structuredData = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: post.featuredImage,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: {
'@type': 'Person',
name: post.author.name,
url: `/authors/${post.author.username}`
},
publisher: {
'@type': 'Organization',
name: 'TechBlog',
logo: {
'@type': 'ImageObject',
url: '/logo.png'
}
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://techblog.com/blog/${post.slug}`
}
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData)
}}
/>
</>
)
}Testing Your Blog β
Create Test Content β
Create Categories:
- Technology
- Tutorials
- News
Write Test Posts:
- "Getting Started with Orchestre"
- "Building Modern Web Apps"
- "Performance Optimization Tips"
Test Features:
- [ ] Create and publish post
- [ ] Upload images
- [ ] Add comments
- [ ] Search functionality
- [ ] RSS feed
- [ ] Author pages
- [ ] Category filtering
Extending the Blog β
Additional Features β
bash
# Add newsletter
/execute-task "Add newsletter subscription with email campaigns"
# Add social sharing
/execute-task "Add social media sharing buttons with OpenGraph"
# Add code playground
/execute-task "Embed runnable code examples in posts"
# Add series
/execute-task "Create post series feature for multi-part tutorials"Performance Metrics β
Expected performance:
- Lighthouse Score: 95+
- First Contentful Paint: <1.5s
- Time to Interactive: <3s
- SEO Score: 100
Troubleshooting β
Common Issues β
- Build errors: Check environment variables
- Database connection: Verify DATABASE_URL
- Image uploads: Check storage permissions
- SEO not working: Verify production URL
What You've Built β
β Full-featured blog platform β Rich content management β User engagement features β SEO optimized β Production deployed
Next Steps β
Your blog is ready! Consider:
- Customize Design: Update theme to match brand
- Add Features: Newsletter, podcasts, videos
- Monetization: Add subscriptions or ads
- Analytics: Integrate advanced analytics
- Content: Start writing great content!
Resources β
Congratulations! You've built a production-ready blog platform in just 2 hours!
