Mapping & Migrating My Old PHP Site to Astro

Claude dug through the backup of my old Website Builder PHP site, mapped every file, and drafted a concrete plan for migrating everything to Astro — so I actually own and understand my own website.

  • #astro
  • #php
  • #web-dev
  • #migration
  • #behind-the-scenes

Note: This post was researched and written by Claude (the AI assistant I’ve been building this site with). I asked it to dig through a backup of my old website, map out how everything worked, and write up a migration plan. What follows is its report.


I went through the backup of this site’s previous incarnation — a proprietary PHP “Website Builder” platform — to understand exactly what was there and figure out how to port it cleanly to Astro. What follows is a full map of the old codebase and a phased migration plan.


The Old Stack: What Was Actually Running

The old site was generated by a hosted website builder and published as a set of PHP files. Under the hood:

  • Platform: Proprietary “Website Builder” (hosted, drag-and-drop)
  • Entry point: public_html/index.php → delegates to ncsitebuilder/index.php
  • Routing: A central dispatcher parses the URL, maps it to a hashed page ID, and include()s the matching PHP template
  • Data: Page configs and content stored partly in PHP arrays inside index.php, partly in a SQLite database (dat/project.db), and partly as pre-rendered HTML inside each page’s PHP template
  • Assets: 9 page-specific CSS bundles + 1 common bundle, and 9 page-specific JS bundles + 1 common bundle — all pre-compiled, not intended to be edited by hand

The template files have hash-based filenames (e.g., a18a5e5577780091a934e00d6ba0010d.php), which makes them completely opaque at a glance. Here’s the full map.


File Map: What Every PHP File Does

Page Templates (hash-named .php files)

File (short hash)URL aliasPage
...0091...0d.php/Home — “Thinker, Tinkerer, Artist” intro with three content sections
...01d2...05.php/Contacts/Contacts — Contact form, 13 fields, emails to [email protected]
...02b5...81.php/Tech/Tech Projects — Portfolio cards: Trading, Software, Photography, Video, Hardware, AI
...0300...46.php/AI-Adventures/AI Adventures (Blog) — Blog listing page, 6 posts per page, links to individual posts
...047a...e7.php/Art/Art Projects — Portfolio cards: Rock/Metal, Design, Photography, Video, Admin, Company
...0553...fd.php/Gift-ideas/Gift Ideas (Store) — E-commerce listing, 11 items, PayPal checkout, marked noindex
...0016...4e.php/Projects/Projects — Slideshow gallery with geo-tagged images, powered by PhotoSwipe
...6700...6e.php(internal)Custom 404 page — bare header/footer, no content
...7102...cb.php(internal)Maintenance page — “This page is currently down for maintenance”

Core Engine Files

FilePurpose
index.phpCentral router + dispatcher. Parses URL, loads page template, handles form submissions, applies SEO meta, manages sessions
functions.inc.php~50KB of utility functions: URL parsing, form handling, SEO/canonical URL generation, comment rendering, session helpers, localization
pd.jsonPlugin feature-flags config — which modules are enabled (Blog, Store, Forms, Gallery, PayPal, etc.)
sitemap.txtXML sitemap with 20 URLs: 7 pages + 2 blog posts + 11 store items
class.json.phpJSON encoding utility for older PHP versions
ga.phpGoogle Analytics injection helper
GatewayPaypal.phpPayPal v2 payment gateway handler
polyfill.phpHTML5 compatibility polyfills
main_BuyNow.php”Buy Now” button handler for store checkout

Data & Database

FilePurpose
dat/project.dbSQLite 3 database — full site project data, page layouts, plugin configs
dat/backend_tasks.jsonBuild task log — records when pages were last published and which plugins were active

Asset Directories

DirectoryContents
css/common-bundle.css + 9 page-specific CSS bundles
css/font-awesome/Font Awesome icon assets
css/flag-icon-css/Country flag icons (used in the contact form’s country dropdown)
css/fonts/Bootstrap Glyphicons (eot, svg, ttf, woff, woff2)
js/common-bundle.js + 9 page-specific JS bundles
js/photoswipe/PhotoSwipe — the open-source JS gallery library powering the Projects slideshow
gallery/Original uploaded images
gallery_gen/Auto-generated thumbnails and WebP-optimized versions
gateways/Full PayPal PHP SDK
phpmailer/PHPMailer library for form email delivery
phpseclib/PHP security library
tcpdf/PDF generation library
src/Core PHP modules: BlogModule, StoreModule, FormModule, BlogElement, StoreElement

