Zurück zu den Artikeln

My Current Design State: Features and Learnings

A comprehensive look at what I've built so far: media management, content editing, real-time previews, and the architectural decisions that made it all possible.

My Current Design State: Features and Learnings

After two intense days of development (February 3-4, 2026), my Phoenix-based personal website platform is fully functional. Let’s walk through what I’ve built, the challenges I faced, and the decisions that shaped the current architecture.

What I’ve Built

Public Website Features

Homepage

  • Personal profile with avatar (circular, with ring border)
  • Professional headline and bio
  • Social media links (GitHub, LinkedIn, Mastodon)
  • Recent work experience showcase
  • Latest blog articles with excerpts
  • Newsletter signup form
  • Fully responsive, mobile-first design

About Page

  • Large portrait image with characteristic Spotlight rotation effect
  • Comprehensive biography
  • Complete work history with date ranges
  • Education and certifications
  • Skills categorized by type (languages, frameworks, tools, etc.)
  • All content pulled dynamically from database

Articles (Blog)

  • Article listing with categories and tags
  • Individual article pages with markdown rendering
  • SEO-optimized metadata
  • Related articles suggestions
  • Dark mode prose styling with @tailwindcss/typography

Custom Pages

  • Dynamic page creation via admin
  • Markdown content support
  • SEO metadata per page
  • Reorderable via display_order field

Admin Dashboard

Profile Management

  • Edit personal information (name, headline, bio)
  • Contact details (email, phone, location, website)
  • Avatar picker modal with two modes:
    • Select from uploaded media library
    • Enter external image URL
  • Circular avatar preview
  • Social media links (JSONB field for flexibility)
  • SEO metadata (meta title, description)
  • Availability flags (for hire, consulting)

Media Library

  • Drag-and-drop file upload
  • Real-time upload progress bars
  • Image optimization with Mogrify/ImageMagick:
    • Auto-orientation
    • EXIF metadata stripping
    • Resize to max 2000x2000px
    • 85% quality compression
  • Grid view with hover actions
  • Edit modal for alt text and titles
  • Delete with confirmation
  • All images served from /uploads/

Content Management

  • Articles with markdown editor
  • Categories and tags
  • Publish/unpublish workflow
  • Custom pages with slug management
  • Work experience tracking
  • Education records
  • Certifications with expiration dates
  • Skills with proficiency levels

Authentication

  • Secure admin login with bcrypt
  • Session management
  • CSRF protection
  • Remember me functionality

Technical Features

Dark Mode

  • Default to dark theme
  • Manual toggle with sun/moon icons
  • Persisted in localStorage
  • Synced via Alpine.js
  • Every component has dark mode variants
  • Modal backgrounds adapt to theme

Internationalization

  • German (default) and English
  • Language switcher in admin
  • Separate translation domains:
    • default.po - Public website
    • admin.po - Admin interface
    • errors.po - Validation errors
  • Preference stored in session

LiveView Enhancements

  • Real-time form validation
  • Instant feedback on user actions
  • No page refreshes for CRUD operations
  • WebSocket-based updates
  • Optimistic UI updates

Architectural Decisions

Database Schema

I designed my schema around flexibility and future expansion:

Content Contexts:

  • profiles - Single row for site owner (no user_id relation)
  • articles - Blog posts with SEO fields
  • pages - Custom pages with markdown
  • work_experiences - Employment history linked to profile
  • education - Academic background
  • certifications - Professional credentials
  • skills - Categorized abilities with proficiency

Media Context:

  • media - Uploaded files with metadata
  • UUID filenames prevent conflicts
  • JSONB metadata field for extensibility
  • Belongs to user who uploaded

Key Design Choices:

  • Used JSONB for flexible data (social_links, metadata)
  • Timestamps on everything for audit trails
  • Boolean flags over enum strings (available_for_hire)
  • Separate SEO fields per content type
  • Display order for manual sorting

Component Architecture

Three-Tier Component System:

  1. Core Components (core_components.ex)

    • Phoenix framework essentials
    • Modal, flash messages, icons
    • Base form inputs
    • Table helpers
  2. Admin Components (admin_components.ex)

    • Catalyst-inspired UI elements
    • Page headers, form containers
    • Badges, buttons, cards
    • Empty states, alerts
    • Description lists
  3. Public Components (in layouts)

    • Spotlight-inspired design
    • Container, Card, Section
    • Social icons
    • Newsletter form

Component Patterns:

  • Use attr/3 for props with validation
  • Use slot/3 for flexible content areas
  • Support both :patch and :navigate for LiveView
  • Include @rest for arbitrary HTML attributes
  • Dark mode classes on everything

File Organization

lib/spotlight/              # Business Logic
├── accounts/              # Authentication
├── content/               # Articles, Pages, Profiles
└── media/                 # File uploads

