Overview: "Eating My Own Cooking"
The most honest demonstration of a developer's skill is the system they build for themselves, where there's no client brief to hide behind and no corner-cutting when the deadline looms.
Klytron.com is a full-featured personal portfolio and blog built on Laravel 13 — and the engineering decision that defines it is deliberate: no database for content. Every blog post, portfolio item, service page, and site configuration lives as Markdown files with YAML frontmatter. The database constraint was exercised — and rejected.
This isn't a static site generator. It's a fully dynamic Laravel application with content routing, RSS/Atom/JSON feeds, XML sitemap generation, schema.org structured data, OpenSearch support, and zero-downtime atomic deployments via Deployer.
Why No Database?
The instinct for most Laravel developers is to reach for Eloquent and migrations. For a content site with infrequent writes and high read frequency, a flat-file architecture is simply superior in every dimension that matters:
| Concern | Database CMS | Flat-File (This Site) |
|---|---|---|
| Deployment | Schema migrations on every deploy | git push — done |
| Local dev setup | Seed DB, run migrations | Clone and go |
| Content backup | DB dump + files | git — already done |
| Version history | External plugin or custom | Full git log for free |
| Read performance | Query + cache | Cache-warm flat read |
| Portability | DB credentials required | Any PHP host, any VPS |
The trade-off is write performance (which doesn't matter for a personal site updated a few times a week) and query flexibility (which is unnecessary when content categories map directly to directories).
Content Architecture — The ContentService
The heart of the system is ContentService — a singleton that parses, caches, and serves Markdown content throughout the application.
// ContentService — core pattern (simplified)
public function get(string $path): array
{
return Cache::lock("content.{$path}", 5)->block(2, function () use ($path) {
return Cache::remember("content.{$path}", now()->addHour(), function () use ($path) {
return $this->parse($path);
});
});
}
Key design decisions:
- Atomic locking via
Cache::lockprevents cache stampede under concurrent requests - 1-hour TTLs balance freshness with performance — a
ddev artisan cache:clearon deploy handles invalidation - YAML frontmatter is parsed with the
Spatie YAML Front Matterpackage and content is rendered viaLeague CommonMarkwith GFM extension - Collections over arrays throughout —
ContentService::all()returns aCollectionfor clean chaining
SEO & Discovery Layer
The site implements the complete modern SEO stack:
/sitemap.xml— dynamically generated from allpublishedcontent files/robots.txt— route-served fromPageController, fully configurable/feed/posts— Atom feed with full-text content/feed/posts/rss— RSS 2.0 feed/feed/posts/json— JSON Feed 1.1/opensearch.xml— OpenSearch description document for browser search plugin integration- Schema.org JSON-LD — injected per page type (Article, Person, WebSite, Service)
- Dynamic OG images — server-side generated via
OgImageControllerusing PHP GD with branded background
All feed URLs use the production domain from config/site.php, keeping localhost test feed URLs out of production XML.
Design System
The UI is a dark-mode-only glassmorphic design built on Bootstrap 5 with heavy CSS customisation compiled via Vite. Key design tokens:
- Primary:
#62a92b— a deliberate choice against the generic blue/purple palette SaaS floods use - Glassmorphism:
backdrop-filter: blur()cards withrgbaborders and ambient green glow accents - Typography: DM Mono — both primary and secondary font
Custom Components:
- Mac-style terminal windows for all code blocks (CSS + JavaScript, no dependencies)
- Dynamic Table of Contents auto-generated from post headings via vanilla JS
- Sticky sidebars on all detail pages for metadata and sharing
- Reading progress bar on blog posts
- Global command palette (
⌘K) with AJAX-powered search
Deployment Pipeline
Zero-downtime atomic deployments using Deployer (php-deployment-kit):
git push → GitHub → SSH trigger → Deployer
→ composer install (no-dev)
→ npm run build
→ php artisan config:cache
→ php artisan route:cache
→ php artisan view:cache
→ atomic symlink swap
→ php artisan cache:clear
The symlink swap is instantaneous — requests that arrived mid-deploy see either the old or new release, never a broken intermediate state. Rollback to the previous release is a single dep rollback command.
Technology Stack
| Component | Technology |
|---|---|
| Framework | Laravel 13 |
| PHP | 8.4+ |
| Content | Markdown + YAML frontmatter (flat-file CMS) |
| Build | Vite + Sass |
| CSS | Bootstrap 5 + custom design system |
| Icons | Remixicon, Tabler Icons |
| Fonts | DM Mono (Google Fonts) |
| Deployment | Deployer (php-deployment-kit), zero-downtime |
| Hosting | Ubuntu VPS, Nginx, PHP-FPM |
| CDN/DNS | Cloudflare |
| Caching | Laravel File Cache (1-hour TTL) |
| Dev Env | DDEV (Docker) |