Zach’s ugly mug (his face) Zach Leatherman

Migrating my 16+ year old web site to Netlify in a few short days

February 04, 2022

Yes, I work at Netlify.

Yes, I hit my two year anniversary of working at Netlify a week ago.

And no, my site—prior to this week—was not hosted on Netlify.

Why? Pure, unadulterated procrastination. I should have moved it over sooner. I’m very glad to have made the move. Let’s do a quick summary of the process and the pitfalls I ran into—as well as the benefits I’m seeing after the migration.

git Submodules

I have a bunch of microsites that live on my primary domain. I had experimented with git submodules for these, but these were a bit painful to manage. I switched these over to use degit instead.

For example my Unicode Range Interchange project lives at /unicode-range-interchange/ but the source code for it lives in a different repo entirely. I could have hosted this on a separate app on Netlify and solved this using a proxy redirect, but for simplicity I decided to keep it local and in the same build. (It’s worth noting that using this approach means updates to unicode-range-interchange require a build to see them in production, which has its own benefits and drawbacks—specifically thinking about rollbacks here)

I added this to my build script to check out the project at build time and place the contents directly into my output directory (_site/unicode-range-interchange):

npx degit zachleat/unicode-range-interchange _site/unicode-range-interchange

Atomic Builds

Bad Analytics Code

The method I was using to inject analytics-driven popularity data on my site (see Popular Posts) relied (embarrassingly enough) on non-atomic builds. It used things in my _site directory and was pretty brittle. Because the _site directory doesn’t exist at the beginning of the build on Netlify, I had to rework this code (it was a good idea anyway, the code was bad).

The pretty big limitation preventing a wider application of this code (why I haven’t made it a plugin yet) is that it relies a bit too heavily on my file name convention for blog posts ( to calculate the PageViews per Day metric used on my site. Once I figure out a better way to generalize the solution there, I can publish this for re-use by others.

Deleting Hand-rolled Code

My previous deployment method was using rsync. I got rid of all that extra hand-rolled code from my repo. I deleted six npm scripts that supported my old deployment process, whew. I was also able to get rid of a bunch of custom code to add Brotli compression to my site (now that’s done for me automatically on Netlify).

Deployment Race Conditions

Before Netlify—without atomic builds and deploys—I was seeing production issues with some of my automated OpenGraph image generation code. Specifically, race conditions in my deployment script where the OpenGraph images would be requested before the pages were uploaded causing HTTP 404 error pages to show up in the screenshots. 😱 Netlify solves this by flipping the switch from the old version to the new without cross-deploy contamination.


Proxy Redirects Good

Frustratingly, some of the pages on my site used PHP. Most of the instances were small things, like using sleep() on a page to simulate poor network conditions, like these Asynchronous CSS Loading tests (and on GitHub).

I left these in place and used Proxy redirects to shadow them from my old web host.

/test/async-css-loading/* 200!

I ended up using this same approach for my Twitter Archive too, which doesn’t actually use any PHP but I just simply couldn’t be bothered with migrating it yet 😅.

Redirect Fidelity

Had a couple of mod_rewrite redirects leftover from my WordPress days when I used to use the full date in the path, kind of like /YYYY/MM/DD/slug/. Looked like this in my .htaccess file:

RedirectMatch 301 /web/\d{4}/\d{2}/\d{2}/(.*)$ /web/$1

It’s easy enough to move these into my Netlify _redirects file—however I lost some detail in the conversion. Now, it looks like this:

/web/:year/:month/:day/* /web/:splat 301!

Specifically, there is no way to ensure that :year, :month, and :day were only numeric (matching /web/a/b/c/ when it would not have before). I need to be a bit extra careful with deeper paths on my site to ensure I don’t get unintentional redirecting. But in all fairness, I should probably retire these redirects—it’s been about 10 years since my site was on WordPress 😅.


I tried to get too fancy with my DNS. Here’s what I wish I had done (we don’t need to belabor what I did—late at night—which was bad 😅 and caused an outage on my site).

I wish I had gone all in on Netlify DNS from the beginning. Specifically, following these steps:

  1. Configure Netlify DNS and copy the same DNS records from your old provider to your new provider (I had some extra ones for email).
  2. Go to the Domain Management portion of your site’s Netlify settings and add the Domain Alias. Netlify will do the rest for you.
  3. Change your Authoritative Name Servers on your old provider to point to Netlify’s servers. Don’t remove any DNS configuration or change any other DNS settings on your old host. Leave everything else as-is. Only update your Authoritative Name Servers to point to Netlify. These changes may take a few days to propagate globally so you want to make sure the old and new way both work, simultaneously.


The best Netlify features that I’m glad to have access to now:

  • Super easy branch deploys for testing things out.
  • Much easier _redirects syntax (and proxy redirects too!)
  • Atomic deploys. A reliable rollback mechanism means that I can be much less stressed about deploys.
  • And a brand new feature: Scheduled builds!! Because all of my building happened on my development machine, there was no way for me to trigger a deploy from the cloud. Now I can update my popular posts every day, automatically.
  • And having the site on a global CDN ain’t bad either.

I’m sure a bunch more great features will shake out in the future, but these are the biggest ones in my mind right now!

< Newer
Add Responsive-Friendly Enhancements to `details` with `details-utils`
Older >
Back to the Facepile, Featherweight Edition

Zach Leatherman IndieWeb Avatar for a builder for the web and the creator/maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 81 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »


Owen Buckley 💡Juan Fernandes (he/him)


Denver Prophit Jr. 🇺🇦Cory DransfeldtJack OsborneangelChris 🍋🎙Erik VorhesLee FisherArpitMatt BiilmannMaurilio Rodrigues CordeiroJess Peck 🐍🤖Owen Buckley 💡MaëligAlejandro AR ⚡amberleyuptonkingMatthias OttTodd Libby 🦞Dez BebernissJesse HeadyTom VanAntwerpJuan Fernandes (he/him)Trevor JohnstonGreg WolanskiТихий БесAshur Cabreradef timmy_sql(self, **kwargs):Jakov KovacMike Aparicio
  1. kinduff


    Show original post And you didn't even mentioned Netlify's Functions! I'm surprised you didn't found a use case for it during the migration. I'm glad you're dogfooding.

  2. Denver Prophit Jr. 🇺🇦


    I’m going to use GA groupings. I believe I need do a loop filter to search for post.title so I can theme the results? Set to a @ weekly cron Do this for each collection + home page. Next will be indieweb stats. 🧮

  3. Zach Leatherman


    Not sure—can the title can be relied upon as unique?

Shamelessly plug your related post

These are webmentions via the IndieWeb and

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)