lib/spotlight_web/         # Web Interface
├── components/
│   ├── core_components.ex
│   ├── admin_components.ex
│   └── layouts/
├── controllers/          # HTTP endpoints
├── live/admin/           # Admin LiveViews
└── router.ex

priv/
├── static/uploads/       # User uploads
├── repo/migrations/      # DB schema
└── gettext/              # Translations

Asset Pipeline

  • Tailwind CSS v3 with JIT compiler
  • esbuild for JavaScript bundling
  • Alpine.js for minimal interactivity
  • Heroicons embedded in CSS
  • Watchers for hot reloading in dev

Challenges and Solutions

Challenge 1: Modal Dark Mode

Problem: Core modal component didn’t support dark mode Solution: Added dark mode classes to background overlay and modal container:

class="bg-zinc-50/90 dark:bg-zinc-950/90"  # Backdrop
class="bg-white dark:bg-zinc-900"           # Modal

Challenge 2: Media Display

Problem: Uploaded images weren’t showing (404s) Solution: Added "uploads" to SpotlightWeb.static_paths() to serve files via Plug.Static

Challenge 3: Avatar Selection UX

Problem: Text input for avatar URL was clunky Solution: Built modal with dual interface:

  • Media library grid (click to select)
  • URL input field (for external images)
  • Preview shows current avatar
  • Remove button to clear selection

Challenge 4: Form Field Syntax in Templates

Problem: Using atoms like field={:title} in inputs Solution: Use Phoenix.HTML.FormField syntax: field={@form[:title]}

Challenge 5: Image Optimization

Problem: Large image uploads consuming bandwidth Solution: Mogrify pipeline:

  • Auto-orient based on EXIF
  • Strip all metadata
  • Resize to 2000x2000 max
  • 85% JPEG quality Result: 70%+ file size reduction

Performance Metrics

Current performance benchmarks:

  • Homepage load: ~40ms (server) + assets
  • Article page: ~50ms (includes markdown parsing)
  • Media upload: Real-time progress, <2s processing
  • Admin navigation: Instant (LiveView patch)
  • CSS bundle: 12KB gzipped
  • JavaScript: ~8KB (mostly Alpine.js)

What’s Next?

Features I’m planning:

Short Term:

  • [ ] Article categories page
  • [ ] Tag cloud and filtering
  • [ ] Search functionality
  • [ ] RSS feed generation
  • [ ] Social share buttons

Medium Term:

  • [ ] Projects showcase
  • [ ] Speaking engagements
  • [ ] Contact form with anti-spam
  • [ ] Analytics dashboard
  • [ ] Sitemap generation

Long Term:

  • [ ] Newsletter management (subscribers, campaigns)
  • [ ] Comment system (maybe)
  • [ ] Webmentions support
  • [ ] API for external integrations
  • [ ] Multi-author support

Key Learnings

What Worked Well:

  1. Phoenix LiveView: Eliminated JavaScript complexity while maintaining interactivity
  2. Tailwind Utility Classes: Rapid UI development without context switching
  3. Catalyst Component Patterns: Professional look with minimal custom CSS
  4. Ecto Migrations: Database changes are versioned and reversible
  5. Pattern Matching: Cleaner code with fewer bugs

What I’d Do Differently:

  1. Test Earlier: Should have written tests alongside features
  2. Design System First: Would establish spacing/color conventions before building
  3. Mobile Preview: Should test mobile layouts continuously, not at the end
  4. Accessibility Audit: Need to run automated accessibility checks
  5. Documentation: Inline docs for complex functions would help

Unexpected Delights:

  • LiveView’s form validation is magical
  • Dark mode with Tailwind is almost too easy
  • Ecto’s changesets catch bugs before hitting the database
  • Hot reloading makes development addictive
  • The BEAM VM’s error handling is comforting

Conclusion

Building a personal website platform from scratch with Phoenix has been incredibly rewarding. I now have:

  • ✅ A performant, modern web application

  • ✅ Full control over every feature

  • ✅ Clean, maintainable codebase

  • ✅ Beautiful admin interface

  • ✅ Mobile-responsive design

  • ✅ Dark mode throughout

  • ✅ Multi-language support

The journey from WordPress to Phoenix taught me that sometimes the best tool isn’t the most popular one—it’s the one that matches your needs and values. For me, that meant choosing developer experience, performance, and maintainability over plugin ecosystems and familiar territory.

If you’re considering building something similar, my advice: Go for it. The learning experience alone is worth it, and you’ll end up with something uniquely yours.

Tech Stack Summary

  • Backend: Phoenix 1.7, Elixir 1.18, Erlang/OTP 26
  • Database: PostgreSQL with Ecto
  • Frontend: Phoenix LiveView, Alpine.js, Tailwind CSS v3
  • Deployment: Nix shell for reproducible environments
  • Media: Mogrify + ImageMagick for image processing
  • Content: Markdown with Earmark parser
  • i18n: Gettext (German/English)

Want to see more? Check out my GitHub repository or get in touch.

Previous articles: