Build WordPress
KINDAreplaces $4/mosaves $48/yrback to the verdict
A single-author CMS whose hard problems are extracting a live WordPress site completely rather than partially, keeping every published URL working after the switch, and rendering imported content that was written for a renderer you no longer have.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the site and the import scripts.
Get it nodejs.org or your version manager. open ↗
Verify
node --version prints v22 or higher - accountfree while the site is still up
Why Extraction runs against the live site, and doing it after the hosting lapses is not possible.
Get it An application password or read-only credentials on the existing site, plus its API base URL.
Verify
A request for one post returns JSON and the totals headers - have readyfree
Why It is the complete list of URLs you are about to become responsible for, and it disappears with the site.
Get it Download the sitemap and any index files it points at, before anything else.
Verify
The saved file lists every URL you expect, including archives and the feed - have readya few pounds a month
Why You are the hosting relationship now, and a lapsed certificate is the most common way a personal site goes dark.
Get it Any small instance with an automatic certificate, or a platform that manages one.
Verify
The site loads with a valid certificate and renewal is automatic - have readypennies per month
Why The thing you removed was doing this for you, and you will not notice the absence until you need it.
Get it Object storage or a second machine, with the backup command scheduled.
Verify
A restore from the copy reproduces counts and checksums
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- posts(id, kind, slug, title, body_format, body, excerpt, status, published_at, updated_at) · body_format is markdown or imported_html - legacy(post_id, source_url, source_id, source_modified, import_batch, unhandled_markers_json) · what the old site said, kept for the arguments later - redirects(from_path, to_path, code, reason, created_at) · every old URL that is not the new URL - media(id, filename, original_url, bytes, sha256, width, height, alt, fetched_at) - post_media(post_id, media_id, rewritten) · which references in a body were rewritten to local files - terms(id, kind, slug, name) and post_terms(post_id, term_id) · categories and tags, flattened deliberately - imports(id, started_at, finished_at, source_base, expected_counts_json, actual_counts_json, ok) · the manifest, and the gate
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/site.db | Posts, terms, redirects and the import manifests. |
MEDIA_DIR | required | ./data/media | Originals and derived sizes. Originals are never deleted by the application. |
SOURCE_API_BASE | optional | https://old.example.com/wp-json/wp/v2 | Only needed while importing. Remove it once the manifest matches. |
SOURCE_AUTHsecret | optional | user:application-password-redacted | Read-only credentials for the old site. Delete after the final import. |
ADMIN_PASSWORD_HASHsecret | required | argon2id$...redacted | The single author login, generated by the included command. |
PUBLIC_BASE_URL | required | https://example.com | Used in the feed, the sitemap and every redirect target. |
BACKUP_DIR | required | ./backups | Where the archive lands before it is copied off the host. |
The build, in order
Get everything out, and prove it
A complete copy of the live site sits on your disk with counts that match what the site reported.
The header tells you how many there are, so there is no excuse.
Files
scripts/import.tstests/import-paging.test.tsThe export file is a list of links, not an archive.
Files
scripts/media.tsThis is the only moment you can still go back and look.
Files
scripts/import.ts
done when · tick each as it passeswatch out- Requesting a collection without paging returns the first page only, and since a hundred posts looks like a lot, the import is declared a success while half the archive is still on a host you are about to cancel. Read the totals from the response headers, page until you have them all, and make a count mismatch fail the run rather than print a warning.
Content written for a renderer you no longer have
Imported bodies render correctly or are visibly flagged, and nothing is silently dropped.
You cannot get back what you removed at import time.
Files
src/render.tssrc/import/body.tsInvisible loss is the failure mode of every migration.
Files
src/import/markers.tstests/markers.test.tsSo you can tell a broken image from one that was always broken.
Files
src/import/media.ts
done when · tick each as it passeswatch out- Stripping anything that is not plain HTML on import produces pages that look fine and are missing a gallery, a form and three embeds, and since the original is gone you will not find out until a reader tells you. Keep the original body, list every unhandled marker per post, and show the count in the admin so the work is visible instead of invisible.
Every old link still goes somewhere
No URL that worked before the switch returns a bare not found after it.
Including archives, feeds and attachment pages, which everyone forgets.
Files
src/redirects.tsIt is the only complete list of what you are responsible for.
Files
scripts/check-urls.tstests/redirects.test.tsFeed readers are the subscribers who never tell you they left.
Files
src/routes/feed.ts
done when · tick each as it passeswatch out- Changing the permalink structure because the new one is tidier breaks every link anyone ever shared, and search engines and other people's posts do not come back once they have been redirected to a not found page. Keep the old paths working with permanent redirects, and only change the structure once the redirect table exists and the sitemap check passes.
Writing the next post
Publishing something new is faster than it was on the old site.
Derived files are cheap; originals are not recoverable.
Files
src/media.tstests/media.test.tsOne less thing that can be down at midnight.
Files
src/posts.tsIf it is slower you will write less, which defeats the project.
Files
src/admin/editor.ts
done when · tick each as it passeswatch out- Resizing an upload and throwing the original away means the day you change the layout or want a larger image you are re-uploading from a phone backup, which is the same class of loss as the import problem one phase earlier. Keep the original file, derive sizes from it, and let the derived files be deletable and regenerable.
You are the host nowproduct builder
Backups run, restores are rehearsed, and the content can leave again.
A backup nobody has restored is a directory of hope.
Files
scripts/restore.tstests/restore.test.tsThe format that will still open in ten years.
Files
scripts/export.tsThe hosting relationship you removed was doing this for you.
Files
README.mddocs/operations.md
done when · tick each as it passeswatch out- Treating backups as a command you will run later means the first restore you attempt is the one after the disk failed, and a restore that has never been rehearsed usually reveals that the media directory was excluded. Run the restore in the test suite against a fresh directory and compare counts and checksums, so the backup is proved rather than assumed.
That is the whole plan for WordPress. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Plugins and themes.
- Commerce of any kind.
- Multiple authors with roles.
- A block editor.
- The plugin ecosystem: SEO, forms, caching, memberships, analytics, backups, all one click away in WordPress and all your problem in the DIY build
- WooCommerce and anything resembling commerce
- Gutenberg block editing and the visual page builders non-technical people actually like
- A theme marketplace, so your site looks like whatever the agent felt like that day
- Any hope of handing the site to a developer who already knows the stack
- Security updates and managed hosting done by someone who is not you
- A static export of the whole site, so the content survives even if the application stops running
- Comments by email rather than a form, which removes the spam problem instead of fighting it
- A second reader for the old site kept running for a month after the switch, so you can compare pages you have not looked at yet
Need the files? The project pack on the verdict page hands your agent the whole brief · more website builders.