Back to Posts

Building a Modern CMS from Scratch: A Full-Stack Engineering Journey

Building a Modern CMS from Scratch: A Full-Stack Engineering Journey
10 min read

My thoughts and decisions on creating my blog posts system here on my portfolio.

Building a Modern CMS from Scratch: A Full-Stack Engineering Journey

When I set out to build my personal portfolio and blog, I faced a common dilemma: use an off-the-shelf CMS like WordPress or a headless solution like Contentful, or build something custom that perfectly fits my needs? As a full-stack engineer who loves solving problems, I chose the latter. Here's how I built a production-ready content management system from the ground up using modern web technologies.

Why Build a Custom CMS in 2025/2026?

Before diving into the technical details, let me address the elephant in the room: why build a custom CMS when there are so many great options available?

The answer comes down to three key factors:

Control and Flexibility - Every feature is exactly what I need, no bloat, no unnecessary complexity. I'm not paying for features I'll never use or wrestling with plugins that almost do what I want.

Performance - By building on Next.js 16 with the App Router and implementing smart caching strategies, I achieved sub-second page loads without relying on third-party services.

Learning and Ownership - As an engineer, there's immense value in understanding your entire stack. Plus, I own my data completely—no vendor lock-in, no surprise API changes, no monthly fees scaling with my success.

The Tech Stack: Modern, Efficient, Scalable

I built this CMS using a carefully selected stack that balances developer experience with production performance:

CMS Admin List View

