Case files / PAN-138

PAN-138In flight

Town directory, remastered

An old WordPress directory for a small coastal town, rebuilt from a database dump into a new site with its own admin. Every old address still lands somewhere, and the tide table is computed from scratch and checked against a real gauge. The public still sees a coming-soon page.

Control flow

  1. Import: the old WordPress dump
  2. Reconcile: source equals imported plus skipped
  3. Redirect: old addresses 301, dead ones 410
  4. Publish: a map-first site, phone first
  5. Submit: visitors send listings and reviews
  6. Moderate: staff approve, as drafts

Industry: Tourism · Stack: Next.js 16 · PostgreSQL · Prisma · MapLibre · AWS EC2 · Seed species: Moon

Problem

A small town’s tourism directory had lived on WordPress for years. Its listings, events and links were scattered across search results and other people’s pages. Replacing it meant losing none of that, and the old site was the only record of what existed.

How it works

The importer reads the old database dump and reconciles it: every row in the source is either imported or skipped with a reason. Every old address gets a 301 to its new home; addresses that are truly gone get a 410. The new site is map-first and built for phones.

Visitors can send listings, events and reviews. An approved submission becomes a draft, never a live page. A review goes live only after its email is confirmed and staff publish it. Business owners can propose changes to their listing; staff apply them.

The tide table is computed from harmonic constituents in a public ocean tide model, not scraped from anywhere.

Results

  • Every listing, event, image and redirect reconciled against the source. Every address in the old sitemap resolves. A second run changed 0 rows. A row deleted on purpose made the check fail and name the row.
  • Tides checked against a nearby gauge over 6,111 hours: hourly error 13 cm, highs and lows within 11 minutes. A planted 20 degree phase error failed the check.
  • Home page LCP 1.53 seconds on a throttled slow 4G phone, against a 2.0 second budget, after three changes.

What broke

A security review found admin pages leaning on one shared check. It was caught before anything was deployed, and every admin page now checks for itself.

The importer once wrote a redirect from the home page to a supermarket’s listing. The writer now refuses it, and the check fails on it. Moon times used the UTC day and showed the previous day’s moonset; that one was found live. A coming-soon rewrite returned a 500 on every page and became a redirect instead.

Limitations

Staging only. The content import on staging, QA and the cut-over are still ahead. No AI runs in the site; agents built it, in a repo whose hooks refuse any commit without a green gate.

Next

Run the content import on staging, then QA and the move to the client’s own hosting account.

Open the living case file