Skip to content

Keep every URL through a Drupal 7 to 11 migration

How to inventory every URL on a Drupal 7 site, decide what to keep or redirect, set up the redirect module and pathauto, and verify before and after launch.

8 min readBlueKiezel
  • migration
  • seo
  • drupal 7
  • redirects

A migration from Drupal 7 to Drupal 11 does not have to cost you rankings, although nobody can promise that it will not. What you can control is the signals: every URL that matters can be kept or redirected, and every one of them can be verified before launch. The work is unglamorous: build a complete inventory, decide the fate of each URL, configure the redirect module and pathauto to match, then test the whole list twice.

Build the URL inventory

You cannot keep what you have not listed. The inventory has four sources, and each catches URLs the others miss.

The database. Drupal 7 stores aliases in the url_alias table. Export it with Drush:

drush sql-query "SELECT source, alias, language FROM url_alias" > aliases.tsv

Also export the existing redirects if the site has the Drupal 7 redirect module installed. The redirect table holds old paths that are already being forwarded, and those must survive the move too:

drush sql-query "SELECT source, redirect, status_code FROM redirect" > redirects.tsv

Add the paths that have no alias: node/123, taxonomy/term/45, user/7, views pages and any custom menu callbacks. A view listing at /news with a pager has many URLs, and ?page=3 is one of them.

Search Console. Export the Performance report for the last sixteen months, all pages, and the Pages report under Indexing. This tells you which URLs Google actually knows and which ones earn clicks. It also catches URLs that no longer exist in the database but still get impressions.

Analytics. Export the landing-pages report for the last twelve months. Traffic is the tie-breaker when deciding what to keep, and analytics will show campaign URLs and tagged links that Search Console never sees.

A crawl. Run Screaming Frog against the live site with images, PDFs and JavaScript-rendered links included. It finds the URLs that are linked but not aliased: files under sites/default/files, RSS feeds, legacy print views, and the odd hard-coded link in a block.

Merge the four exports into one sheet keyed on the path. Normalise trailing slashes, case and query strings before deduplicating, because /About-Us/ and /about-us are the same page to a human and two rows to a spreadsheet.

Decide: keep, redirect or retire

Each row gets one of three decisions.

| Decision | When | What happens at launch | | --- | --- | --- | | Keep | The page survives with the same URL | Alias reproduced exactly on Drupal 11, returns 200 | | Redirect | The page survives with a new URL, or merges into another page | 301 from the old path to the new one, one hop | | Retire | The page is gone and nothing replaces it | 410 if it had traffic or links, plain 404 otherwise |

Keep is the default. Changing URLs during a migration is a choice, and every change is a small bet against your own rankings. If the site is also getting a content restructure, do that as a separate project or accept that the redirect column will be long. The content audit is where merge decisions get made, and its output is the first draft of this column.

Retire is where teams get nervous. A page with no traffic in twelve months, no inbound links and no legal reason to exist can go. Redirecting everything to the homepage is not a kindness, and Google treats mass redirects to an unrelated page as soft 404s anyway.

Redirect module and pathauto patterns

On the Drupal 11 side, two modules carry the plan.

Pathauto must be configured before content is migrated, not after. Reproduce the Drupal 7 patterns exactly, per content type and per vocabulary. If Drupal 7 used news/[node:title] for articles, Drupal 11 must use the same pattern, with the same punctuation settings, the same maximum length and the same word-removal list. Then set pathauto's update action to leave existing aliases untouched, so that a later edit by an editor does not silently rewrite a URL that Google has indexed for years.

The Migrate API handles the aliases themselves. With migrate_upgrade, migrate_plus and migrate_tools installed, the d7_url_alias migration copies every row of url_alias across, and the redirect module's d7_path_redirect migration brings the old redirects with it. Run them after the content migrations, check the counts, and compare against your inventory:

drush migrate:status --group=migrate_drupal_7
drush migrate:import d7_url_alias
drush migrate:import d7_path_redirect

Node IDs are preserved by default, so node/123 on the old site is node/123 on the new one. Keep it that way. Remapping IDs breaks every internal link stored in body text and every URL that was never aliased.

The redirect module takes the redirect and retire columns. Install it with Composer, enable its redirect_404 submodule for logging, and import the redirect list. The path_redirect_import module reads a CSV of old path, new path and status code, which is exactly what your sheet already is.

composer require drupal/redirect drupal/path_redirect_import
drush en redirect redirect_404 path_redirect_import

Keep the list flat. Every redirect should land on a page that returns 200 directly. If the sheet says A goes to B and B goes to C, fix it to A goes to C before importing. Chains cost crawl budget and lose a little signal at every hop.

The pre-launch verification loop

Verification is a loop, not a step, because the first pass always finds problems.

Point a hosts-file entry or a staging hostname at the Drupal 11 build and run the full inventory through it. Screaming Frog's list mode takes the sheet as input and reports the status code and final destination for every row. The expected result for each decision column is simple: keep rows return 200 at the same path, redirect rows return one 301 to a URL that returns 200, retire rows return 410 or 404 as planned.

For spot checks, or when you want to script it into the deploy pipeline, curl does the same job one URL at a time:

while read -r url; do
  printf '%s %s\n' "$(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' "$url")" "$url"
done < inventory.txt

Filter the output for anything that is not the expected code. Typical first-pass findings are aliases with different capitalisation, trailing slashes that pathauto stripped, file paths that moved because the files directory was reorganised, and views that were rebuilt at a slightly different path.

Fix, re-import, run again. Stop when the list is clean, then run it once more on the morning of launch, because somebody will have edited a pathauto pattern in between.

Also check the pieces around the URLs: canonical tags point to the new site's hostname, the XML sitemap only lists keep rows and redirect targets, the robots file is not the staging one, and hreflang alternates match if the site is multilingual.

The first 30 days after launch

The inventory did its job. Now the search engines confirm it.

  • Day one. Submit the new sitemap in Search Console and request indexing for the top twenty pages by traffic. Confirm the old sitemap URL either still resolves or redirects to the new one.
  • Daily for the first week. Read the redirect_404 log. It shows real requests for paths that returned 404, sorted by frequency. Anything that appears more than a handful of times gets a redirect added through the admin interface, no deploy needed.
  • Weekly. Check the Pages report in Search Console for a rise in "Not found" or "Redirect error". Compare impressions and clicks for the top hundred pages against the same week before launch. A dip in the first week is normal while Google recrawls. A dip that is still there in week four is a page to look at.
  • Day 30. Re-run the full inventory crawl one last time and archive the result. Rankings that were going to move have moved by now, and the archive is your evidence for what the migration did and did not change.

Keep the Drupal 7 database export somewhere safe. Six months from now someone will ask about a URL nobody listed, and the old url_alias table will answer.

Where to start

Our Drupal 7 migration service includes the inventory, the redirect map and the verification loop as standard deliverables, because a migration that loses rankings is not finished. If you want to know how large your inventory is and how much of it can be kept unchanged before you commit, the fixed-price five-day assessment produces that list along with the rest of the migration plan.