Building a Complete GitHub-to-Blog Pipeline: From Memory Files to Published Posts
A deep dive into implementing a full-stack feature that transforms GitHub repository memory files into polished blog posts, complete with project management UI and mobile-first design.
Building a Complete GitHub-to-Blog Pipeline: From Memory Files to Published Posts
Last week, I embarked on building what seemed like a straightforward feature: import memory files from GitHub repositories and transform them into blog posts. What started as a simple integration turned into a comprehensive full-stack implementation that taught me valuable lessons about TypeScript inference, mobile-first design, and the complexity of real-world data pipelines.
The Vision: Seamless Content Creation
The goal was ambitious but clear: create a system where developers could:
- Connect their GitHub repositories
- Import "memory files" (development session notes, project documentation)
- Automatically generate blog posts using AI
- Manage and publish content through a polished interface
Think of it as a bridge between the raw, technical notes developers write during coding sessions and the polished content their audience wants to read.
Architecture Decisions: Building for Scale
Database Design
The foundation started with extending our Prisma schema to support projects and blog posts:
model Project {
id String @id @default(cuid())
name String
githubRepo String?
userId String
tenantId String
blogPosts BlogPost[]
// ... relations and timestamps
}
model BlogPost {
id String @id @default(cuid())
title String
content String @db.Text
status String @default("draft")
projectId String
project Project @relation(fields: [projectId], references: [id])
// ... metadata fields
}
This schema supports multi-tenancy while keeping the relationships clean and queryable.
The GitHub Connector: BYOK Approach
Rather than managing API keys centrally, I implemented a "Bring Your Own Key" (BYOK) approach for GitHub integration:
export class GitHubConnector {
async fetchRepos(token: string): Promise<GitHubRepo[]> {
const response = await fetch('https://api.github.com/user/repos', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/vnd.github.v3+json'
}
});
// ... error handling and parsing
}
async syncToDatabase(
files: MemoryFile[],
projectId: string,
userId: string
): Promise<void> {
// Batch upsert logic with conflict resolution
}
}
This approach gives users full control over their data while reducing our infrastructure complexity.
AI-Powered Content Generation
The blog generation service became a TypeScript port of our existing Python script, leveraging Anthropic's Claude:
export class BlogGenerator {
async generateBlogPost(memoryContent: string): Promise<BlogPost> {
const prompt = `Transform this development session memory into an engaging blog post:
${memoryContent}
Requirements:
- Technical accuracy with narrative flow
- Include code examples where relevant
- Target developer audience
- Add proper frontmatter`;
const response = await this.anthropicProvider.generateText(prompt);
return this.parseFrontmatter(response);
}
}
The key insight here was maintaining consistency between our existing tooling while adapting to the new architecture.
Frontend Challenges: Mobile-First Complexity
The tRPC Type Inference Problem
One of the most frustrating challenges came from TypeScript's interaction with tRPC. When passing query results as props to components, the type inference broke down:
// This didn't work as expected
type GenerateSheetProps = {
unbloggedQuery: ReturnType<typeof trpc.projects.blogPosts.unblogged.useQuery>;
};
// TypeScript couldn't resolve .data?.length or .data?.map()
// The inferred type showed data as {} instead of the expected array
The Solution: Explicit interface definitions over clever type inference:
interface UnbloggedEntry {
id: string;
fileName: string;
lastModified: string;
}
type GenerateSheetProps = {
isLoading: boolean;
data?: UnbloggedEntry[];
};
Lesson Learned: When working with complex type systems, explicit is often better than clever. tRPC's ReturnType inference can be unreliable for nested router queries, especially when passed between components.
Mobile-First UI Components
The project detail page required careful attention to mobile usability:
// Collapsible blog post cards with touch-friendly targets
<Card className="border-l-4 border-l-blue-500">
<CardHeader
className="cursor-pointer pb-2"
onClick={() => setExpanded(!expanded)}
>
<div className="flex items-center justify-between min-h-[44px]">
<CardTitle className="text-sm font-medium line-clamp-2">
{post.title}
</CardTitle>
<ChevronDown className={cn(
"h-4 w-4 transition-transform",
expanded && "rotate-180"
)} />
</div>
</CardHeader>
{expanded && (
<CardContent className="pt-0">
<MarkdownRenderer content={post.content} />
</CardContent>
)}
</Card>
Key mobile considerations:
- 44px minimum touch targets for interactive elements
- Sticky action bars that don't interfere with content
- Progressive disclosure through collapsible sections
- Optimized typography with
@tailwindcss/typography
The Complete Flow: End-to-End Experience
The final implementation supports this user journey:
- Project Creation: Users create a project and connect their GitHub repository
- Memory File Discovery: The system scans for memory files and presents them as checkboxes
- Selective Import: Users choose which files to import and whether to auto-generate blog posts
- Content Management: A tabbed interface shows blog posts, source files, and project settings
- Publishing Workflow: Individual posts can be edited, regenerated, or published
Each step includes proper loading states, error handling, and mobile-optimized interfaces.
Technical Stack Highlights
- Backend: Next.js API routes with tRPC for type-safe APIs
- Database: Prisma with PostgreSQL for robust data modeling
- AI Integration: Anthropic Claude via BYOK pattern
- Frontend: React with Tailwind CSS and shadcn/ui components
- Markdown:
react-markdownwithremark-gfmfor GitHub-flavored markdown
Looking Forward
This implementation opened several interesting possibilities:
- Batch Processing: Generate multiple blog posts from a single import
- Template System: Custom prompts for different content types
- Analytics Integration: Track which memory files produce the best content
- Collaboration Features: Team-based project management
Key Takeaways
- Type Safety vs. Complexity: Sometimes explicit types are clearer than inferred ones
- Mobile-First Design: Touch targets and progressive disclosure are crucial for complex interfaces
- BYOK Architecture: Letting users manage their own API keys reduces infrastructure burden
- AI Integration: Consistency between existing tools and new features improves adoption
Building this pipeline reinforced that the most challenging part of modern web development isn't any single technology—it's orchestrating them all to create a seamless user experience. The technical implementation is just the foundation; the real work is in the details that make the feature feel polished and reliable.
This feature shipped as commit 49e98f3 and represents about 15 files changed across the full stack. The complete implementation took one focused development session, proving that sometimes the best way to understand a problem is to build the entire solution.