← Blog

Magento

Magento 1.9 to 2.4.7: a six-week Hyvä rescue playbook

An agency calls on a Tuesday. Their client runs €3.2M a year on Magento 1.9, Sinterklaas peak starts in eight weeks, and the EOL warnings are stacking up.

Jacob Molkenboer· Founder · A Brand New Company· 10 Jun 2026· 9 min
Open leather ledger with brass key on spine, cream index card with green ribbon and red wax seal on ivory paper.

An agency calls on a Tuesday in early October. Their client runs a homeware shop doing €3.2M a year on Magento 1.9.4.3. The PHP 5.6 EOL warning has been on the CTO's desk for three years. Sinterklaas peak starts the second week of November. The deadline is non-negotiable, the URL structure is sacred, and the checkout cannot be down for more than four hours on any single Sunday between now and December 5.

We have done this rebuild several times. Here is the playbook we hand the client on day one, almost unchanged each time.

The setup

Magento 1 reached end of life on 30 June 2020. Adobe stopped patching. PCI compliance gets harder every quarter. Most stores still on it in 2026 are there because the rebuild quote scared somebody. The rebuild quote scared somebody because the old quotes were €120k for a Luma theme that would feel exactly as slow as the old store.

Hyvä changed that maths. It strips out Magento's RequireJS and KnockoutJS frontend and replaces it with Tailwind plus Alpine.js. A Lighthouse score that used to take three weeks of frontend surgery is now the default. A six-week rebuild is now a real thing, and that is what this playbook is built around.

Constraints we accept on day one:

  • No SKU or category URL changes. Every product URL on the old store keeps responding on the new store.
  • One four-hour checkout freeze, on one Sunday morning, agreed up front.
  • Customer passwords stay valid. No forced reset emails.
  • Order history visible to customers from day one of the new store.

Week 1: audit and the freeze window

We do not write a single line of theme code in week one. We read.

The audit covers four things. First, the extension list. We export the full list of installed modules and grade them: keep, replace with native M2, replace with a Hyvä-compatible alternative, kill. A typical 2014-era M1 store has about 80 modules in app/code. Half of them are dead. About a quarter exist only because the merchant once asked for a behaviour the team eventually stopped using.

Second, the data shape. We count rows in the heavy tables: catalog_product_entity, sales_order, customer_entity, url_rewrite. For this shop: 11,400 products, 84,000 orders, 31,200 customers, 178,000 URL rewrites. Numbers like these decide whether the cutover takes four hours or fourteen.

Third, the integrations. We list every system that touches Magento: ERP, WMS, accounting, PSP, mail platform, review platform. Each one becomes a row in a spreadsheet with two columns: how it talks to M1 today, how it will talk to M2 on cutover day.

Fourth, the freeze window. We look at the last two years of order data by hour. We find the lowest-volume four-hour block on a Sunday inside the agreed buffer. For this shop that was Sunday 03:00 to 07:00 CET. We block the calendar that day, in writing, on every stakeholder's diary.

Warning

Do not promise a zero-downtime cutover on a Magento 1 to Magento 2 rebuild. The order table has to stop moving while you copy it. A short, scheduled, communicated freeze is honest. A "zero-downtime" claim becomes a six-hour incident around 04:00.

Week 2: data migration and the URL rewrite contract

Adobe shipped a Data Migration Tool for the M1 to M2 jump. It works. It is also a 2017 codebase that needs hand-holding. We use it for the heavy lifting (customers, orders, EAV attributes) and write our own scripts for everything else.

The URL rewrite contract is the single technical promise the merchant cares most about. Google has indexed every SKU page. Affiliates have hardcoded campaign URLs. Email templates from 2019 are still being clicked. A URL change here is a Q4 revenue event, not an SEO event.

The first thing we do on the new Magento 2.4.7 install: export the old core_url_rewrite table and diff it against the new url_rewrite table after the catalog import. Anything that does not match becomes a row in a 301 map.

mysql -h m1-replica -e "SELECT request_path, target_path FROM core_url_rewrite \
  WHERE is_system = 0 OR product_id IS NOT NULL OR category_id IS NOT NULL" \
  store_old > /tmp/m1-rewrites.tsv

mysql -h m2 -e "SELECT request_path, target_path FROM url_rewrite \
  WHERE entity_type IN ('product','category')" \
  store_new > /tmp/m2-rewrites.tsv

comm -23 <(cut -f1 /tmp/m1-rewrites.tsv | sort -u) \
         <(cut -f1 /tmp/m2-rewrites.tsv | sort -u) \
  > /tmp/missing-rewrites.txt

Every URL in missing-rewrites.txt needs an answer before cutover. Most get solved by adjusting the URL key on the new product. The stubborn ones go into nginx as permanent 301 rules so the merchant never has to think about them again.

Week 3: Hyvä theme and the parts we do not rebuild

Week three is the only week that looks like a normal Magento project from the outside. We install the Hyvä Themes package, generate a child theme, and start porting the old store's design.

The rule we hold to: do not redesign on a rebuild. The merchant has not asked for a redesign. The merchant has asked for the same store on a working stack. If the homepage layout has emotional weight inside the company, we copy it pixel for pixel, even the bits we would have argued out of a greenfield brief. Redesign work goes into a separate phase two after Sinterklaas.

What we do quietly improve, because Hyvä makes it almost free:

  • Largest Contentful Paint on the PDP, by removing the four jQuery plugins that were doing image swap on the old store. Alpine handles the same behaviour in about forty lines.
  • Layered navigation, which on M1 made an XHR per click and is now a single Alpine state object.
  • The mini-cart, which used to trigger a full Knockout re-render and now does not.

