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.









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
andsrc/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, validationssrc/hooks/
– typed React Query hooks
Data model
Tables managed:
posts
– title, slug, summary, content_md, draft, publish status, cover image, timestampstags
– label, slug, usage countspost_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)
- GFM support (tables, task lists) via
- 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.
- Install deps
cd "blog-admin" npm install
- 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
- Dev server
npm run dev # App at http://localhost:3000
- 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