Skip to content
SEO & GROWTH

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.

Shane Blandford
Shane Blandford · FOUNDERPUBLISHED · 38 MIN READ

Magento SEO comes down to six things you control: crawl rules, canonical tags, URL settings, meta fields, structured data and page speed. Most of that work happens in admin configuration and theme templates before anyone writes a word of copy. A catalog left at install defaults can hand Google more filter, sort and search URLs than product pages, with nothing on any of them saying which address is the real one. This guide covers Magento 2.4.7 to 2.4.9, on Magento Open Source and Adobe Commerce, one setting at a time: the admin path, the value to choose, and how to check that it worked.

Orange Collar Media builds SEO Suite, an SEO module for Magento sold under the OCM Labs name and one of the Magento extensions we build. Every section below starts with what Magento does on its own, and says plainly where a module helps and where it does not.

What is Magento SEO?

SEO on Magento is the work of getting a catalog crawled efficiently and indexed correctly, so Google spends its attention on products, categories and content pages instead of on URL variants of them. Copy matters too. But copy on a page Google never picks as canonical does nothing.

Magento ships more SEO controls than its reputation suggests. Its gaps sit exactly where large catalogs get into trouble: faceted URLs, multi-language store views and rich results. The table lists what a clean 2.4.x install gives you. A dash in the default column means the value depends on the install, so read it from your own admin before you change anything.

ADMIN PATHDEFAULTWHAT IS MISSING
PRODUCT AND CATEGORY URL SUFFIXStores > Configuration > Catalog > Catalog > Search Engine Optimization.htmlNothing; the risk is changing it on a live store
CATEGORY PATH IN PRODUCT URLSStores > Configuration > Catalog > Catalog > Search Engine OptimizationNoNothing, as long as it stays off
301 WHEN A URL KEY CHANGESStores > Configuration > Catalog > Catalog > Search Engine OptimizationYesNo bulk redirect import, no 410 response
CATEGORY/PRODUCT URL REWRITESStores > Configuration > Catalog > Catalog > Search Engine OptimizationNoUpgraded stores may hold Yes, and then the rewrite count grows with every category assignment
CANONICAL TAG ON CATEGORIESStores > Configuration > Catalog > Catalog > Search Engine OptimizationNoPaginated pages point to page one on 2.4.7 (self-canonical from 2.4.8)
CANONICAL TAG ON PRODUCTSStores > Configuration > Catalog > Catalog > Search Engine OptimizationNoNothing once it is on
CANONICAL TAG ON CMS AND HOME PAGESNone-No native option
DEFAULT TITLE, PREFIX, SUFFIX, DEFAULT DESCRIPTIONContent > Design > Configuration > (scope) > HTML Head-No render-time templates by page type
DEFAULT ROBOTS METAContent > Design > Configuration > (scope) > Search Engine RobotsINDEX,FOLLOWNo robots value by page, filter or page type
ROBOTS.TXT EDITORContent > Design > Configuration > (scope) > Search Engine Robots-Nothing; it is a free-text field
META AUTO-GENERATION MASKSStores > Configuration > Catalog > Catalog > Product Fields Auto-GenerationBuilt from the product nameValues are written once and never refresh
XML SITEMAPStores > Configuration > Catalog > XML Sitemap-Nothing that blocks you
HREFLANGNone-No output at all
STRUCTURED DATALuma product page templateMicrodataNo JSON-LD, no BreadcrumbList
410 GONE RESPONSENone-Not available in the admin

URL handling and redirect history are solid, and the canonical switches exist but ship turned off. Every entry in the right-hand column gets its fix in the section that covers it: a setting, a theme template, a web server rule or a module.

Magento SEO best practices: the short checklist

Run these in order. For the full audit, with a severity and a verify step for every check, use the full store audit checklist.

  1. Count catalog URLs against Search Console's indexed count before you change any setting (index bloat).
  2. Leave URL suffixes alone on a live store, and pick them before launch on a new one (URLs).
  3. Keep Use Categories Path for Product URLs set to No (URLs).
  4. Give every store view its own crawlable address, through a store code or its own base URL (URLs).
  5. Keep Create Permanent Redirect for URLs if URL Key Changed set to Yes (URLs).
  6. Turn on both canonical switches, for products and for categories (canonical tags).
  7. Decide which filter types earn a real indexable page and which rely on the category canonical alone, and write the decision down as a table (layered navigation).
  8. Block sort, limit, view-mode and price parameters in robots.txt only if they waste crawl, and only once Google has consolidated them (layered navigation).
  9. Check that production serves INDEX,FOLLOW and staging does not (robots).
  10. Generate one XML sitemap per store view and submit each one in Search Console (sitemap).
  11. Write titles and descriptions by hand for the categories and products that earn traffic (meta titles).
  12. Add Product and BreadcrumbList JSON-LD to product pages and validate it (structured data).
  13. Output reciprocal hreflang across every store view that has an equivalent page (hreflang).
  14. Measure Core Web Vitals from field data, one template at a time (Core Web Vitals).
  15. Decide how out-of-stock, discontinued and moved URLs behave before the first product goes away (catalog lifecycle).

How do you measure index bloat before changing anything?