Note: Google Fonts (Roboto, Raleway, Ubuntu, Josefin Sans) were loaded from Google’s CDN at runtime — they are not self-hosted in this backup.


What the Old Site Actually Had (Content Inventory)

Pages in the main nav:

  • Home — tagline, three-section intro
  • Projects — slideshow gallery portfolio
  • AI Adventures — blog (2 posts at time of backup)

Additional pages (accessible by URL, not in the main nav):

  • Contacts — contact form
  • Tech Projects — portfolio cards
  • Art Projects — portfolio cards
  • Gift Ideas — store with 11 gift items (PayPal-enabled, but noindex’d)

Blog posts:

  • “Punk Jazz, by ChatGPT” (2023-02-16)
  • “How ChatGPT Rewards Lies” (2023-02-15)

Store items (Gift Ideas page), by actual title:

  • A Copy of Your Favorite Record
  • A Modern Swiss Army Knife
  • Ashley’s Book of Knots
  • Tiny Plastic Robots
  • Powerful T-shirts
  • Guitar Strings for my Ukulele
  • Donate to Guitar Repairs
  • Donate to my 4Runner Project
  • ProCo Rat Distortion Pedal
  • Tetsuo Sakurai Gentle Hearts Tour 2004
  • EHX Op-Amp Big Muff Fuzz Pedal

Forms:

  • Contact form (13 fields)
  • Store inquiry form (13 fields)
  • Billing form (13 fields, for PayPal checkout)

The Migration Plan: PHP → Astro

The goal isn’t just to replicate the old site — it’s to own it. That means every page is a file you can open and read, every post is plain Markdown, and there’s no build platform that obfuscates everything behind hash-named files.

Guiding Principles

  1. Content lives in Markdown. Blog posts, project descriptions, and store items should all be plain .md files, not locked in a database.
  2. One component per concern. Header, Footer, Nav, and layout sections should each be their own .astro component — easy to find, easy to edit.
  3. Static output first. No PHP, no server-side rendering needed for a portfolio site. Astro’s static build is faster and simpler to host.
  4. Pages mirror the old URL structure. Existing URLs like /Projects/ and /AI-Adventures/ should resolve to the same content (or redirect properly).

Phase 1 — Pages (already partially done)

Map each old PHP page to an Astro page file:

Old URLOld PHP fileAstro target
/...0091...0d.phpsrc/pages/index.astro
/Projects/...0016...4e.phpsrc/pages/projects.astro
/AI-Adventures/ or /blog/...0300...46.phpsrc/pages/blog/index.astro
/Contacts/...01d2...05.phpsrc/pages/contact.astro (new)
/Tech/...02b5...81.phpMerge into projects.astro or add src/pages/tech.astro
/Art/...047a...e7.phpMerge into projects.astro or add src/pages/art.astro
/Gift-ideas/...0553...fd.phpsrc/pages/gifts.astro (optional, later)

Action items:

  • Rebuild the Home page content (three-section intro: Thinker, Tinkerer, Artist)
  • Create a Contact page with a static form (use Netlify Forms, Formspree, or similar — no PHP needed)
  • Decide whether Tech and Art pages get merged into a unified Projects page or stay separate

Phase 2 — Projects Content Collection

The old Projects/Tech/Art pages were just hardcoded HTML cards. The better approach in Astro is a content collection:

src/content/projects/
  automated-trading.md
  software-engineering.md
  hardware-development.md
  ai-research.md
  music.md
  photography.md
  video.md

Each file gets frontmatter like:

---
title: "Automated Trading"
category: "tech"   # or "art"
description: "..."
image: "/images/projects/trading.jpg"
featured: true
---

Then src/pages/projects.astro queries the collection and renders the cards dynamically — add or remove a project by editing a Markdown file.


Phase 3 — Blog Migration

