Personal Website Management – Blog Admin

A modern Next.js + Supabase admin app to manage personal blog posts, tags, and content with a Monaco-powered Markdown editor and live preview.

Blog admin dashboard overview
Main dashboard with posts and tags overview
Posts list view
Paginated list of all blog posts with status indicators
Create new post interface
Form to create a new blog post with all fields
Edit post fields
Editing post metadata and basic information
Edit post content with Monaco editor
Markdown editor with live preview for post content
Edit post tags
Tag management interface for posts
Tags management interface
Create, edit, and delete tags with usage counts
Save post changes confirmation
Confirmation dialog for saving post modifications
Post update approval
Final approval step before publishing changes
Slide 1 / 9

Overview

A production-ready admin interface to manage a personal blog: posts, tags, and content relationships. Built with Next.js App Router, TypeScript, Supabase, React Query, Tailwind, and shadcn/ui. It features a Monaco-powered Markdown editor with live preview, robust form validation (Zod + React Hook Form), and a clean, responsive UI.

  • Monorepo path: blog-admin/
  • App Router with API routes under src/app/api/*
  • Data storage with Supabase (PostgreSQL)
  • Realtime-friendly patterns and React Query caches
  • Strict TypeScript for reliability

Architecture

  • Next.js 15 (App Router) with Turbopack for fast builds and dev.
  • UI: Tailwind CSS + shadcn/ui (Radix primitives) for accessible components.
  • State/Data: React Query for fetching, caching, and optimistic flows.
  • Forms: React Hook Form with Zod schemas in src/lib/validations.ts.
  • Database: Supabase client (src/lib/supabase.ts) and queries (src/lib/queries.ts).
  • Editor: Monaco Editor wrapped in MarkdownEditorWithPreview for edit/preview/split modes.
  • Types and hooks organized under src/types and src/hooks.

Directory highlights:

  • src/app/ – pages and API routes (posts, tags, stats)
  • src/components/ui/ – reusable UI components (button, card, dialogs, editor)
  • src/lib/ – supabase client, queries, utilities, validations
  • src/hooks/ – typed React Query hooks

Data model

Tables managed:

  • posts – title, slug, summary, content_md, draft, publish status, cover image, timestamps
  • tags – label, slug, usage counts
  • post_tags – many-to-many between posts and tags

Features

  • Posts and tags CRUD with React Query for smooth UX
  • Monaco Markdown editor with:
    • GFM support (tables, task lists) via remark-gfm
    • Syntax highlighting via rehype-highlight
    • Live preview and split view
    • Sensible defaults (word wrap, line numbers, automatic layout)
  • Form validation with Zod schemas and error messaging
  • Responsive layouts with proper overflow management
  • Optimized tables (column sizing, no horizontal scrolls)

Editor experience

We consolidated editor usage into a single MarkdownEditorWithPreview component to ensure consistent behavior and layout across the Create and Edit flows. Issues like height shrinkage in dialogs were addressed using:

  • Parent flex containers with min-h-0 and explicit heights where needed
  • Avoiding percentage heights within complex dialog flex trees
  • Fallback to textarea if Monaco fails to mount within a set timeout

Performance & build

  • Dev startup: ~1–2s (Turbopack)
  • Lint: ~3s
  • Build: ~25–30s
  • Shared bundle: ~135KB

These values are from local runs and can vary by machine.

How to run

Prereqs: Node.js LTS and a Supabase project.

  1. Install deps
cd "blog-admin" npm install
  1. Configure environment

Create blog-admin/.env.local from example and set Supabase keys:

Copy-Item .env.example .env.local # Then edit .env.local and fill: # NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url # NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key # SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
  1. Dev server
npm run dev # App at http://localhost:3000
  1. Lint & build
npm run lint npm run build

Notes:

  • Without Supabase credentials, UI loads but data ops return 500s (expected).

Challenges & solutions

  • Markdown rendering inconsistencies → standardized ReactMarkdown config with GFM + highlight plugins and custom component styling.
  • Dialog/editor height issues → unified container strategy with min-h-0, explicit pixel heights where appropriate, and a single editor component.
  • Table overflow in posts list → column sizing and layout tweaks to remove the bottom scrollbar.

Future work

  • Role-based auth and access control
  • Media manager for cover images
  • Draft autosave and version history
  • MDX support for richer content blocks
  • End-to-end tests (Playwright) and unit tests for API routes

Attribution

  • UI primitives by Radix via shadcn/ui
  • Markdown highlighting via highlight.js themes
  • Icons by Lucide React