
PSbloggen.se migration
Moved a ten-year PlayStation enthusiast blog's content off WordPress and built it a full IGDB-powered editorial platform: curated collections, a daily guess-the-game feature, PS Plus catalog tracking, and reader accounts, all self-hosted. Running on staging; the switch to psbloggen.se is still to come.
Read the case studyCase study: PSbloggen.se migration →- Migrated 10+ years of WordPress content to Markdown via a custom XML → Turndown pipeline — 4,300+ posts, 1,700+ backfilled with IGDB metadata
- Dual-stack: Astro SSR editorial site for reviews and editorial, React SPA for live IGDB game data, one shared auth and nav
- Grew past the original scope into a full editorial platform: curated 'Samlingar' collections, a daily guess-the-game feature, PS Plus catalog tracking, author/developer profile pages
- Reader-facing additions: shareable public game lists per user, year-in-review stats pages, a 'this day in gaming history' homepage widget, and a full PlayStation-themed visual redesign with a glowing SVG score arc
- Community features: threaded comments with spam filtering, reader-submitted content reports, contact inbox with email replies — all moderated from the admin
- Admin CMS matured into a real operations console: first-party + Umami analytics, scheduled R2 backups with one-click restore, a Discord review-pipeline bot, bulk publish/audit tooling
- Image archive rehosted at full resolution as AVIF on R2: 6.93 GB of WordPress uploads down to 2.62 GB, with sharper images than before
- Rebuilt the content pipeline after diagnosing a real regression: dropped Astro's content collections for direct filesystem reads, cutting a content-heavy build from 15s/1.75GB to ~4s/850MB
- Found and fixed a CI build-cache bug (a GitHub Action silently tearing down a persistent build cache every run) that cut a 2-minute CI build down to under 40 seconds
- Publishing without a redeploy: saving a post triggers a debounced background rebuild behind a filesystem lock; the old build keeps serving until the new one is ready, and only a brief Astro restart separates the two
- Single-command Docker deploy with nginx routing both frontends, plus two independently-deployed sibling services (a review-pipeline bot and self-hosted analytics) over a shared Docker network
A small crew of PlayStation enthusiasts had been running a game blog for over ten years. The goal: get off WordPress, keep the content, and build something the team actually wanted to use. What started as a migration plus a game-discovery portal has since grown into a full self-hosted editorial platform — curated collections, a daily guessing game, PS Plus catalog tracking, reader accounts and moderation, and an admin console with its own analytics and operational tooling.
What it is
Editorial site (Astro): Reviews with scores, verdict badges, pros/cons, and platform tags. Editorial posts, curated “Samlingar” collections with mood/category browsing, author and developer profile pages, and a daily “guess the game” feature built from real review data. Runs as a built SSR Node process. Publishing needs no redeploy: the admin saves a post, a debounced background rebuild runs while the old build keeps serving, and once it finishes Astro is briefly stopped, the new build moved into place, and Astro restarted on it. Every saved .md file is also pushed to Git automatically, so Git stays the single source of persistent truth.
Game portal (React): Upcoming and recent PS4/PS5 releases pulled from IGDB, with cover art, trailers, screenshots, similar games, and full-text search. PS Plus catalog browsing with monthly navigation. User accounts with favourites, play-status tracking, and per-game notes.
Admin CMS: Grew from a Markdown-generating form into a real operations console — first-party and Umami analytics side by side, scheduled database backups to Cloudflare R2 with one-click restore, a script runner for bulk imports, content moderation for reader reports and comments, and integration with a Discord bot that handles review assignment and voting.
The migration
WordPress exports to XML. Each post is HTML — category metadata, tags, publish dates, author, and attached media all intact. A Node.js pipeline converted the export:
- Parse XML with
xml2js - Pre-process WordPress-specific markup (caption shortcodes →
<figure>, oEmbed URLs → clean links) - Convert HTML to Markdown with Turndown
- Rewrite internal links and download media locally
- Write each post as
.mdwith Astro frontmatter
Astro’s Zod schema validation caught malformed dates and missing fields at build time. The importer since grew into an ongoing pipeline — it also ingests new and scheduled posts straight from the old blog’s RSS feed, not just the one-time historical export.
Architecture
Express API proxies IGDB (keeping Twitch credentials server-side), handles JWT auth in httpOnly cookies, and serves recent editorial posts as JSON for the React homepage. An in-memory TTL cache wraps all IGDB responses.
SQLite (via better-sqlite3) stores user accounts, favourites, play statuses, comments, and the PS Plus catalog. Embedded in the Express process, backed up to Cloudflare R2 on a schedule.
nginx routes the two frontends through one domain in production, plus a same-origin /stats/ proxy to a sibling analytics deployment (avoids adblockers flagging a third-party analytics domain):
/_astro/*→ Astro pre-built static assets/reviews/,/editorial/→ Astro SSR Node process/api/→ Express/stats/→ Umami (separate deployment, shared Docker network)/uploads/→ Express, which redirects to Cloudflare R2 (S3-compatible, no egress fees)/*→ React SPA
Tech stack
| Layer | Technology |
|---|---|
| Game portal | Vite + React + TypeScript + Tailwind |
| Editorial site | Astro (built SSR, no client content-collection layer) + Tailwind |
| Backend | Express |
| Database | SQLite via better-sqlite3, scheduled backups to R2 |
| Auth | JWT in httpOnly cookies, bcrypt, hCaptcha on signup |
| Media storage | Cloudflare R2 (S3-compatible) |
| Analytics | First-party pageview counter + self-hosted Umami (sibling deployment) |
| Production | nginx + Docker Compose, shared network with sibling services |
Status
The content migration is complete, and the platform is running on staging, where the crew is learning the new admin while we optimize, fix bugs and test. The switch to psbloggen.se is still to come, planned for late November or early December 2026; until then psbloggen.se serves the old WordPress site, and its new posts are brought over through its RSS feed.
What started as a migration plus a game portal has grown into the crew’s full editorial platform: collections, a daily guessing game, PS Plus tracking, comments, and an admin console with real operational tooling behind it.