Count before you configure. Every section after this one changes what Google crawls, and without a baseline you cannot tell whether a change fixed the problem or moved it somewhere else. The method needs admin access, Search Console and, ideally, your web server access logs.

Step 1: count what should be indexed

Three numbers come from the admin, one set per store view.

  • Products. In Catalog > Products, filter Status to Enabled and switch the grid to the store view you are counting. Count every visibility except Not Visible Individually, which has no product page of its own.
  • Categories. Count the active categories in Catalog > Categories, or count the category entries in that store view's generated XML sitemap.
  • CMS pages. In Content > Elements > Pages, filter Status to Enabled and Store View to the one you are counting.

If the store view's sitemap is current, grep -o "<loc>" sitemap.xml | wc -l gives you a quick cross-check. Add the three admin numbers together. Multiply across store views only where each store view has its own URLs.

Step 2: read what Google has indexed

Open Search Console > Indexing > Pages for the property. Note the indexed count, then work down the "Why pages aren't indexed" table. The filter at the top switches between all known pages, all submitted pages and unsubmitted pages only, and that last view is the part of the index you never asked for.

The reasons that map most directly to Magento URL patterns:

  • Duplicate without user-selected canonical usually means filter and sort URLs on a store with the category canonical switch off.
  • Alternate page with proper canonical tag means the canonical works, and it is where your filter and sort URLs should collect. Google still spends some crawls on them.
  • Crawled - currently not indexed and Discovered - currently not indexed collect thin filter combinations, and sometimes real products Google does not value yet.
  • Excluded by 'noindex' tag should hold your search URLs and nothing else.
  • Blocked by robots.txt should match your robots.txt rules line for line.
  • Not found (404) and Page with redirect show disabled products and changed URL keys.

Check the indexed side as well, for "Indexed, though blocked by robots.txt". Those URLs are the robots.txt trap from the layered navigation section, already sitting in your index.

Step 3: compare the two and find the patterns

Take a hypothetical store with one store view: 4,000 visible products, 300 active categories and 20 enabled CMS pages. That is 4,320 URLs that deserve a place in the index. If the Page indexing report shows 9,000 indexed pages, at least 4,680 of them are something other than the catalog. If it shows 3,100, the problem runs the other way, with at least 1,220 catalog pages missing, and the not-indexed reasons tell you why.

Either way the next step is the same. Export the example URLs from each reason, sort them, and group them by pattern. On Magento the patterns are predictable:

EXAMPLEWHERE IT COMES FROM
ATTRIBUTE FILTER PARAMETERS/trail-shoes.html?color=49Layered navigation, attribute code plus option ID
SORT, DIRECTION, LIMIT AND VIEW MODE?product_list_order=price&product_list_dir=descToolbar controls on every category page
PRICE RANGES?price=50-100Price filter in layered navigation
INTERNAL SEARCH RESULTS/catalogsearch/result/?q=trailSearch box, plus anyone who links to a search
CATEGORY-PATH PRODUCT URLS/shoes/trail/trail-runner-2.htmlCategory/product rewrites, old links
STORE SWITCHING AND FRONT CONTROLLER VARIANTS?___store=de, /index.php/trail-runner-2.htmlStore switcher, rewrites off at some point

Search Console shows examples, not every URL. Confirm volumes in your access logs, because Googlebot requests per pattern show where crawl activity actually goes:

# Googlebot requests by URL pattern, from a combined-format access log
grep "Googlebot" access.log | grep -c "product_list_order="
grep "Googlebot" access.log | grep -c "/catalogsearch/"
grep "Googlebot" access.log | grep -E -c "[?&]color="

User-agent strings are easy to fake. Verify a sample of those IP addresses with a reverse DNS lookup before you trust the totals.

Search Console's lag and sampling matter less than they seem here, because you are looking for URL patterns and those show up in any sample.

Step 4: fix, then recount

Change one pattern at a time where you can, starting with the biggest. Google has to recrawl a URL before it sees a new noindex or canonical, so the report moves slowly. Use URL Inspection on a few example URLs to confirm Google sees the change, and use Validate Fix on the reason in the Page indexing report to ask for a recheck. Then recount against the same three admin numbers, and keep the counts in a dated sheet, because every section below will move them.

How should you set URL suffixes, category paths and rewrites?

Leave working URLs alone. Most Magento 2 SEO trouble with URLs is self-inflicted: a suffix changed on a live store, a category path switched on, or two store views sharing one address. Every setting in this section changes the URL of every product or category it touches, so read each one before you save.

URL suffix

Stores > Configuration > Catalog > Catalog > Search Engine Optimization > Product URL Suffix (catalog/seo/product_url_suffix) and Category URL Suffix (catalog/seo/category_url_suffix). Both default to .html.

Google does not rank a URL higher or lower for ending in .html. On a new build, choose the suffix before launch, drop it then if you prefer extensionless URLs, and never touch it again.

On a live store, keep what you have, even if clean URLs look more current. Changing it rewrites every product and category URL at once, which makes it a migration with a redirect map and a recrawl. Do not assume the admin redirects the old addresses for you. Test a sample with curl on a staging copy first.

Category path in product URLs

