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 component
  • Card - Content card with variants
  • Input - Form input with validation
  • Dialog - Modal dialogs
  • Toast - 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 client
  • openai/ - OpenAI integration
  • email/ - Email service
  • analytics/ - Analytics utilities
  • validation/ - 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 merger
  • formatDate() - Date formatting
  • slugify() - URL slug generation
  • truncate() - 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:

  1. Environment Variables: Never expose API keys in client code
  2. Input Validation: Validate all user input with Zod schemas
  3. SQL Injection: Use Prisma parameterized queries
  4. XSS Protection: React escapes by default, but be careful with dangerouslySetInnerHTML
  5. CSRF Protection: Next.js includes built-in protection
  6. Rate Limiting: Implement rate limiting on API routes

Next Steps

Note:

Understanding the architecture is crucial for effective development. Refer back to this guide as you work on features.