Skip to content

Live

Migrating a news platform without turning it off

A quarter of a million articles on hand-rolled PHP, a newsroom that publishes every day, and no window in which the site is allowed to be down.

Role
Software engineer (contract)
Timeline
2025 – present
Stack
LaravelFilamentNext.jsMySQLGitHub ActionsVPS

Context#

Al Afdal is a Lebanese news outlet. Its website had been running for years on hand-written PHP — no framework, no build step, no migrations, and an archive that had grown to more than 250,000 articles. It worked, in the sense that it served readers every day. It also meant that any change to how an article was stored or displayed was a change to files that had accumulated a decade of edits from people who were no longer around to ask.

The outlet wanted a modern site, a mobile app, and an admin their editors could actually use. I was brought in to get them there.

The constraint that shaped everything: a news site is a business whose inventory is its archive and whose distribution is Google. There was no acceptable window in which the site could be down, and no acceptable outcome in which old article URLs stopped resolving.

Constraints#

  • The archive is the asset. 250,000+ articles, all indexed, all reachable from search. Losing URLs would have cost traffic that takes years to rebuild.
  • The newsroom does not stop. Editors publish daily. A migration that required a content freeze was not a migration anyone would agree to.
  • Shared hosting. No queue workers, no control over the PHP version, no deploy process beyond uploading files.
  • One engineer. Whatever I built, I would also be the one operating.

Those four together rule out most of the obvious plans. In particular they rule out the plan everyone reaches for first, which is to rebuild the thing properly and swap it in one weekend.

Decisions#

Decouple the API from the front end#

The mobile app was the immediate reason: a Laravel application rendering Blade templates would have meant one content path for the web and a second, parallel one for the app, which is how you end up with two versions of "what is a published article". A second property — Al Afdal TV (opens in a new tab) — made it three.

The less obvious reason was the one I cared about more. A monolith is a single point of failure with the whole business behind it. Splitting the API from the front end means the thing that serves readers and the thing that serves editors can fail independently, and neither takes the archive with it. Getting a third consumer later becomes a client, not a rewrite.

So: Laravel owns the data and exposes a REST API. Next.js consumes it. So does the mobile app, and so does anything that comes next.

What I turned down: a Laravel monolith with server-rendered templates. Less machinery, faster to the first working page — and one process whose death is total.

Buy the admin, build the API#

Editors interact with the admin every day; they interact with my architecture never. Filament gives a production-grade admin — resources, relations, permissions, media handling — for roughly the effort of describing the models. Building that by hand would have consumed the majority of the project to arrive somewhere slightly worse.

What I turned down: a custom admin. The argument for one is fit; the argument against is that "fit" here meant a CRUD interface over articles, categories, and media, which is the exact shape Filament already is.

Migrate in phases, with both systems live#

This is the decision the whole project turns on. Rather than freeze, convert, and cut over, the new platform came up alongside the old one and took over in pieces. Content was backfilled into the new schema while the legacy site kept serving; the new front end went live once it could answer for the archive as well as the legacy site could.

It is slower and it is more annoying — for a while there are two systems and you have to care about both. What it buys is that no single step is irreversible, and no step requires the newsroom to stop working.

What I turned down: a cutover weekend. Cheaper, cleaner, and it fails in exactly the way you cannot afford: you find the problem with the archive after the archive is the only copy.

Move to a VPS#

This one was mine to call, and it was not really about performance. Frameworks do not work well on shared managed hosting. Laravel wants a writable cache, a specific PHP version, artisan on a shell, queue workers that stay alive, and a deploy that is something other than dragging files into an FTP client. Shared hosting offers none of those and will not be argued with.

So the choice was not "shared hosting or VPS". It was "Laravel or not", and I had already answered that.

What I turned down: staying put and working around it. Tempting, because a hosting migration is a project of its own that nobody is paying for. But every workaround would have been permanent, and I was the one who would be living with them.

Architecture#

The legacy vanilla-PHP platform feeds a one-off backfill into a Laravel REST API. A Filament admin writes to the same API. The API serves JSON to a Next.js website and to the mobile apps.backfillwritesJSONJSONLegacy platformvanilla PHPFilament admineditorsLaravel APIRESTNext.js sitealafdalnews.comMobile apps
The dashed edge is temporary by design — it exists only while the archive is being backfilled.

The shape is unremarkable, which is the point. The interesting part of this project was never the diagram; it was getting from the left-hand box to the right-hand ones while the left-hand box stayed up.

Outcome#

  • 250,000+ articles migrated into the new schema. The backfill itself ran in a few hours — the months went into everything around it.
  • No service interruption. The site never stopped serving readers.
  • Deployment went from manual file upload to GitHub Actions on push.
  • Around ten editors work the admin across shifts, on a tool they can be trained on in an afternoon rather than one that needed a person to interpret.
  • The same API serves the website and the mobile apps, so "published" means one thing in one place.
  • Traffic runs around 9,000 a month — 12k in April 2026, then 8k, 9k, 8k through July.

What I'd do differently#

I would have taken a traffic baseline before touching anything.

The whole argument of this project is in the constraints section: the archive is the asset, its value is in its addresses, and search is the distribution. I built every decision around not breaking that — phased migration, no content freeze, no cutover weekend — and then never measured the thing I was protecting.

So the honest position today is that I have current numbers and no before. I believe the migration cost nothing in traffic, and I cannot show it. If it had cost something, I would have found out slowly, from a feeling, in a quarter's time.

The fix was a morning of work at the start of the project, and there is no version of it I can do now.