Magento SEO Checklist for 2.4.x: Severity, Admin Path and a Verify Step for Every Item
A Magento SEO checklist for 2.4.x stores: 50 items, each with a severity, the admin path or bin/magento command, and a way to verify it passes.

This Magento SEO checklist is the audit sheet we run on a store before changing anything: 50 items for Magento Open Source and Adobe Commerce 2.4.7-2.4.9. Every item carries a severity, the admin path or bin/magento command that sets it, and a verify step with the result that counts as a pass, while the setup and the reasoning behind each setting live in the Magento SEO guide.
Disclosure: OCM Labs, which publishes this blog, builds SEO Suite, an SEO module for Magento. Items it covers carry an SEO Suite line. Every check here runs on a store without it.
How to run this audit
Work the areas in order, and inside each one clear Blockers and Highs before any Medium. A perfect title on a page Google cannot fetch is still invisible.
| BLOCKER | Stops pages being crawled or indexed, or drops them from results |
|---|---|
| HIGH | Splits or wastes ranking signals across duplicates, or hides key pages |
| MEDIUM | Weakens how pages show or compete in results |
| LOW | Polish; fix when the rest passes |
You need:
- Shell access for
bin/magento config:show, which reads what is saved at each scope. If it prints nothing or an error at one scope, nothing is saved there: rerun it one level up and fall back to the module default before you call it a fail. curl, for status codes, redirect hops and the raw HTML before JavaScript runs.- Search Console, the Rich Results Test, PageSpeed Insights and a site crawler.
An item is done when its verify line passes, whatever the admin form says. SEO Suite does not generate XML sitemaps, manage URL rewrites or redirects, monitor 404s, pull Search Console data or touch site speed, so those items read No. Its score is guidance, not a ranking prediction, and passing this list is not a ranking promise either.
Can Google crawl and index your Magento store?
When an item here fails, the robots.txt, meta robots and sitemap setup covers what to set.
2.1 robots.txt matches the admin field and blocks no product or category URL
- Severity: Blocker
- Where: Content > Design > Configuration > (website) > Search Engine Robots, the custom instructions field (
design/search_engine_robots/custom_instructions); a physical robots.txt in the web root can shadow it - Verify:
curl -s https://example.com/robots.txtprints the same rules asbin/magento config:show --scope=websites --scope-code=base design/search_engine_robots/custom_instructions(swapbasefor each website code you run), and noDisallowline matches a product or category URL. Search Console > Settings > robots.txt shows the file fetched without errors.
2.2 Default Robots is INDEX, FOLLOW on every production store view
- Severity: Blocker
- Where: Content > Design > Configuration > (website) > Search Engine Robots > Default Robots (
design/search_engine_robots/default_robots) - Verify:
bin/magento config:show --scope=websites --scope-code=base design/search_engine_robots/default_robotsreturnsINDEX,FOLLOWfor each website code, andcurl -s https://example.com/ | grep -i 'name="robots"'shows the same value in the page head of every store view. - SEO Suite: Partly. It adds per-page and store-wide robots rules; native Default Robots stays the baseline.
2.3 Staging and development hosts cannot be indexed
- Severity: Blocker
- Where: HTTP authentication in the staging web server config, plus Default Robots set to
NOINDEX,NOFOLLOWin the staging database - Verify:
curl -sI https://staging.example.com/returns401. A Google search forsite:staging.example.comreturns nothing. Recheck production's Default Robots after every database copy.
2.4 Catalog search result pages stay out of the index
- Severity: High
- Where:
Disallow: /catalogsearch/in robots.txt if none were ever indexed; otherwise a noindex from the search results layout or an SEO module rule, with the Disallow added once they drop - Verify: If robots.txt already disallows
/catalogsearch/and Page indexing shows no search URLs indexed, URL Inspection onhttps://example.com/catalogsearch/result/?q=bootsreports it as blocked by robots.txt and not indexed. Otherwisecurl -s "https://example.com/catalogsearch/result/?q=boots" | grep -i 'name="robots"'shows aNOINDEXvalue, and the Disallow goes in only after Google drops these URLs. - SEO Suite: a store-wide robots rule for search result pages.
2.5 The XML sitemap is configured, generated by cron and submitted in Search Console
- Severity: High
- Where: Stores > Configuration > Catalog > XML Sitemap (
sitemap/generate/enabled,sitemap/search_engines/submission_robots); Marketing > SEO & Search > Site Map - Verify:
bin/magento config:show sitemap/generate/enabledreturns1. The Site Map grid shows a recent generation date that matches the frequency you set.curl -s https://example.com/robots.txt | grep -i sitemapprints the sitemap URL. Search Console > Indexing > Sitemaps shows the status as Success. Nobin/magentocommand generates a sitemap, so if the date is stale,SELECT status, executed_at FROM cron_schedule WHERE job_code = 'sitemap_generate' ORDER BY scheduled_at DESC LIMIT 5;should show recentsuccessrows.
2.6 The sitemap lists only canonical, indexable URLs that return 200
- Severity: Medium
- Where: Marketing > SEO & Search > Site Map; product, category and CMS page status and visibility
- Verify: Load the sitemap URLs into a site crawler in list mode. Every URL returns 200, carries no noindex and declares itself as canonical.
2.7 The Page indexing report has been read, not glanced at
- Severity: High
- Where: Search Console > Indexing > Pages
- Verify: Export the "Crawled - currently not indexed" and "Duplicate without user-selected canonical" lists. Sample 20 URLs from each and sort them by type (filter, sort parameter, simple child product, CMS page, real product). The mix matches what your layered navigation rule set in section 5 expects;
?product_list_order=URLs mean 4.4 failed, simple products mean 4.3 did. Record both counts with the date.
Do Magento URLs, rewrites and redirects resolve to one address?
URL settings are cheap to check and expensive to change; the URL suffix, category path and rewrite settings cover changing one safely.
3.1 Web server rewrites are on and no URL contains index.php
- Severity: High
- Where: Stores > Configuration > General > Web > Use Web Server Rewrites (
web/seo/use_rewrites), plus the Apache.htaccessor nginx rewrite rules - Verify:
bin/magento config:show web/seo/use_rewritesreturns1, and a full crawl finds no internal link containing/index.php/.
3.2 Product and category URL suffixes are set once and left alone
- Severity: High
- Where: Stores > Configuration > Catalog > Catalog > Search Engine Optimization (
catalog/seo/product_url_suffix,catalog/seo/category_url_suffix) - Verify:
bin/magento config:show catalog/seo/product_url_suffixandbin/magento config:show catalog/seo/category_url_suffixreturn the suffix your live product and category URLs end in, such as.html, at every scope where a value is saved.
3.3 Product URLs skip the category path, or canonicalize to one version
- Severity: High
- Where: the same group (
catalog/seo/product_use_categories,catalog/seo/generate_category_product_rewrites); a store upgraded from an early 2.4 release keeps its old category/product rewrites - Verify:
bin/magento config:show catalog/seo/product_use_categoriesreturns0, andbin/magento config:show catalog/seo/generate_category_product_rewritesshows whether rewrite generation is on. Then, whatever those return,curl -sI https://example.com/mens/boots/trail-runner.htmlreturns404, orcurl -son the same URL shows arel="canonical"pointing at the shorthttps://example.com/trail-runner.html. If generation returns1, do not switch it off without a backup ofurl_rewriteand a log and backlink check for category-path URLs: Adobe warns that doing so permanently deletes the existing category/product rewrites.
3.4 Changing a URL key creates a permanent redirect
- Severity: High
- Where: the same group (
catalog/seo/save_rewrites_history), which sets the default of the "Create Permanent Redirect for old URL" checkbox on product and category forms - Verify:
bin/magento config:show catalog/seo/save_rewrites_historyreturns1. On staging, change a test product's URL key, thencurl -sI -u user:pass https://staging.example.com/old-key.htmlreturns301with aLocationheader on the new key.
3.5 Redirects resolve in one hop, with no chains or loops
- Severity: Medium
- Where: Marketing > SEO & Search > URL Rewrites; Magento has no native command that regenerates them
- Verify:
curl -sIL https://example.com/old-key.html | grep -i -e '^HTTP' -e '^location'shows exactly one301followed by a200. Your crawler's redirect chain report is empty.
3.6 One protocol and one host, with every variant 301ing to it in one hop
- Severity: High
- Where: Stores > Configuration > General > Web > Use Secure URLs on Storefront (
web/secure/use_in_frontend); one host redirect rule at the web server or CDN - Verify:
curl -sI http://example.com/,curl -sI http://www.example.com/andcurl -sI https://example.com/each return a single301tohttps://www.example.com/, or whichever origin you chose.bin/magento config:show web/secure/use_in_frontendreturns1.
3.7 Out-of-stock and disabled products return the status code you intended
- Severity: Medium
- Where: Display Out of Stock Products (
cataloginventory/options/show_out_of_stock); the Enable Product switch; URL Rewrites for replacement redirects - Verify:
curl -sIone out-of-stock product URL and one disabled product URL. Write down what each returns and compare it with your written policy.
Are canonical tags folding Magento's duplicate URLs?
Sort orders, page sizes, child products and category paths all mint duplicate URLs; how Magento's canonical switches behave explains what each setting emits.
4.1 Canonical tags are on for products and categories
- Severity: High
- Where: both canonical settings in Stores > Configuration > Catalog > Catalog > Search Engine Optimization (
catalog/seo/category_canonical_tag,catalog/seo/product_canonical_tag), at default and every store view scope - Verify:
bin/magento config:show catalog/seo/category_canonical_tagandbin/magento config:show catalog/seo/product_canonical_tagboth return1.curl -s https://example.com/trail-runner.html | grep -i 'rel="canonical"'prints one tag with an absolute URL. - SEO Suite: one canonical on every product, category and CMS page, with a product mode and filtered and paginated category handling.
4.2 Every page carries exactly one rel="canonical"
- Severity: High
- Where: theme templates, plus any module that writes to the page head (SEO, layered navigation, AMP)
- Verify:
curl -s https://example.com/trail-runner.html | grep -o -i 'rel="canonical"' | wc -lreturns1. Repeat on a category, a filtered category and a CMS page. - SEO Suite: Partly. It does not override a canonical hardcoded in a theme template; remove that one from the template.
4.3 Configurable product children are Not Visible Individually
- Severity: High
- Where: Catalog > Products > (simple child) > Visibility, or the grid's Update attributes action in bulk; fix the import template too
- Verify: In Catalog > Products, filter Type to Simple Product and Visibility to Catalog, Search. Every row returned is a product you sell on its own, and none of the child URLs appear in the sitemap.
4.4 Sort, limit and view-mode parameters do not create indexable duplicates
- Severity: High
- Where: the category canonical (4.1), with no noindex on these URLs; robots.txt only as an optional crawl-budget step once Google has consolidated them, or from day one if they never were in the index
- Verify:
curl -s "https://example.com/mens/boots.html?product_list_order=price&product_list_limit=36&product_list_mode=list" | grep -i -e 'name="robots"' -e 'rel="canonical"'showshttps://example.com/mens/boots.htmlas canonical and no noindex in the robots meta. In Page indexing, sort URLs collect under "Alternate page with proper canonical tag", not "Excluded by 'noindex' tag". Ifcurl -s https://example.com/robots.txtcarries theproduct_list_Disallow lines, URL Inspection on a sort URL reports it as blocked by robots.txt and not indexed, and the lines went in only after Google had consolidated these URLs or on a store that never had them indexed. - SEO Suite: Yes. Its category canonical drops sort, limit and mode parameters, and it adds no robots value to those URLs, so they get the canonical alone.
4.5 Paginated category pages canonicalize to themselves
- Severity: Medium
- Where: category canonical output from your theme or SEO module
- Verify:
curl -s "https://example.com/mens/boots.html?p=2" | grep -i 'rel="canonical"'returns the?p=2URL itself, not page 1. - SEO Suite: a paginated category canonical mode.
Which layered navigation URLs are indexable, canonicalized or blocked?
Native filter URLs are query parameters such as ?color=49, and this is the rule set we audit them against. Filter, sort and price-range URLs get the canonical alone: a canonical to the unfiltered category and no noindex. A noindex paired with a canonical that points at another URL sends Google mixed signals. Leave these URLs crawlable so Google can read the canonical and consolidate them.
A robots.txt Disallow stays an optional crawl-budget step for parameter patterns that waste crawl: sort, limit, view mode and deep multi-filter combinations. Add it only after Google has consolidated those URLs, or from day one on a store that never had them indexed, because Google cannot read the canonical on a blocked URL. The layered navigation setup covers the reasoning.
| INDEX? | ROBOTS META | CANONICAL | ROBOTS.TXT | |
|---|---|---|---|---|
| ONE FILTER VALUE PEOPLE SEARCH FOR (WATERPROOF BOOTS) | Yes, as a real subcategory | index, follow on the subcategory | The subcategory's own URL | Allow |
| ONE FILTER VALUE WITH NO SEARCH DEMAND | No, consolidated by the canonical | No noindex | The unfiltered category | Allow |
| TWO OR MORE FILTERS COMBINED | No, consolidated by the canonical | No noindex | The unfiltered category | Allow; optional Disallow for deep combinations once Google has consolidated them, or from day one if never indexed |
| PRICE RANGES | No, consolidated by the canonical | No noindex | The unfiltered category | Allow; optional Disallow once Google has consolidated them, or from day one if never indexed |
| SORT, LIMIT AND VIEW MODE | No, consolidated by the canonical | No noindex | The unfiltered category | Allow; optional Disallow once Google has consolidated them, or from day one if never indexed |
| PAGINATION, ?P=2 AND UP | Yes | index, follow | Itself | Allow |
| CATALOG SEARCH RESULTS | No | noindex, follow | Itself | Allow until dropped, then Disallow; Disallow from day one if never indexed |
These are the search and filter lines from the guide's starting robots.txt, line for line. Audit 2.1, 2.4 and 5.4 against your own version of them.
User-agent: *
# Internal search results
Disallow: /catalogsearch/
# Optional crawl-budget lines: add them only once Google has consolidated the matching URLs,
# or from day one on a store that never had them indexed
# Sort, direction, limit and view-mode parameters
Disallow: /*?*product_list_order=
Disallow: /*?*product_list_dir=
Disallow: /*?*product_list_limit=
Disallow: /*?*product_list_mode=
# Price ranges and store switching
Disallow: /*?*price=
Disallow: /*?*___store=5.1 A written rule set says which filter URLs are indexable, canonicalized or blocked
- Severity: High
- Where: a shared document developers and merchandisers both read; it drives 5.2 to 5.4
- Verify: For each attribute with Use in Layered Navigation set (Stores > Attributes > Product > (attribute) > Storefront Properties), the rule set names its class from the table above.
5.2 Single-filter URLs without search demand carry the category canonical and no noindex
- Severity: High
- Where: the category canonical (4.1) or your SEO module's filtered-category canonical; no robots rule that adds a noindex to these URLs
- Verify:
curl -s "https://example.com/mens/boots.html?color=49" | grep -i -e 'name="robots"' -e 'rel="canonical"'shows the unfiltered category as canonical and no noindex in the robots meta. In Page indexing, these URLs collect under "Alternate page with proper canonical tag", not "Excluded by 'noindex' tag". - SEO Suite: Yes. Its settings sit under Stores > Configuration > OCM Labs > SEO. Set Filtered Category Pages to "Use Default (inherit)", which leaves Magento's own robots value in place, and keep Filtered Category Canonical on the unfiltered category. Filtered Category Pages ships set to "noindex, follow", so change it before you rely on the canonical alone. A robots value set on an individual product, category or page still wins.
5.3 Multi-filter and price-range URLs carry the category canonical before any robots.txt block
- Severity: High
- Where: the same canonical as 5.2; an optional, later robots.txt line per the table above
- Verify:
curl -s "https://example.com/mens/boots.html?color=49&size=172" | grep -i -e 'name="robots"' -e 'rel="canonical"'shows the unfiltered category as canonical and no noindex in the robots meta, and the same command on?price=50-100shows the same. For any of these classes robots.txt already blocks, URL Inspection on a sample URL reports it as blocked by robots.txt and not indexed, and the block went in only after Google had consolidated that class or on a store that never had it indexed. - SEO Suite: Yes. The same two settings as 5.2 cover every filter combination, price included. The robots.txt step stays yours.
5.4 No URL class is blocked in robots.txt while it still relies on its canonical or noindex
- Severity: High
- Where: your rule set from 5.1, compared line by line with robots.txt
- Verify: For each
Disallowpattern, pick one URL it matches and confirm the rule set does not expect that URL's noindex or canonical to be read. The "Indexed, though blocked by robots.txt" count in Page indexing is zero or falling.
Does every page have its own title, description and copy?
Run these once sections 2 to 5 pass; the meta title and description guidance by page type covers what to write. A crawl finds the gaps, but checking content page by page while you fix it is slower, and how live content analysis works inside the Magento admin shows that check on the edit form itself.
6.1 Every product, category and CMS page has its own meta title and meta description
- Severity: Medium
- Where: the Search Engine Optimization section of each product, category and CMS page edit form
- Verify: A crawl reports zero indexable pages with a missing title or description.
- SEO Suite: the SEO Dashboard's Missing title and Missing description flags list the gaps; meta templates fill them at render time without writing to the product.
6.2 HTML Head suffix is set, and prefix and default description are left empty, for each store view
- Severity: Medium
- Where: Content > Design > Configuration > (store view) > HTML Head (
design/head/default_title,design/head/title_prefix,design/head/title_suffix,design/head/default_description), pluscatalog/seo/title_separator - Verify:
bin/magento config:show --scope=stores --scope-code=default design/head/title_suffixreturns the brand suffix you expect, and the same command fordesign/head/title_prefixanddesign/head/default_descriptionreturns an empty value. Repeat for every store code. - SEO Suite: Partly. Meta templates cover product, category and CMS pages; HTML Head defaults stay native.
6.3 Titles and descriptions are unique and sized to display
- Severity: Medium
- Where: product, category and CMS page edit forms
- Verify: The crawler's duplicate title and duplicate description reports are empty for indexable URLs, and its title and description length reports show nothing past the display limits you work to.
- SEO Suite: Partly. It scores title and description length; duplicates come from the crawler.
6.4 Product images carry descriptive alt text
- Severity: Medium
- Where: Catalog > Products > (product) > Images And Videos > (image) > Alt Text
- Verify: A crawler with JavaScript rendering switched on reports zero product images missing alt text. Use rendering, because the product gallery is often built by JavaScript and a raw-HTML check undercounts it.
- SEO Suite: Partly. Keyword-in-alt reads gallery labels, but the alt-text check covers description images only.
6.5 Each page template renders exactly one H1
- Severity: Low
- Where: theme templates and Page Builder content
- Verify:
curl -s https://example.com/mens/boots.html | grep -o -i '<h1' | wc -lreturns1on a product, a category, a CMS page and the home page. - SEO Suite: Partly. Its subheading check reads your content, not the theme's H1.
6.6 No two products or categories target the same phrase
- Severity: Medium
- Where: a keyword map owned by whoever writes category copy
- Verify: Search Console > Performance, filter to the target query, open the Pages tab. One URL takes the clicks. Two URLs trading places week to week is the fail.
- SEO Suite: Partly. The Duplicate focus keyword flag catches a reused focus keyword; it is not duplicate-content detection.
6.7 Category pages carry indexable descriptive copy
- Severity: Medium
- Where: Catalog > Categories > (category) > Content > Description
- Verify:
curl -s https://example.com/mens/boots.html | grep -c "a sentence from your category copy"returns 1 or more, which proves the copy is in the HTML before JavaScript runs. - SEO Suite: Partly. It scores content length and internal links; the curl confirms the raw HTML.
Does your Magento product page emit valid structured data?
Audit what the page emits, not what the theme or module promises; the rich snippets walkthrough covers each schema type.
7.1 Product JSON-LD with an Offer validates on every product template
- Severity: Medium
- Where: theme or SEO module output; product attributes for brand, GTIN and MPN
- Verify: The Rich Results Test on a simple product, a configurable product and an out-of-stock product detects Product snippets and Merchant listings with no errors, and price, priceCurrency and availability match what the page shows after catalog price rules. Minimum markup below;
aggregateRatingis left out (7.3). - SEO Suite: Partly. It emits Product JSON-LD with Offer, brand, GTIN and MPN; run the test on your templates.
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Trail Runner Waterproof Shoe",
"sku": "TRW-10-BLK",
"gtin13": "0123456789012",
"image": "https://example.com/media/catalog/product/t/r/trail-runner-black.jpg",
"brand": {
"@type": "Brand",
"name": "Example Outdoor"
},
"offers": {
"@type": "Offer",
"url": "https://example.com/trail-runner.html",
"price": "129.00",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
}7.2 Each product page describes the product once
- Severity: Medium
- Where: theme templates and any module that adds JSON-LD
- Verify: The Rich Results Test lists one Product item, or two whose name, price and availability match exactly.
- SEO Suite: Partly. It leaves the theme's Product microdata in place, so on Luma run this check after installing it.
7.3 aggregateRating appears only on products with reviews
- Severity: Medium
- Where: theme or module review markup
- Verify: The Rich Results Test on a product with zero approved reviews shows no
aggregateRatingorreviewproperty. On a reviewed product,ratingValueandreviewCountmatch the visible review block. - SEO Suite: emits aggregateRating, and up to 5 reviews, only when the product has reviews.
7.4 BreadcrumbList is emitted once, and Organization and WebSite sit on the home page
- Severity: Low
- Where: theme templates and SEO module output
- Verify: The Schema Markup Validator on a product URL lists one BreadcrumbList. On the home page it lists Organization and WebSite once each.
- SEO Suite: Partly. It emits BreadcrumbList, and Organization and WebSite with a SearchAction on the home page; check the theme does not mark up breadcrumbs too.
7.5 Search Console's Merchant listings and Product snippets reports are clean
- Severity: Medium
- Where: Search Console > Shopping > Merchant listings, and Product snippets
- Verify: Zero invalid items in both, and the valid item count is close to your count of indexable products. If it is far below, test a URL of the missing product type.
Do Magento store views keep their own canonical, hreflang and meta?
Skip this unless bin/magento store:list prints more than one store view; the hreflang and store view setup covers locale codes and x-default.
8.1 Store codes in URLs are on or off on purpose
- Severity: Medium
- Where: Stores > Configuration > General > Web > Url Options > Add Store Code to Urls (
web/url/use_store) - Verify:
bin/magento config:show web/url/use_storereturns the value you chose, and the codes frombin/magento store:listmatch the path segments in your live URLs.
8.2 Each store view's pages canonicalize to their own URL
- Severity: High
- Where: base URLs at each store view scope (
web/unsecure/base_url,web/secure/base_url); canonical output from 4.1 - Verify:
curl -s https://example.com/fr/bottes-randonnee.html | grep -i 'rel="canonical"'returns the/fr/URL, not the default store view's URL. - SEO Suite: one canonical per page in each store view.
8.3 Same-language store views do not duplicate each other
- Severity: High
- Where: hreflang (8.4), plus copy that differs where markets do: spelling, currency, shipping and returns
- Verify: URL Inspection on a product in your second English store view shows the Google-selected canonical equal to the user-declared one. Page indexing's "Duplicate, Google chose different canonical than user" lists no URLs from one store view pointing at the other.
- SEO Suite: Partly. Its hreflang marks regional alternates; the copy differences are yours.
8.4 hreflang alternates are reciprocal, use valid codes and include x-default
- Severity: High
- Where: an SEO module, since Magento core emits no hreflang; the store locale (
general/locale/code) - Verify:
curl -s https://example.com/trail-runner.html | grep -i hreflanglists every store view plusx-default, and the same command on each alternate lists the original back.bin/magento config:show --scope=stores --scope-code=fr general/locale/codereturns a value likefr_FR, and the matching tag readsfr-FRorfr(any letter case). - SEO Suite: hreflang across store views, with an
x-defaultstore and a locale override.
8.5 Meta fields are translated for each store view, not inherited from default
- Severity: Medium
- Where: product and category forms at store view scope with Use Default Value unticked; CMS pages, which have no scope switcher, as one page per store view
- Verify: Crawl each store view and compare titles. No page on the French store view carries an English title or description.
- SEO Suite: Partly. The Dashboard flags missing titles and descriptions; the translation is yours.
Does the store pass Core Web Vitals in field data?
Google's "good" thresholds at the 75th percentile are LCP 2.5 seconds or less, INP 200 milliseconds or less (INP replaced FID in March 2024) and CLS 0.1 or less. The Magento settings that move Core Web Vitals cover the fixes.
9.1 LCP, INP and CLS pass in field data
- Severity: Medium
- Where: theme, hosting, images and third-party scripts
- Verify: Search Console > Core Web Vitals shows no Poor URL groups on mobile or desktop. PageSpeed Insights shows all three metrics passing in the field data section, not the lab score, for a product, a category and the home page.
9.2 The store runs in production mode
- Severity: High
- Where: your deployment pipeline
- Verify:
bin/magento deploy:mode:showprintsCurrent application mode: production.Rerun it after any hand fix on the server.
9.3 Full page cache is on, and Varnish serves it in production
- Severity: High
- Where: Stores > Configuration > Advanced > System > Full Page Cache (
system/full_page_cache/caching_application) - Verify:
bin/magento cache:statusshowsfull_page: 1.bin/magento config:show system/full_page_cache/caching_applicationreturns2, which is Varnish;1is the built-in cache. After any change, runbin/magento cache:clean full_page, then runcurl -s -o /dev/null -w '%{time_starttransfer}\n' https://example.com/mens/boots.htmltwice and record both times; the second, served from the cache, is the lower one.
9.4 Category and product templates serve images sized for their slot
- Severity: Medium
- Where: theme image definitions in
etc/view.xml, product image uploads, CDN resizing - Verify: PageSpeed Insights on a category and a product URL names the LCP element in its diagnostics, and that element is not a full-size upload scaled down by the browser.
9.5 Indexers run on schedule and report Ready
- Severity: Medium
- Where: System > Tools > Index Management, set to Update by Schedule; the server crontab
- Verify:
bin/magento indexer:statusshows every indexer as Ready, with none at Reindex required. If one is, and it clears after a manualbin/magento cron:run, the server crontab is not running Magento's cron. If it does not, checkcron_schedulefor failed indexer jobs.
Can Google reach every product through internal links?
Magento already has the internal-linking mechanisms most stores need. The audit is whether they show up in the raw HTML.
10.1 Breadcrumbs render on product and category pages
- Severity: Medium
- Where: theme layout; the category path setting from 3.3 changes what a product page shows
- Verify:
curl -s https://example.com/mens/boots.html | grep -i breadcrumbsfinds the breadcrumb block in the raw HTML. Run the same check on a product opened directly, since breadcrumbs are its only reliable links up the category tree.
10.2 Related products and up-sells are filled on your top products
- Severity: Low
- Where: Catalog > Products > (product) > Related Products and Up-Sells; Cross-Sells render in the cart, which Google does not crawl
- Verify:
curl -s https://example.com/trail-runner.html | grep -c 'product-item-link'(the Luma class; adjust for your theme) returns more than the page's own count, which shows related and up-sell links in the raw HTML.
10.3 Every visible product is reachable from a category
- Severity: High
- Where: the Categories field on the product edit form; Catalog > Categories > (category) > Products in Category
- Verify: Compare the product URLs in your sitemap with the URLs a crawler finds starting from the home page. Any product in the sitemap but not in the crawl is an orphan, often from an import with an empty category column.
10.4 No internal link returns a 4xx or passes through a redirect
- Severity: Medium
- Where: CMS pages and blocks, menus, footer links and widgets; hardcoded links in CMS blocks are the usual source
- Verify: The crawler's inlinks report shows zero internal links to 4xx URLs. Internal links to 3xx URLs are updated to the final URL.
10.5 CMS content links to the categories it mentions
- Severity: Low
- Where: Content > Pages and Content > Blocks; the Catalog Category Link widget, which follows the category if its URL key changes
- Verify:
curl -s https://example.com/trail-running-guide | grep -o 'href="[^"]*"'lists a link to each category the guide names. - SEO Suite: Partly. Its internal links check counts links on category and CMS pages; choosing the targets is up to you.
The Magento SEO checklist in one table
Copy this table into a sheet, add a Status column and a date, and you have the tracking file for the audit.
| ITEM | SEVERITY | WHERE | VERIFY | SEO SUITE | |
|---|---|---|---|---|---|
| 2.1 | robots.txt matches admin, blocks no product or category URL | Blocker | design/search_engine_robots/custom_instructions (website) | curl robots.txt matches config:show at website scope | No |
| 2.2 | Default Robots is INDEX, FOLLOW on production | Blocker | design/search_engine_robots/default_robots (website) | config:show and curl return INDEX,FOLLOW | Partly |
| 2.3 | Staging cannot be indexed | Blocker | Web server auth; staging Default Robots | curl staging returns 401 | No |
| 2.4 | Catalog search results kept out of the index | High | robots.txt Disallow if never indexed; else layout or module noindex | URL Inspection shows blocked and not indexed, or curl shows NOINDEX | Yes |
| 2.5 | Sitemap configured, generated by cron, submitted | High | sitemap/generate/enabled; Site Map grid | config:show returns 1; Sitemaps report Success | No |
| 2.6 | Sitemap holds only canonical, indexable 200 URLs | Medium | Site Map grid; product status and visibility | Crawler list mode: all 200, self-canonical | No |
| 2.7 | Page indexing report read and classified | High | Search Console > Indexing > Pages | Duplicate buckets match the rule set | No |
| 3.1 | Web server rewrites on, no index.php | High | web/seo/use_rewrites | config:show returns 1; crawl finds no index.php | No |
| 3.2 | URL suffixes set once, unchanged on a live store | High | catalog/seo/product_url_suffix, catalog/seo/category_url_suffix | config:show matches the suffix on live URLs | No |
| 3.3 | No category path in product URLs | High | catalog/seo/product_use_categories; catalog/seo/generate_category_product_rewrites | config:show returns 0 for the path and shows whether rewrite generation is on; category-path URL 404s or canonicalizes to the short URL | No |
| 3.4 | URL key change creates a 301 | High | catalog/seo/save_rewrites_history | config:show returns 1; old key returns 301 | No |
| 3.5 | Redirects resolve in one hop | Medium | Marketing > SEO & Search > URL Rewrites | curl -sIL shows one 301 then 200 | No |
| 3.6 | One protocol, one host, one hop | High | web/secure/use_in_frontend; edge redirects | Each variant returns one 301 | No |
| 3.7 | Out-of-stock and disabled products return intended code | Medium | cataloginventory/options/show_out_of_stock | curl -sI matches your policy | No |
| 4.1 | Canonical tags on for products and categories | High | catalog/seo/category_canonical_tag, catalog/seo/product_canonical_tag | config:show returns 1 for both | Yes |
| 4.2 | Exactly one canonical per page | High | Theme and modules writing to head | curl count returns 1 | Partly; a canonical printed by the theme must be removed from the template |
| 4.3 | Configurable children Not Visible Individually | High | Product > Visibility | Grid filter shows no stray children | No |
| 4.4 | Sort, limit and mode URLs carry the category canonical and no noindex; optional block once consolidated | High | Category canonical; robots.txt optional, once consolidated | curl shows the clean category canonical and no noindex; Page indexing lists them as Alternate page with proper canonical tag | Yes |
| 4.5 | Paginated pages canonicalize to themselves | Medium | Theme or module canonical output | curl ?p=2 canonical is itself | Yes |
| 5.1 | Written filter URL rule set exists | High | Shared rule set document | Every layered nav attribute has a class | No |
| 5.2 | Single filters without demand carry the category canonical and no noindex | High | Category canonical or module filtered-category canonical | curl shows the unfiltered canonical and no noindex | Yes |
| 5.3 | Multi-filter and price URLs carry the category canonical before any block | High | Category canonical or module filtered-category canonical; robots.txt optional, later | curl shows the unfiltered canonical and no noindex; blocked classes report not indexed | Yes |
| 5.4 | No class blocked while it relies on its canonical or noindex | High | Rule set versus robots.txt | "Indexed, though blocked" is zero or falling | No |
| 6.1 | Every page has its own meta title and description | Medium | Entity edit > Search Engine Optimization | Crawl shows no blank titles or descriptions | Yes |
| 6.2 | HTML Head suffix set, prefix and default description empty, per store view | Medium | design/head/title_suffix, design/head/title_prefix, design/head/default_description | config:show with store scope: suffix set, prefix and description empty | Partly |
| 6.3 | Titles and descriptions unique and sized | Medium | Entity edit forms | Crawler duplicate and length reports empty | Partly |
| 6.4 | Product images carry alt text | Medium | Product > Images And Videos > Alt Text | Rendering crawl shows no missing alt | Partly |
| 6.5 | One H1 per page template | Low | Theme templates, Page Builder | curl H1 count returns 1 | Partly |
| 6.6 | No two pages target the same phrase | Medium | Keyword map | One URL takes clicks for the query | Partly |
| 6.7 | Category pages carry indexable copy | Medium | Category > Content > Description | curl finds the copy in raw HTML | Partly |
| 7.1 | Product JSON-LD with Offer validates | Medium | Theme or module output | Rich Results Test: no errors | Partly |
| 7.2 | Product described once | Medium | Theme microdata plus JSON-LD | Rich Results Test: one Product, or matching values | Partly |
| 7.3 | aggregateRating only with reviews | Medium | Review markup | No rating on a zero-review product | Yes |
| 7.4 | BreadcrumbList once; Organization and WebSite on home | Low | Theme and module output | Schema Markup Validator counts | Partly |
| 7.5 | Merchant listings and Product snippets reports clean | Medium | Search Console > Shopping | Zero invalid items | No |
| 8.1 | Store codes in URLs set on purpose | Medium | web/url/use_store | config:show matches store:list codes | No |
| 8.2 | Each store view canonicalizes to itself | High | Store view base URLs | curl canonical stays on the store view | Yes |
| 8.3 | Same-language store views do not duplicate | High | hreflang plus copy differences | Google-selected canonical equals declared | Partly |
| 8.4 | hreflang reciprocal, valid codes, x-default | High | Module; general/locale/code | curl lists all alternates both ways | Yes |
| 8.5 | Meta fields translated for each store view | Medium | Edit forms at store view scope | No English titles on other languages | Partly |
| 9.1 | LCP, INP and CLS pass in field data | Medium | Theme, hosting, images, scripts | Core Web Vitals report: no Poor groups | No |
| 9.2 | Production mode | High | Deployment pipeline | deploy:mode:show prints production | No |
| 9.3 | Full page cache on, Varnish in production | High | system/full_page_cache/caching_application | cache:status full_page 1; config returns 2 | No |
| 9.4 | Images sized for their slot | Medium | etc/view.xml, CDN | PageSpeed Insights LCP element not oversized | No |
| 9.5 | Indexers on schedule and Ready | Medium | Index Management; crontab | indexer:status all Ready | No |
| 10.1 | Breadcrumbs render on product and category pages | Medium | Theme layout | curl finds breadcrumbs in raw HTML | No |
| 10.2 | Related products and up-sells filled | Low | Product > Related Products, Up-Sells | curl finds product links | No |
| 10.3 | No orphan products | High | Product > Categories | Sitemap URLs all found by crawl | No |
| 10.4 | No broken or redirected internal links | Medium | CMS blocks, menus, widgets | Crawler: zero links to 4xx | No |
| 10.5 | CMS content links to categories | Low | Content > Pages, Blocks, Widgets | curl lists category links | Partly |
If you worked items 6.1 to 6.7 by hand on twenty pages and have thousands left, SEO Suite scores every product, category and CMS page against 18 checks and lists the failures in one dashboard. It costs $250 for Magento Open Source and $500 for Adobe Commerce.
✦ FAQ
How do I run a Magento SEO audit, and how long does it take?+–
Is Magento good for SEO out of the box?+–
How do I stop layered navigation from creating duplicate pages?+–
Should Magento product URLs include the category path?+–
Does Magento generate an XML sitemap automatically?+–
How often should I re-run a Magento SEO audit?+–

Shane Blandford
Founder of Orange Collar Media, a Denver ecommerce agency behind 800+ Magento and Shopify builds. In Magento since version 1.x - building, rescuing, and supporting revenue-critical stores since 2008.
✦ KEEP READING
Related from the Journal.
Magento 2 Rich Snippets: How to Set Up Product Schema That Validates
Magento 2 rich snippets, done by hand: Google's required Product properties, a JSON-LD block mapped to Magento data, and fixes for duplicate microdata.

What a Magento 2 SEO Extension Should Do, and How to Test One
What a Magento SEO extension should do as you edit: a live 0-100 score, an 18-check list with fix hints, a Google preview, and a buyer's test for demos.

Magento SEO: The Technical Setup Guide for 2.4.7 to 2.4.9
Magento SEO, set up path by path: canonical and robots settings, layered navigation rules, hreflang, JSON-LD and a Search Console index-bloat check.