Frontend Layer

  • Next.js 16 with the App Router for server-side rendering and static generation
  • React Server Components to minimize client-side JavaScript and improve performance
  • TypeScript throughout for type safety and better developer experience
  • Tailwind CSS for rapid, consistent UI development
  • Backend & Database

  • PostgreSQL as the primary database—reliable, powerful, and perfect for structured content
  • Drizzle ORM for type-safe database queries with excellent TypeScript integration
  • Next.js API Routes for serverless backend functions
  • Content Features

  • Markdown support with real-time preview
  • Rich text toolbar for quick formatting without memorizing syntax
  • Automatic slug generation with smart URL-friendly transformations
  • Draft and publish workflow for content staging
  • New Post UI

    Architectural Decisions That Matter

    Smart Caching with Incremental Static Regeneration

    One of the biggest challenges with any CMS is the balance between fresh content and fast performance. Static sites are blazingly fast but require rebuilds. Fully dynamic sites are always fresh but slower.

    I implemented Next.js's Incremental Static Regeneration (ISR) with a 60-second revalidation window:

    typescript
    export const revalidate = 60;

    This means:

  • Blog posts are served as static HTML (incredibly fast)
  • Content updates within 60 seconds of publishing
  • No full site rebuilds required
  • Zero downtime deployments
  • For the API routes that power the dashboard, I used force-dynamic to ensure administrators always see the latest data:

    typescript
    export const dynamic = 'force-dynamic';
    export const revalidate = 0;

    Server Components vs Client Components

    Next.js 13+ introduced a paradigm shift with React Server Components. I leveraged this architecture to optimize performance:

    Server Components handle:

  • Data fetching from the database
  • Blog post rendering
  • Metadata generation for SEO
  • Static content layout
  • Client Components only for:

  • Interactive features (reading progress bar, share buttons)
  • Real-time form interactions in the dashboard
  • Markdown preview toggle
  • This separation means the initial page load is minimal JavaScript, improving performance significantly, especially on mobile devices.

    Database Schema Design

    I kept the database schema simple but extensible:

    typescript
    posts: {
      id, title, slug, content, excerpt,
      published, authorId,
      createdAt, updatedAt, deletedAt
    }
    
    users: {
      id, email, passwordHash, name,
      createdAt, updatedAt, deletedAt
    }

    The soft delete pattern (deletedAt) allows content recovery and maintains referential integrity. The excerpt field enables SEO-friendly meta descriptions without parsing the full content.

    Features That Enhance the Writing Experience

    Real-Time Markdown Preview

    As a developer, I love Markdown for its simplicity. But constantly switching between editor and preview is tedious. I built a toggle preview mode that renders content exactly as it will appear on the published site:

  • Instant switching between edit and preview modes
  • Custom Markdown parser supporting headers, code blocks, lists, and blockquotes
  • Syntax highlighting for code snippets
  • Responsive preview that matches the production layout
  • Intelligent Content Toolbar

    While Markdown is great, not everyone remembers the syntax. The toolbar provides one-click formatting:

  • Headers (H1, H2, H3)
  • Bold, italic, inline code
  • Links and lists
  • Smart text selection wrapping
  • Each button intelligently wraps selected text or provides a template at the cursor position.

    Word Count and Reading Time

    Writers need metrics. The dashboard automatically calculates:

  • Total word count
  • Character count
  • Estimated reading time (based on 200 words per minute)
  • These update in real-time as you type, helping with content planning and SEO optimization.

    URL Slug Management

    SEO-friendly URLs are crucial. The system automatically generates clean slugs from post titles:

    typescript
    "Building a Custom CMS" → "building-a-custom-cms"
    "10 Tips for Better Code!" → "10-tips-for-better-code"

    You can also manually edit slugs with validation to ensure they remain URL-safe (lowercase, alphanumeric, hyphens only).

    Reader Experience Optimizations

    Building a great CMS isn't just about the admin experience—the reader experience matters just as much.

    Reading Progress Indicator

    A gradient progress bar at the top of each post shows reading progress. It's smooth, non-intrusive, and helps readers gauge article length at a glance.

    The implementation uses efficient scroll event handling with proper cleanup to avoid memory leaks.

    Social Sharing Integration

    Every post includes:

  • Desktop: Floating share buttons (Twitter, LinkedIn, copy link)
  • Mobile: Bottom-of-post sharing options
  • Pre-filled share text with post title and URL
  • This increases content reach without relying on heavy third-party widgets.

    Author Profiles

    Each post displays author information with a professional bio section, including a profile image. This humanizes the content and improves E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) signals for SEO.

    Enhanced Typography

    I paid special attention to readability:

  • Larger base font size (18px) for body text
  • Generous line height (1.75) for comfortable reading
  • Proper heading hierarchy with visual distinction
  • Code blocks with subtle backgrounds and borders
  • Blockquotes with left-border accent styling
  • SEO Optimization Built-In

    Search engine optimization is baked into every layer:

    Metadata Generation

    Each post automatically generates:

  • Dynamic title tags with site branding
  • Meta descriptions from excerpts
  • Open Graph tags for social sharing
  • Twitter Card metadata
  • Canonical URLs
  • Structured data (JSON-LD) for rich snippets
  • Structured Data

    I implemented comprehensive schema.org markup:

    json
    {
      "@type": "BlogPosting",
      "headline": "Post Title",
      "author": {"@type": "Person", "name": "..."},
      "datePublished": "...",
      "dateModified": "...",
      "publisher": {...}
    }

    This helps search engines understand content context and enables rich search results.

    Performance Metrics

    Fast sites rank better. This CMS delivers:

  • Sub-second Time to First Byte (TTFB)
  • Optimized images with Next.js Image component
  • Minimal JavaScript for fast Time to Interactive (TTI)
  • Server-side rendering for instant content visibility
  • The Dashboard: Where It All Comes Together

    The admin dashboard is clean, intuitive, and powerful:

  • Post Overview: Card-based layout showing all posts with status indicators (Published/Draft)
  • Quick Actions: Edit, delete, and status toggle from the list view
  • Rich Editor: Full-screen editing experience with distraction-free writing
  • Auto-save Indicators: Visual feedback during save operations
  • Validation: Client-side validation prevents common errors before submission
  • Security Considerations

    Even for a personal blog, security matters:

  • Password hashing with industry-standard algorithms
  • Session-based authentication
  • Protected API routes requiring authentication
  • SQL injection protection via parameterized queries (Drizzle ORM)
  • XSS prevention through React's automatic escaping
  • Deployment and Infrastructure

    The entire system deploys to Vercel with zero configuration:

  • Serverless Functions: API routes scale automatically
  • Edge Network: Content served from 100+ global locations
  • Automatic HTTPS: SSL certificates managed automatically
  • Git-Based Deployments: Push to main branch = instant deployment
  • Preview Deployments: Every PR gets a unique preview URL
  • Database hosting on a managed PostgreSQL provider ensures reliability without the operational overhead.

    Lessons Learned and Future Improvements

    What Went Well

    Building this CMS taught me invaluable lessons about full-stack architecture:

  • Type safety saves time: TypeScript + Drizzle caught countless bugs before runtime
  • Server Components are powerful: Reducing client-side JavaScript improved performance noticeably
  • Simple is better: Resisting feature creep kept the codebase maintainable
  • What I'd Do Differently

    Every project has room for improvement:

  • Image Management: Currently, images are stored in the public directory. A proper media library with CDN integration would be better
  • Version History: Content versioning would enable rollbacks and change tracking
  • Collaborative Features: Multi-user editing with role-based permissions
  • Analytics Integration: Built-in post performance metrics
  • Roadmap

    Future enhancements I'm considering:

    1. Tag and Category System: Better content organization and discovery

    2. Full-Text Search: Algolia or PostgreSQL full-text search integration

    3. Related Posts: Algorithmic recommendations based on content similarity

    4. Newsletter Integration: Email capture and automated sending

    5. A/B Testing: Built-in title and excerpt testing for engagement optimization

    Performance Benchmarks

    Numbers don't lie. Here's how the CMS performs:

  • Lighthouse Score: 95+ across all categories
  • First Contentful Paint: <1.2s
  • Time to Interactive: <2.5s
  • Total Bundle Size: <100KB (before content)
  • API Response Time: <100ms (95th percentile)
  • These metrics rival or exceed many commercial CMS platforms while maintaining complete control.

    Why This Matters for Modern Web Development

    This project represents more than just a blog—it's a case study in modern full-stack development:

    Modern React Patterns: Server Components, Suspense boundaries, and proper client/server separation showcase Next.js 16 best practices.

    Database-Driven Architecture: Moving beyond static-only sites while maintaining performance demonstrates real-world scalability.

    Type Safety Throughout: End-to-end TypeScript from database to UI ensures reliability and maintainability.

    Performance-First Mindset: Every architectural decision considered speed, from ISR to minimal JavaScript.

    Open Source and Learning

    The beauty of building your own tools is the learning experience. Every challenge—from implementing reading progress to handling Markdown rendering to optimizing database queries—deepened my understanding of web fundamentals.

    Would I recommend everyone build their own CMS? Probably not. But if you're a full-stack developer looking to level up your skills, there's no better teacher than building a production system from scratch.

    Conclusion: Empowerment Through Ownership

    Building a custom CMS was an investment of time and effort, but the returns have been substantial:

  • Complete control over features and performance
  • Deep understanding of every component
  • No monthly fees or vendor dependencies
  • A portfolio piece demonstrating full-stack capabilities
  • The satisfaction of building something that's entirely mine
  • For developers considering a similar journey, my advice is simple: start small, iterate based on real needs, and don't over-engineer. Build what you need today, and extend it as requirements evolve.

    The web is built on content, and understanding how to manage, deliver, and optimize that content is a fundamental skill for any modern full-stack engineer. Whether you build your own CMS or use existing tools, understanding the underlying architecture makes you a better developer.

    Want to see the CMS in action? You're reading a post created with it right now. All the features mentioned—from the reading progress bar to the share buttons to the structured data—are part of the system.

    Have questions about the technical implementation? Feel free to reach out—I love talking about web architecture and engineering challenges.

    Share this post

    Star Vilaysack

    About Star Vilaysack

    Full-stack software engineer based in Minneapolis, specializing in building production-grade web applications. Passionate about web development, cloud architecture, and creating exceptional user experiences.