Stores > Configuration > Catalog > Catalog > Search Engine Optimization > Use Categories Path for Product URLs (catalog/seo/product_use_categories), default No. Keep it at No.

With Yes, and with category/product rewrites generated, a product assigned to three categories answers at three paths, and category listings link to the category-path version. The URL Google settles on can then change every time a merchandiser re-categorizes a product. Any benefit from a keyword in the path is small next to the cost of several addresses per product. The short URL is stable.

Category/product rewrites

Stores > Configuration > Catalog > Catalog > Search Engine Optimization > Generate "category/product" URL Rewrites (catalog/seo/generate_category_product_rewrites). On a clean install the default is No. A store upgraded from an older release may still hold Yes, so read the value in your own admin before you plan around it.

With Yes, Magento writes one rewrite per category assignment per store view, and those rewrites are what make category-path product URLs resolve even with the path setting off. A big catalog with a deep tree then builds a large url_rewrite table. With No, category-path product URLs do not resolve at all, and the short URL is the only address a product has.

If your store holds Yes and that table has grown large enough to slow product saves and imports, switching to No is reasonable while category paths stay off and the product canonical is on. Switching it to No deletes the existing category/product rewrites from url_rewrite immediately, and they cannot be restored by switching the setting back. Back up the table first. Then grep your access logs and your backlink data for category-path product URLs, because those URLs stop resolving the moment you save.

Store code in URLs

Add Store Code to Urls lives at Stores > Configuration > General > Web > Url Options (web/url/use_store) and defaults to No.

Each store view needs its own crawlable address. If an English and a German store view share one base URL and shoppers switch with a cookie or a ___store= parameter, Google crawls one language and sees the switcher URLs as duplicates. There are two fixes. Set Add Store Code to Urls to Yes, which gives you /en/ and /de/ paths, or give each store view its own base URL at Stores > Configuration > General > Web > Base URLs at store-view scope.

The store code becomes the path segment, so rename a code like default to en at Stores > Settings > All Stores before you switch it on. On a live store this is another migration: every URL changes.

Redirects when a URL key changes

Stores > Configuration > Catalog > Catalog > Search Engine Optimization > Create Permanent Redirect for URLs if URL Key Changed (catalog/seo/save_rewrites_history), default Yes. Keep it at Yes.

The setting pre-ticks a "Create Permanent Redirect for old URL" checkbox in the Search Engine Optimization section of each product and category edit screen. Whoever edits the key can still untick it. When they do, the old URL returns a 404. Put that box on your content team's checklist.

Web server rewrites

Stores > Configuration > General > Web > Search Engine Optimization > Use Web Server Rewrites (web/seo/use_rewrites). Set it to Yes so storefront URLs drop index.php. The index.php variants usually still resolve, so test one with curl -sI; that is one more reason to turn on the canonical switches in the next section.

SEO Suite does not manage URL rewrites or redirects. Everything in this section stays native Magento, whichever module you run.

How do canonical tags work in Magento?

Magento emits a canonical link on product and category pages only when two admin switches are on, and both ship set to No. While they are off, nothing on the page tells Google which of a product's addresses is the real one, so turn both on.

  • Stores > Configuration > Catalog > Catalog > Search Engine Optimization > Use Canonical Link Meta Tag For Products (catalog/seo/product_canonical_tag), default No. Set it to Yes.
  • Stores > Configuration > Catalog > Catalog > Search Engine Optimization > Use Canonical Link Meta Tag For Categories (catalog/seo/category_canonical_tag), default No. Set it to Yes.

Clean the full page cache after saving with bin/magento cache:clean full_page, or cached pages keep serving the old head.

Product canonical and category paths

With the product switch on, Magento points the canonical at the product URL without any category path. On a store where Generate "category/product" URL Rewrites is Yes, a product reachable at /trail-runner-2.html and at /shoes/trail/trail-runner-2.html declares the first as canonical on both. That is the behavior you want, and it lets old category-path links keep working without competing with the short URL.

Google treats a canonical as a hint, not a command. It weighs internal links, sitemap entries and redirects alongside the tag, so keep those signals pointing at the same short product URL in navigation, in the sitemap and in links inside CMS content.

Category canonical, filters and pagination

The category switch points a category URL at the bare category address and drops filter and sort parameters, so /trail-shoes.html?color=49&product_list_order=price declares /trail-shoes.html. For filter and sort URLs that is right. Pagination depends on the version. On 2.4.8 and 2.4.9 the category canonical keeps p and drops every other parameter, so /trail-shoes.html?p=2 declares itself, which is what Google's guidance for paginated listings asks for. On 2.4.7, page two declares page one as its canonical.

On 2.4.7 that costs you something: products that only appear on page three and beyond can lose their crawl path when Google folds those pages into page one. There is no admin setting for it. The fix is a small plugin in a custom module that keeps p in the category canonical and drops every other parameter, which is what 2.4.8 does on its own. Check which behavior your store has before you write any code:

curl -sL "https://www.example.com/trail-shoes.html?p=2" | grep -o '<link  *rel="canonical"[^>]*>'

If the href ends in ?p=2, paginated pages already declare themselves and there is nothing to build.

