Architecture Overview
Deep dive into Portfolio OS system architecture and design patterns
Overview
Deep dive into Portfolio OS system architecture and design patterns
Portfolio OS is built using modern web development best practices with a focus on type safety, performance, and developer experience. This overview highlights how the different apps, packages, and services interact so new contributors can quickly orient themselves before diving into specific implementation guides.
System Architecture
Monorepo Architecture
Portfolio OS uses a monorepo structure with Turborepo and PNPM workspaces to manage multiple applications and shared packages.
Note:
Looking for a quick reference? Jump to Monorepo Structure for a deep dive into workspace tooling, or stay here for the high-level architecture context.
Directory Structure
Application Layer
Site App (apps/site)
The public-facing portfolio and blog application.
Key Features:
- Server-side rendering (SSR) for SEO
- Blog content from Hashnode
- Projects showcase with case studies
- Contact form with email integration
- Redis caching for performance
Tech Stack:
- Next.js 14 App Router
- React Server Components
- Tailwind CSS + Radix UI
- Hashnode GraphQL API
Routes:
/- Homepage with hero and featured content/blog- Blog post listing/blog/[slug]- Individual blog post/projects- Projects showcase/projects/[slug]- Project case study/about- About page/api/*- API routes (contact, webhooks)
Dashboard App (apps/dashboard)
Admin interface for content management and analytics.
Key Features:
- Content management (CRUD operations)
- Media library with Vercel Blob storage
- Analytics dashboard
- TipTap rich text editor
- Database management with Prisma
Tech Stack:
- Next.js 14 App Router
- Prisma ORM
- SQLite (dev) / PostgreSQL (prod)
- Vercel Blob storage
- TipTap editor
Routes:
/dashboard- Main dashboard/dashboard/posts- Manage posts/dashboard/media- Media library/dashboard/analytics- Analytics view/api/*- API routes for CRUD operations
Docs App (apps/docs)
Documentation site for developers and users.
Key Features:
- MDX-powered documentation
- Syntax highlighting
- Search functionality
- Component playground
Package Architecture
UI Package (packages/ui)
Shared React components using Radix UI primitives.
Components:
Button- Accessible button componentCard- Content card with variantsInput- Form input with validationDialog- Modal dialogsToast- Notifications
Example:
// packages/ui/src/button.tsx
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
outline: "border border-input",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3",
lg: "h-11 px-8",
},
},
}
)
export interface ButtonProps extends VariantProps<typeof buttonVariants> {
// ...
}
Lib Package (packages/lib)
Business logic and service integrations.
Modules:
hashnode/- Hashnode API clientopenai/- OpenAI integrationemail/- Email serviceanalytics/- Analytics utilitiesvalidation/- Form validation schemas
Example:
// packages/lib/src/hashnode/client.ts
export async function getAllPosts() {
const response = await fetch(HASHNODE_API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: GET_ALL_POSTS_QUERY,
}),
})
return response.json()
}
Utils Package (packages/utils)
Pure utility functions without external dependencies.
Functions:
cn()- Tailwind class name mergerformatDate()- Date formattingslugify()- URL slug generationtruncate()- Text truncation
DB Package (packages/db)
Prisma schema and database client.
Schema:
// packages/db/prisma/schema.prisma
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Data Flow Architecture
Blog Content Flow
Dashboard Content Flow
Design Patterns
1. Server Components by Default
// apps/site/app/blog/page.tsx
// No "use client" directive = Server Component
export default async function BlogPage() {
// Can fetch data directly
const posts = await getAllPosts()
return (
<div>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
)
}
2. Repository Pattern
Encapsulate data access logic in repository modules:
// packages/lib/src/repositories/post.ts
export class PostRepository {
async getAll() {
return prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: "desc" },
})
}
async getBySlug(slug: string) {
return prisma.post.findUnique({
where: { slug },
})
}
}
3. Service Layer
Business logic separated from data access:
// packages/lib/src/services/blog.ts
import { PostRepository } from "../repositories/post"
import { CacheService } from "./cache"
export class BlogService {
constructor(
private postRepo: PostRepository,
private cache: CacheService
) {}
async getPosts() {
const cached = await this.cache.get("posts")
if (cached) return cached
const posts = await this.postRepo.getAll()
await this.cache.set("posts", posts, 3600)
return posts
}
}
4. Composition over Inheritance
Build complex UIs by composing simple components:
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>
Content here
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
Performance Optimizations
1. Redis Caching
// Cache blog posts for 1 hour
const posts = await cache.get("posts", async () => {
return await getAllPosts()
}, { ttl: 3600 })
2. Image Optimization
import Image from "next/image"
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={630}
priority // Above the fold
/>
3. Dynamic Imports
import dynamic from "next/dynamic"
const HeavyComponent = dynamic(
() => import("./HeavyComponent"),
{ ssr: false } // Client-only
)
4. Incremental Static Regeneration
export const revalidate = 3600 // Revalidate every hour
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
return <Post data={post} />
}
Security Considerations
Note:
Security Best Practices:
- Environment Variables: Never expose API keys in client code
- Input Validation: Validate all user input with Zod schemas
- SQL Injection: Use Prisma parameterized queries
- XSS Protection: React escapes by default, but be careful with
dangerouslySetInnerHTML - CSRF Protection: Next.js includes built-in protection
- Rate Limiting: Implement rate limiting on API routes
Next Steps
- Development Workflow - Learn the development process
- Coding Standards - Follow best practices
- Monorepo Structure - Navigate the workspace
- Package System - Work with shared packages
Note:
Understanding the architecture is crucial for effective development. Refer back to this guide as you work on features.