CMS pages get copied across with a small script that walks the M1 cms_page table and posts to the M2 REST API. Block IDs are remapped, and any inline asset URL that points at the old /media/wysiwyg/ path gets rewritten. We move the actual media files with rsync, not through the admin, because the admin upload reformats filenames and breaks deep links inside CMS content.

Week 4: Hyvä Checkout and the payment integrations

Hyvä Checkout is a separate product from the Hyvä theme. It replaces Magento's stock checkout with a single-page implementation that drops the Knockout layer entirely. It is faster, it is cleaner, and it is where most of the integration risk lives on a Dutch e-commerce rebuild.

Dutch shops need iDEAL, Bancontact, Klarna, sometimes Riverty, sometimes all of them. They are usually routed through Mollie, MultiSafepay, or Adyen. Every PSP has a Hyvä Checkout compatibility module. Most of those modules work. The ones that do not, you find out at 02:00 on cutover night. So you test them in week four.

The test we always run: place a real order, with a real card, for €0.01, through every active payment method, on a staging environment pointed at the PSP's production endpoint. Then refund it. Then place an order, capture it, ship it through the WMS integration, and verify the order shows the correct status in the merchant's ERP. If any step requires a human to "just click here," that step gets an automation ticket before cutover.

Week 5: dry run on production data

Week five is the rehearsal. We take a fresh dump of the M1 production database on Sunday night, run the full migration scripts against it on a clone of the M2 production environment, and time every step. The first time we do this, the migration takes eleven hours. By the third rehearsal we have it down to three hours and forty minutes. The order import is always the longest single step. Customer rehashing is always the runner-up.

During this week we also write the rollback plan. It is one page, and it has three branches. If the migration fails before DNS flip, do nothing and reschedule. If the migration succeeds but the cutover window blows past 07:00, point DNS back at M1 and reschedule. If a critical bug appears within 24 hours of cutover, restore from the pre-cutover M1 snapshot. Each branch has a named owner and a phone number on the page.

Week 6: cutover weekend

Saturday afternoon: final M1 deployment freeze. Nothing ships to M1 after 16:00. Marketing knows. Customer service knows.

Saturday evening: a delta dump runs every hour, capturing only the rows changed since the last full dump. These deltas are queued for replay.

Sunday 03:00: checkout disabled on M1 with a friendly maintenance page. Catalogue stays browseable. We take the final delta, replay all queued deltas against the staging M2, run integrity checks (order count, customer count, total revenue last 30 days, all match within the agreed tolerance), and run the URL rewrite diff one last time.

Sunday 05:30: DNS flip. TTL was lowered to 60 seconds on Friday. The new store is live.

Sunday 06:00: smoke test. A real human places a real order with a real card. Order lands in the WMS. Confirmation email arrives.

Sunday 07:00: announce. Marketing pushes the "we're back" newsletter that was drafted on Friday.

What we got wrong

On the first rescue we did like this, we underestimated the customer password rehash. Magento 2 uses a different hashing scheme by default, and the migration tool handles it correctly, but we had not load-tested the login path against 30,000 returning customers hitting the new store in the first hour. Login was slow on Sunday morning. We fixed it by warming PHP-FPM and increasing the Redis connection pool, but we should have caught it in week five.

On a later rescue, we shipped a checkout that worked perfectly with one PSP but failed silently for one specific iDEAL bank on mobile Safari. The PSP's Hyvä Checkout module had a bug in how it serialised the issuer ID. We caught it because a customer service agent forwarded a complaint within two hours of cutover. We patched it inside the hour. Now we have a test matrix that includes that exact bank, on that exact browser, on every rebuild.

The pattern: the things that bite are never in the catalogue migration. They are in the long tail of integrations that nobody documented because they had been working for nine years.

The shape of the work

When we ran this rescue for a homeware client last year, the thing we kept hitting was the URL rewrite tail. They had a custom blog module that wrote rewrites to a non-standard table. We solved it by extending our diff script to read from that table too, which is now in our standard kit. That kind of bespoke legacy migration work is most of what we do for agencies who need a Magento rebuild without losing peak.

The smallest thing you can do today: export your core_url_rewrite table to a TSV file, count the rows where is_system = 0, and put that number in a shared doc. That number is the size of the contract you are about to write with Google.

Key takeaway

A Magento 1 to 2.4.7 rescue with Hyvä is a six-week job if you protect the URL contract, schedule the freeze in writing, and rehearse the cutover on real data.

FAQ

Is six weeks really enough to migrate a €3M Magento 1 store?

Yes, if you accept the playbook constraints: no redesign, one scheduled checkout freeze, frozen URL contract, and a tight integration scope. Redesign or scope creep doubles the timeline.

Why Hyvä instead of the default Luma theme?

Luma carries Magento's RequireJS and Knockout frontend, which is the slow part. Hyvä replaces it with Tailwind and Alpine, so a six-week rebuild ships with a fast PDP by default.

Can we keep every URL from the old store?

Almost always. The Data Migration Tool plus a diff against the new url_rewrite table catches mismatches, and the residual list goes into nginx as permanent 301 rules before DNS flip.

How do you avoid forced password resets for returning customers?

The Data Migration Tool ports the customer password hashes. Magento 2 rehashes on first successful login, transparently. No reset email goes out, but you need to load-test the login path.

What is the biggest risk on cutover night?

Payment integrations. PSP modules for Hyvä Checkout sometimes ship with edge-case bugs that only fire on one bank or one device. Test every method with a real card before cutover, not after.

magentomigrationlegacy sitese-commercephpcase study

Building something?

Start a project