The 2.4.8 behavior copies whatever p value the request carries, so /trail-shoes.html?p=1 declares itself too and becomes a second copy of page one. Keep every link to page one, in themes, modules and CMS content, pointing at the bare category URL.

CMS and home pages

Native Magento outputs no canonical tag on CMS pages or on the home page. Check whether your home page content also answers at its CMS identifier, such as /home, and whether tracking parameters create indexable copies of landing pages. A self-referencing canonical on CMS pages needs a layout change in your theme or a module.

One canonical per page

Two canonical tags pointing at different URLs give Google conflicting instructions, and Google may ignore both. On Magento it happens when a theme and a module, or two modules, each add one. Check a product, a category, a filtered category and a CMS page:

curl -s https://www.example.com/trail-runner-2.html | grep -o 'rel="canonical"' | wc -l

Each should print 1. If you run our module, its robots and canonical handling is documented separately, including how it treats filtered and paginated categories.

How do you configure layered navigation for SEO?

A filtered URL earns a place in the index only when it matches real search demand on its own and shows a set of products meaningfully different from the unfiltered category. Brand often passes that test. A single color inside a category sometimes does. Size, price, sort order and combinations of two or more filters rarely do, and they are where index bloat on a Magento store comes from.

Native Magento builds filter URLs from the attribute code and the option ID: /trail-shoes.html?color=49, /trail-shoes.html?color=49&size=172, and /trail-shoes.html?price=50-100 for a price range. At install defaults each of those pages carries INDEX,FOLLOW and no canonical tag. With the category canonical on, they all canonicalize to the bare category, and for most of them that canonical is the whole fix.

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.

For a filter worth indexing, build a real page. A subcategory such as "Trail shoes from Brand X", or a CMS landing page, gets its own URL key, title, description, copy and self-canonical, and a filtered URL natively gets none of those. The new page duplicates the filter result on purpose: the filter URL keeps pointing at its parent category, and the new page is the one address built to rank for that demand.

The robots.txt trap

A URL disallowed in robots.txt is never fetched. Google never sees its noindex and never sees its canonical, and it can still index the bare URL from links, without any content. That is how "Indexed, though blocked by robots.txt" ends up in a Page indexing report.

So robots.txt backs up the canonical; it never replaces it. 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.

A starting robots.txt for filter, sort and search URLs

Adapt this to your own attribute codes and paths. It goes in the custom instructions field covered in the next section.

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=
# Session and account paths
Disallow: /checkout/
Disallow: /customer/
Disallow: /wishlist/
Disallow: /catalog/product_compare/
# Pagination (?p=) and single attribute filters stay crawlable

Sitemap: https://www.example.com/sitemap.xml