Both old blog posts already exist as .md files in this repo. Going forward:

  • All posts live in src/content/blog/
  • Frontmatter schema is validated via Zod (already set up in src/content/config.ts)
  • The URL structure is /blog/<slug>/ rather than /AI-Adventures/<slug>/ — add a redirect if needed

To preserve old URLs, add Astro redirects in astro.config.mjs:

redirects: {
  '/AI-Adventures/[...slug]': '/blog/[...slug]',
}

Phase 4 — Contact Form (No PHP)

The old contact form relied on PHP + PHPMailer. Replacements for a static site:

OptionProsCons
FormspreeEasy setup, free tier, spam filteringMonthly submission limits
Netlify FormsZero config if hosted on NetlifyNetlify-only
EmailJSWorks from any host, free tierClient-side (email visible in source)
Web3FormsFree, no account needed for setupNewer, smaller community

Recommended: Formspree or Netlify Forms — drop in the action attribute, done.

Fields to replicate from the old form: Name, Email, “How did you find me?”, “Your Question?”, Message. The rest (Country, City, Address, VAT number) can be skipped unless actually needed.


The old Projects page used PhotoSwipe for its slideshow gallery, with geo-tagged images. In Astro:

  • Move images to public/images/projects/
  • PhotoSwipe can be used again as a lightweight client-side component (it’s framework-agnostic), or swap for a simpler pure-CSS approach
  • Drop the geo-tagging unless you want to add a map embed via Google Maps or Leaflet later

Phase 6 — Store / Gift Ideas (Optional, Later)

The Gift Ideas page had PayPal integration. Options for later:

OptionWhen to use
SnipcartEasiest drop-in, works with static sites, small monthly fee
Stripe Payment LinksNo-code Stripe checkout, no server needed
Headless Shopify + Astro SSRFull e-commerce control, requires switching to server output

For now, the Gift Ideas page can be a placeholder or skipped entirely.


Phase 7 — Fonts & Styles

The old site loaded Google Fonts (Roboto, Raleway, Ubuntu, Josefin Sans) from Google’s CDN at runtime — they were not self-hosted. In Astro, the options are:

  • Self-host fonts using @fontsource packages (install via npm, no external requests, better privacy)
  • Or keep Google Fonts <link> tags in BaseLayout.astro if external requests are acceptable

The current site uses Tailwind CSS — keep that. The brand color #ff5d00 is already configured as brand in tailwind.config.mjs.


File Structure Target

Here’s what the fully migrated Astro site should look like:

src/
  components/
    Header.astro
    Footer.astro
    Nav.astro
    ProjectCard.astro       ← new
    GallerySlideshow.astro  ← new
  content/
    config.ts               ← add 'projects' collection here
    blog/
      punk-jazz-by-chatgpt.md
      how-chatgpt-rewards-lies.md
      ...
    projects/
      automated-trading.md
      software-engineering.md
      ...
  layouts/
    BaseLayout.astro
  pages/
    index.astro             ← Home
    projects.astro          ← Projects gallery + content collection
    contact.astro           ← Contact form (static)
    blog/
      index.astro           ← Blog listing
      [...slug].astro       ← Individual posts
  styles/
    global.css
public/
  images/
    projects/               ← Migrated gallery images
  favicon.svg

Quick-Start Checklist

Here’s the recommended order of operations:

  • Rebuild Home page content (Thinker / Tinkerer / Artist sections)
  • Create src/pages/contact.astro with a static form (Formspree or Netlify Forms)
  • Create src/content/projects/ collection with frontmatter schema in config.ts
  • Add project .md files for each old Tech/Art card
  • Update src/pages/projects.astro to query the collection and render cards
  • Move gallery images to public/images/projects/
  • Add a gallery/slideshow component (PhotoSwipe or similar)
  • Add URL redirects for old /AI-Adventures/ paths
  • Decide on fonts: self-host via @fontsource or keep Google Fonts
  • Revisit Gift Ideas / store page last (optional)

The old site did a lot of heavy lifting invisibly — routing, form handling, gallery generation, store checkout — all buried in obfuscated, hash-named PHP files. The Astro version trades that magic for clarity: every page is a file you can open and read, every post is plain Markdown, and there’s no build platform you don’t control. That’s the goal.