Google reads * as a wildcard, so /*?*product_list_order= matches the parameter anywhere in the query string. Test every line before it ships. Search Console's robots.txt report shows the file Google last fetched, and URL Inspection tells you whether a given URL is blocked.

Meta robots and canonical by filter type

The canonical does the consolidating, for filtered URLs already in the index as well as new ones: Google folds them into the category as it recrawls them and reads the tag. Magento emits that canonical on every filtered URL once the category switch is on, and the Default Robots value of INDEX,FOLLOW lets Google keep crawling those pages and following their product links. Google may set a category canonical aside when a filtered page looks different enough from its category. When that happens on a filter with real demand, give it a real page of its own.

ROBOTS METACANONICAL TARGETCRAWL OR BLOCKREASON
BRAND, WITH SEARCH DEMAND OF ITS OWNIndex, follow, as a real subcategory or landing pageThe new page itselfCrawlShoppers search brand plus product type, and a real page carries its own title and copy
SINGLE COLOR OR MATERIAL, WITH DEMANDIndex, follow, as a real page; otherwise no noindexThe new page, or the unfiltered categoryCrawlSame test as brand; build the page only where the demand exists
SIZE, FIT OR WIDTHNo noindexUnfiltered categoryCrawlResults swing with stock, so the page changes week to week
PRICE RANGENo noindexUnfiltered categoryCrawl; optional price= block once Google has consolidated them, or from day one if never indexedRanges are open-ended and rarely match a search query
TWO OR MORE FILTERS COMBINEDNo noindexUnfiltered categoryCrawl; optional block for deep combinations once Google has consolidated them, or from day one if never indexedCombinations multiply far faster than the catalog grows
MULTI-SELECT WITHIN ONE ATTRIBUTENo noindexUnfiltered categoryCrawl; optional block once Google has consolidated them, or from day one if never indexedLuma's layered navigation takes one value per attribute, so these URLs come from a theme or extension
SORT AND DIRECTIONNo noindexUnfiltered categoryCrawl; optional block once Google has consolidated them, or from day one if never indexedSame products in a different order
LIMIT AND VIEW MODENo noindexUnfiltered categoryCrawl; optional block once Google has consolidated them, or from day one if never indexedSame products at a different page size or layout
PAGINATION (P=)Index, followThe page itself (native on 2.4.8+ with the category canonical on; needs code on 2.4.7)CrawlProducts deep in a category need a crawl path

None of those rows needs a robots plugin: the category canonical and the Default Robots value cover every one. Write the table for your own attribute set before anyone touches robots.txt.

On SEO Suite, the settings sit under Stores > Configuration > OCM Labs > SEO. For canonical-only filters, 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.

To verify, curl a filtered or sort URL: the unfiltered category is the rel="canonical", and the robots meta carries no noindex. In Search Console Page indexing, these URLs collect under "Alternate page with proper canonical tag", not "Excluded by 'noindex' tag".

curl -s "https://www.example.com/trail-shoes.html?color=49&product_list_order=price" | grep -i -e 'name="robots"' -e 'rel="canonical"'

How do you set up robots.txt, meta robots and the XML sitemap?

All three live in the admin, and two of them are scoped. Check each website and store view as well as the default config.

Where Magento serves robots.txt from

Content > Design > Configuration, then edit the row for the scope you want, then Search Engine Robots > Edit custom instruction of robots.txt File (design/search_engine_robots/custom_instructions). Magento serves that field at /robots.txt for the scope, which lets each website on its own domain carry its own file.

The reset button beside the field fills it with Magento's stock instructions, and that list includes Disallow: /*?. That one line blocks every URL with a query string, pagination included, and hides every filter URL's canonical from Google. Remove it if you follow the table above.

A physical robots.txt in the web root usually wins over the admin value, because the web server returns the file before the request ever reaches Magento. If your admin edits never appear, look for that file. Then check the live result on each domain with curl -s https://www.example.com/robots.txt.

The default robots meta value

Content > Design > Configuration > (scope) > Search Engine Robots > Default Robots (design/search_engine_robots/default_robots), default INDEX,FOLLOW. That value goes on every page in the scope. Production should read INDEX,FOLLOW. Staging should read NOINDEX,NOFOLLOW and also sit behind HTTP authentication, because the meta tag only works on pages Google fetches and a password keeps the staging copy out of reach entirely.

The incident to guard against is a staging database or a locked config value reaching production with NOINDEX,NOFOLLOW still set. If you lock configuration with bin/magento app:config:dump, check which values landed in app/etc/config.php and app/etc/env.php. After every deploy, view the source of one live product page and read the robots tag.

Internal search results

Search results live under /catalogsearch/result/?q=. Keep them out of the index: they duplicate categories, and anyone can create a new one by typing a query and linking to it. If none are indexed yet, the Disallow line in the robots.txt above is enough. If some are, they need noindex first, and Magento has no separate robots setting for them. A change to your theme's search results layout, or a module, has to set it, and the Disallow line follows once the pages drop.

XML sitemap configuration and generation

The settings live at Stores > Configuration > Catalog > XML Sitemap: frequency and priority for categories, products and CMS pages, image options for products, cron generation settings, file limits, and Search Engine Submission Settings > Enable Submission to Robots.txt. Set that last one to Yes and Magento adds a Sitemap: line to the robots.txt it serves.

Google ignores the priority and change frequency values. Do not spend an hour tuning them. It does use lastmod when the dates are accurate.

Generate the files at Marketing > SEO & Search > Site Map. Add one sitemap per store view, each with its own filename and path, then use Save & Generate. Turn on cron generation in the XML Sitemap settings so the files follow the catalog as products come and go, and confirm Magento's cron actually runs on the server. Submit each file in Search Console's Sitemaps report.

Then spot-check the file. Every URL in it should return 200, be canonical and be indexable. A sitemap full of URLs that redirect or canonicalize elsewhere contradicts your own canonical tags.

SEO Suite does not generate XML sitemaps. Magento's own generator does that job, and nothing in this section changes if you install a module.

AI crawler user-agents

This file is also where you decide whether AI crawlers may use your content. Google-Extended is the token Google reads to decide whether your content may be used for its Gemini models, and disallowing it does not remove pages from Google Search. Other AI crawlers publish their own user-agent tokens. Add a User-agent group for each one you want to block, in the same custom instructions field, and recheck the list whenever you review the file.

How should you write meta titles and descriptions in Magento?

Write them by hand for the pages that earn traffic, and let defaults cover the rest. Magento gives you three places to set them and one mechanism that looks like automation but is not.

Fields on the edit screens

Products, categories and CMS pages each have a Search Engine Optimization section on their edit screen, with URL Key, Meta Title, Meta Keywords and Meta Description. Switch the scope selector on a product or category to a store view to write translated values; left at default scope, every store view inherits the default text. A CMS page has no scope selector: a translated version is a separate page assigned to its own store view, and it can reuse the same URL key or take a translated one. When Meta Title is empty on a product, Magento uses the product name as the title.

Google ignores the meta keywords tag. Leave that field empty.

HTML Head defaults, prefix and suffix

Content > Design > Configuration > (scope) > HTML Head holds Default Page Title, Page Title Prefix, Page Title Suffix and Default Meta Description. The prefix and suffix wrap every title in the scope, product pages included. Use the suffix for the store name if you want it at all. Leave the prefix empty, because a prefix pushes the product name to the right, where truncation cuts it.

Leave Default Meta Description empty as well. One sentence stamped on every page that lacks its own description makes those pages look identical in results, while an empty field lets Google build a snippet from the page content. A generic line can beat a random snippet on a page nobody wrote for. The better fix for that page is a description written for it.

Product Fields Auto-Generation masks

Stores > Configuration > Catalog > Catalog > Product Fields Auto-Generation holds masks for Meta Title, Meta Keywords and Meta Description, built from product attribute codes. The stock masks use the product name, and the Meta Description mask adds the product description. The masks fill those fields when someone creates a product in the admin. The result is stored on the product as ordinary text, so it never updates: rename the product or change an attribute the mask used, and the old title stays.

Render-time templates work the other way. They compute the title each time the page renders, from current attribute values, and write nothing back to the product. Native Magento has no render-time templates; some modules, ours among them, add them for products, categories and CMS pages.

Meta title and description guidance by page type

Google cuts titles by pixel width rather than by a character count, and it may rewrite a title or description it judges a poor match for the query. Put the words that matter in the first half of the title, where they survive truncation.

  • Product pages. Lead with the product name, then add the attribute shoppers search with, such as model number, material or compatibility, and let the suffix carry the store name if there is room. The description says what the page offers that the next result might not: stock, delivery, or the spec that decides the purchase.
  • Category pages. Use the category as shoppers phrase it plus one qualifier, such as "Women's trail running shoes", and keep filter values out of the title. The description names the range and what sets it apart.
  • CMS pages. Name the page's job in plain words, such as "Returns and exchanges", and write a description that answers the question the page exists for.

The demo below shows a different way to do the same job: writing a product title and description against a desktop and mobile results preview on the edit screen, with length feedback as you type. Watch the preview more than the score. In SEO Suite the score is guidance for the person writing the page, not a prediction of where it will rank. If you are comparing modules, here is what to look for in an SEO extension for Magento, live content analysis included.

Writing a product title and description against a live search results preview

Structured data: what Magento outputs and what valid Product JSON-LD looks like

Native Magento outputs no JSON-LD. Luma's product page template carries schema.org microdata on elements such as the product name, SKU and price, and there is no BreadcrumbList, Organization or WebSite markup. Other themes differ, so view the source of your own product page before you plan anything.

Google reads both formats and recommends JSON-LD. Add it from one template block: it does not depend on theme HTML staying put, and one file is easier to keep correct than attributes scattered across templates.

This is what one product page should emit, for a hypothetical shoe at https://www.example.com/trail-runner-2.html: a Product and a BreadcrumbList in one @graph.

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Product",
      "@id": "https://www.example.com/trail-runner-2.html#product",
      "name": "Trail Runner 2",
      "sku": "TR2-BLK-42",
      "description": "Lightweight trail running shoe with a rock plate.",
      "image": "https://www.example.com/media/catalog/product/t/r/trail-runner-2.jpg",
      "url": "https://www.example.com/trail-runner-2.html",
      "brand": {
        "@type": "Brand",
        "name": "Example Outdoor Co."
      },
      "offers": {
        "@type": "Offer",
        "url": "https://www.example.com/trail-runner-2.html",
        "price": "129.00",
        "priceCurrency": "USD",
        "availability": "https://schema.org/InStock",
        "itemCondition": "https://schema.org/NewCondition"
      }
    },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://www.example.com/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Trail Shoes",
          "item": "https://www.example.com/trail-shoes.html"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "Trail Runner 2"
        }
      ]
    }
  ]
}

Every value must match the visible page. The price is the price the shopper sees, as a number with no currency symbol, and availability switches to https://schema.org/OutOfStock when the stock status does. The url is the canonical URL, never a category-path variant. The breadcrumb's last item can leave out item, because it is the page itself.

Emit one Product node per product page, and emit none when the product has nothing valid to describe. Google needs a name plus at least one of offers, review or aggregateRating, so an empty Product node earns an error rather than partial credit. If Luma's microdata stays in place, the Rich Results Test will report two Product items. Make sure they agree on price and availability.

To validate, paste the URL or the code into Google's Rich Results Test, then into the Schema Markup Validator for anything that is valid schema.org but outside Google's rich result types. After Google recrawls, the Product snippets and Merchant listings reports in Search Console show errors and warnings across the whole catalog. Valid markup makes a page eligible for rich results; Google still decides whether to show them. The full setup, from Product JSON-LD to merchant listing eligibility, is in our guide to Magento rich snippets.

How do you set up hreflang across Magento store views?

Native Magento outputs no hreflang tags. You add them with a template in your theme or with a module. Writing the tags is the easy part; the work is mapping the same product, category or CMS page across store views whose URL keys differ.

Where each store view's locale lives

Stores > Configuration > General > Locale Options > Locale (general/locale/code), set at store-view scope. Magento stores the locale in the form en_US. Hreflang wants an ISO 639-1 language code, optionally followed by a hyphen and an ISO 3166-1 alpha-2 region code, so en_US becomes en-us. Lowercase is the common convention.

LOCALEHREFLANGBASE URL
ENGLISH, US (EN)en_USen-ushttps://www.example.com/en/
GERMAN (DE)de_DEde-dehttps://www.example.com/de/
FALLBACK FOR EVERYONE ELSE-x-defaulthttps://www.example.com/en/

The x-default decision

The x-default value names the page for visitors whose language matches none of your versions. Point it at a language selector if you have one. Otherwise point it at the store view you would show an unknown visitor, which is usually your largest market. Make that choice once, for the whole site.

What the output looks like

Here are the rendered tags for one product across two store views. The German store view has its own URL key, which is why the mapping has to run on the product ID and never on the URL string:

<link rel="alternate" hreflang="en-us" href="https://www.example.com/en/trail-runner-2.html">
<link rel="alternate" hreflang="de-de" href="https://www.example.com/de/traillaufschuh-2.html">
<link rel="alternate" hreflang="x-default" href="https://www.example.com/en/trail-runner-2.html">

Both pages carry the same three lines. Every version lists every version, itself included, and every link must be returned: if the German page lists the English one and the English page does not list it back, Google can ignore the pair. Each href is the canonical, indexable URL of that version.

Leave a store view out of the set when the product is disabled there or not assigned to that website. An alternate that 404s or redirects breaks the set for that page. If one English store view serves every English-speaking country, use plain en rather than inventing region codes it does not serve.

Search Console retired its International Targeting report, so check hreflang yourself: fetch both pages with curl and compare the lines. The SEO Suite hreflang documentation covers how the module formats codes and which pages it leaves without alternates.

Which Magento settings move Core Web Vitals?

Google rates a page experience as good when, at the 75th percentile of page loads, Largest Contentful Paint (LCP) is 2.5 seconds or less, Interaction to Next Paint (INP) is 200 milliseconds or less, and Cumulative Layout Shift (CLS) is 0.1 or less. INP replaced First Input Delay as the responsiveness metric in March 2024. On a JavaScript-heavy theme that change raised the bar, because INP counts the latency of every interaction on the page rather than only the delay before the first one.

LCP: server response and the main image

The LCP element on a product or category page is usually the main product image or a hero banner, and it cannot paint before the HTML arrives. Set Stores > Configuration > Advanced > System > Full Page Cache > Caching Application (system/full_page_cache/caching_application) to Varnish for production; the built-in cache is fine for development.

Then fix the image itself. Serve the main image at the size it displays, in a modern format, and never lazy-load it. Pre-generate resized catalog images with bin/magento catalog:images:resize after imports, so a shopper's request never waits on resizing.

INP: JavaScript on the main thread

INP measures how quickly the page responds to a tap, click or key press, and on Magento the main cost is JavaScript. Luma's front end runs on RequireJS, jQuery and Knockout, and every extension that adds a storefront widget adds to that load. Hyva replaces the stack with Alpine.js and Tailwind CSS, which is why a Hyva storefront ships less JavaScript to the browser.

The admin settings sit at Stores > Configuration > Advanced > Developer. JavaScript Settings holds Merge JavaScript Files (dev/js/merge_files), Enable JavaScript Bundling (dev/js/enable_js_bundling) and Minify JavaScript Files (dev/js/minify_files). CSS Settings holds Merge CSS Files (dev/css/merge_css_files) and Minify CSS Files (dev/css/minify_files). The Developer section only appears in developer mode, so on production set the values from the command line and redeploy static content:

bin/magento config:set dev/js/minify_files 1
bin/magento config:set dev/css/minify_files 1
bin/magento setup:static-content:deploy
bin/magento cache:flush

Minify, yes. Native bundling groups modules without regard to which page needs them, so a product page can download code it never runs. Measure INP and LCP on real templates before and after, and turn bundling off again if the numbers get worse.

A move to Hyva is a re-theme, and every extension's storefront output has to be checked against it, so it belongs in a project plan with its own budget. On Luma, the levers within reach are minification, image sizing and removing third-party tags nobody reads the data from.

CLS: reserving space

Layout shift on Magento storefronts tends to come from images without dimensions, sliders and promo bars that render after load, cookie notices that push content down, and swatches that a JavaScript widget draws after the first paint. Give every image width and height attributes. Reserve the slot for anything injected late. Load web fonts with font-display set so text does not jump when the font arrives.

Measure with field data

Judge every fix by field data from real visitors. Search Console's Core Web Vitals report, under Experience, groups URLs by status from real Chrome users, split into mobile and desktop. PageSpeed Insights shows the same Chrome UX Report field data for a URL or the whole origin, above its Lighthouse lab result. Field data covers a rolling 28-day window, so a fix shows up gradually rather than the next morning.

Lighthouse runs under controlled conditions, which makes it the right tool for finding causes, though its scores still move between runs. It is not what Search Console reports. Measure by template, covering home, category, product and search results, because a fix on one template says nothing about the others.

What should happen to out-of-stock, discontinued and changed URLs?

Keep a URL alive while it still serves the visitor, redirect it when a true replacement exists, and let it go with a 404 or 410 when nothing does. Magento handles part of this natively. A 410 is not a native Magento admin option: it needs a web server rule or custom code.

RESPONSEHOW TO DO IT IN MAGENTOTHE TRAP
TEMPORARILY OUT OF STOCKKeep live, 200Leave the product enabled and set its stock status to Out of Stock. Display Out of Stock Products at Stores > Configuration > Catalog > Inventory > Stock Options (cataloginventory/options/show_out_of_stock, default No) controls whether it still shows in listings and searchDisabling the product to hide it turns its URL into a 404; the JSON-LD availability must change to OutOfStock
DISCONTINUED, REPLACEMENT EXISTS301 to the replacementDelete the old product, or change its URL key with the "Create Permanent Redirect for old URL" box unticked, then add the custom rewrite; or edit the generated redirect's Target Path in Marketing > SEO & Search > URL Rewrites to the replacementIf Magento reports that the request path already exists, the old product's own rewrite still holds it
DISCONTINUED, NO REPLACEMENT410, or 404Disabling or deleting the product returns a 404. For a 410, add a web server rule, such as an nginx location block that returns 410 for that pathRedirecting every dead product to the home page or a category
URL KEY CHANGED301 old to newAutomatic while Create Permanent Redirect for URLs if URL Key Changed is Yes and the checkbox on the edit screen stays tickedLinks inside CMS blocks still point at the old URL
CATEGORY RESTRUCTURE301 old category and product pathsMoving a category or changing its URL key regenerates its rewrites, and with the redirect setting on, the old paths become 301sNever assume; crawl the old URL list after the change
PLATFORM MIGRATION301 every old URL with traffic or links to its closest new equivalentBuild and load a redirect map, as belowChains, loops and blanket redirects to the home page

Redirecting a discontinued product to its category is right only when the category genuinely serves the same intent. Done across the board, it invites Google to treat those redirects as soft 404s, and the shopper who clicked a specific product lands on a list with no explanation.

The migration redirect map

  1. Export every old URL that matters: the old sitemap, the Pages tab of Search Console's Performance report, your analytics landing pages, server logs and your backlink data.
  2. Map each one to its single closest new URL, by SKU for products and by hand for categories and content pages, in a two-column sheet.
  3. Load the map. The URL Rewrites grid adds custom rewrites one at a time, so at migration scale load them with a script or as a web server redirect map.
  4. Crawl the old list on launch day. Every old URL should return one 301 to a URL that returns 200, with no chains.
  5. Submit the new sitemaps in Search Console, and keep the redirects in place.

A loop like this checks step 4 from a plain text list of old URLs:

while read -r url; do
  first=$(curl -s -o /dev/null -w "%{http_code}" "$url"); final=$(curl -sL -o /dev/null --max-redirs 10 -w "%{http_code} %{num_redirects} %{url_effective}" "$url"); printf '%s %s %s\n' "$first" "$final" "$url"
done < old-urls.txt

A passing line starts 301 200 1.

If you would rather not write and maintain the robots meta plugin for search results yourself, SEO Suite for Magento handles it from its own section at Stores > Configuration > OCM Labs > SEO, along with the filtered category canonical, hreflang and JSON-LD. It costs $250 for Magento Open Source and $500 for Adobe Commerce, and runs on 2.4.7 to 2.4.9.

FAQ

How do you do SEO on Magento?+
Measure first, then configure, then write. Count the URLs your catalog should have indexed and compare that with Search Console's Page indexing report. Turn on the product and category canonical tags, let filter and sort URLs rely on the category canonical, keep search URLs out of the index, and give each store view its own address. Then write titles and descriptions for the pages that earn traffic, add Product JSON-LD, fix the slowest template and recount after Google recrawls.
What is the best SEO setup for Magento?+
One indexable URL per product, category and CMS page in each store view, and nothing else in the index. In practice that means both canonical switches on, category paths off in product URLs, and filter, sort and price URLs left crawlable with the category canonical and no noindex. Add Varnish for the full page cache, Product JSON-LD on product pages and reciprocal hreflang across store views.
Is Magento good for SEO out of the box?+
Partly. The basics work once you configure them, but the product and category canonical switches ship turned off. Magento outputs no hreflang and no JSON-LD, offers no 410 response, and has no robots control for filtered or search pages. A fresh install left at defaults lets Google index filter and sort URLs, so most of the work is changing settings and filling those gaps.
What SEO features does Magento offer natively?+
Magento gives you URL suffixes and URL keys, automatic 301s when a URL key changes, custom URL rewrites, product and category canonical tags, a default robots meta value by scope and an editable robots.txt. It adds meta title, keyword and description fields on products, categories and CMS pages, a title prefix and suffix, auto-generation masks and an XML sitemap generator. Luma's product template carries schema.org microdata. Most of these settings sit under Stores > Configuration > Catalog and Content > Design > Configuration.
Which ecommerce platform is best for SEO?+
The one that gives you control over what search engines read, and that your team will actually configure. Judge platforms on five things: control of URLs and redirects, canonical and robots rules by page type, freedom to output the structured data you need, how faceted navigation is handled, and how fast the default front end is on real phones. Magento gives you full URL control; the rest is reachable through configuration, theme code or a module, and front-end speed depends on the theme.
Do you need an SEO extension for Magento?+
Not for the basics: native Magento covers URLs, redirects, canonicals, meta fields and sitemaps once you configure them. A module earns its place where Magento has no setting at all, such as robots values by page type, hreflang, JSON-LD, render-time meta templates and, on 2.4.7, a self-canonical for each paginated category page. Either way, most of the work is configuration and writing, and no extension does that part for you.
Shane Blandford

Shane Blandford

FOUNDER

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.

All posts by Shane Blandford→

KEEP READING

Related from the Journal.

Your store can sell more. Let's find out how much.

Start a project