Sylius SEO Suite Pro: SEO Audit
The SEO audit checks products and categories for missing, too long and duplicate titles and descriptions, missing images and noindex. Start it from the dashboard button or a cron job, with snapshot, lock and limits.
Sylius SEO Suite Pro: SEO Audit
The SEO audit checks the products and categories of a channel for SEO problems you can fix in the admin, and lists what it found with a link to the product or category concerned. You start it with a button on the dashboard or with a console command. You reach it through the "SEO audit" button at the top of the "SEO Suite Pro" page.
The audit looks at what the storefront really delivers: the title and description of a page after the priority chain (manual text, then template, then fallback, see SEO Templates). A product whose title comes from a template is therefore not reported as missing a title.
What it checks
The audit runs per channel and per language of the channel, for every enabled product and category that is available in the channel. Disabled products and categories, and those of other channels, are not looked at. A product without a translation in a language is not checked in that language.
| Code | Checks | Severity | Threshold |
|---|---|---|---|
title_missing |
No manual meta title and no title template applies, so the page title is just the name | warning | none |
title_too_long |
The delivered title is too long (the detail shows the length) | warning | more than 60 characters (seo_templates.title_max_length) |
title_duplicate |
The same title on more than one product (or category) of the channel in the same language (the detail shows how many share it) | warning | from 2 identical titles |
description_missing |
The delivered meta description is empty | warning | none |
description_too_long |
The delivered meta description is too long (the detail shows the length) | info | more than 155 characters (seo_templates.description_max_length) |
description_duplicate |
The same description on more than one product (or category), as for titles | warning | from 2 identical descriptions |
product_without_image |
A product without any image (reported once per product, not per language) | warning | no image |
noindex |
The product or category is set to noindex; often intended, listed so that a forgotten setting is noticed |
info | none |
The severities that exist are error, warning and info. The eight built-in checks only use warning and info; error is available to your own checks. Duplicates ignore upper/lower case and runs of whitespace. An empty text is never a duplicate, it is reported as missing instead. The length thresholds are not audit settings of their own: they follow the limits of the SEO templates, so the audit and the template truncation use the same yardstick.
Below the channels the dashboard also shows the ten most requested 404 paths that no active redirect covers, read live from the 404 log (see Redirects). Each path has a link that opens the redirect form prefilled. This block is not part of the snapshot and works without the audit tables.
What it does not check
- Alt texts of images: Sylius core has no alt text field on product images, so there is nothing to read.
- Image dimensions (for example "at least 800 pixels"): the shop cannot read images behind a CDN or an external URL, and decoding every local image file during a run would make it slow and unreliable. Only the presence of an image is checked.
- Anything beyond products and categories (CMS pages do not exist in Sylius core), and the quality of the text itself.
Own checks are possible, see below.
How a run works
A run is a snapshot: it writes its findings into mmd_sylius_seo_suite_pro_audit_issue and the run itself into mmd_sylius_seo_suite_pro_audit_run (status running, done or failed, plus the number of findings per check). The dashboard and the list only read these tables, nothing is computed when you open them.
Only a successful (done) run is the current state. The list and the counters show the newest done run of each channel. A run that failed (its findings are partial) never replaces it: the dashboard then says that the last run failed and keeps showing the last good one.
graph TD
A["Start for one channel"] --> B{"Channel lock free?"}
B -->|"no, heartbeat fresh"| C["Refused: audit already running"]
B -->|"yes, or heartbeat older than stale_after_minutes"| D["Take lock, create run as running"]
D --> E["Load and check a page of page_size products"]
E --> F["Write findings of the page, set heartbeat, clear memory"]
F --> G{"More pages and below max_subjects?"}
G -->|"yes"| E
G -->|"max_subjects reached"| H["Truncated: run ends as done with hint max_subjects"]
G -->|"no"| I["Duplicate checks write their findings"]
I --> J["Close run as done, release lock"]
H --> J
J --> K["Delete runs beyond keep_runs"]
E -->|"error"| L["Close run as failed, release lock"]
- Products and categories are read in pages of
audit.page_size(200), by primary key. The entity manager is cleared after every page, so memory does not grow with the catalogue. - There is at most one run per channel. The lock is the unique
lock_keycolumn of the run row, so it holds across processes and servers. The run writes a heartbeat after every page. A run whose heartbeat is older thanaudit.stale_after_minutes(10) counts as dead and is closed as failed by the next start. The lock is also released when closing the run itself fails, and by a shutdown function when the process dies (fatal error, time or memory limit). - The last
audit.keep_runs(3) runs per channel are kept, older ones are deleted with their findings, but only after a run ended successfully. A failing run therefore never costs you the last good snapshot. You need no cron job of its own for that. - A failed run only stores the fixed code
error. The reason is in the shop's log, the text of the exception never goes into the database.
audit.max_subjects(20000) limits the products and, separately, the categories per channel. A larger channel is cut off: the run ends asdonewith the hint "stopped at the subject limit". What it found is real but covers only part of the catalogue. The console command reports this as a failure (exit code 1), so you notice it in a cron job.
Starting an audit
Dashboard
In the admin: "Run audit now" on the card of a channel on the audit dashboard (a POST with a CSRF token). The route sits behind the admin firewall like every other admin screen. The request lasts as long as the run (the PHP time limit is set to 600 seconds, and the session is released before the run so other admin tabs do not wait). The result appears as a flash message: success, a warning when truncated or "already running", an error for missing tables or an unknown channel.
The button runs synchronously and is limited to 600 seconds. The run also clears the entity manager, which detaches everything the request loaded, among them the administrator's user object (it is reloaded with the next request). That is fine for a small catalogue; for a large one use the console command or a cron job.
Console
For every channel or one specific channel:
bin/console mmd:seo-suite-pro:audit:run
bin/console mmd:seo-suite-pro:audit:run --channel=shop
The exit code is 0 when every run finished, 1 when a run was refused (already running), stopped at the subject limit or failed, and 2 for an unknown channel. A nightly cron job at 03:30:
30 3 * * * cd /var/www/shop && bin/console mmd:seo-suite-pro:audit:run --no-interaction
The dashboard and the list
The dashboard shows, per channel, the newest run with counters per check, the button to start a run, and the 404 paths without a redirect. The findings list is a read-only grid with the state of the newest done run of each channel, filterable by product or category code, channel, check, severity and type.
Own checks
Implement Mmd\SyliusSeoSuiteProPlugin\Audit\AuditCheckInterface (or extend AbstractAuditCheck). Any service implementing the interface is tagged mmd_sylius_seo_suite_pro.audit_check automatically; give the tag a priority to set the order. The runner calls start() once per run, inspect() for every product and category in every language, and finish() at the end. A complete example, including the translation keys for the labels, is on the Extending page.
Settings
All keys are optional and live under mmd_sylius_seo_suite_pro.audit:
| Setting | Default | Description |
|---|---|---|
audit.keep_runs |
3 |
Number of runs per channel that are kept |
audit.max_subjects |
20000 |
Products and, separately, categories one run checks per channel at most |
audit.stale_after_minutes |
10 |
A run without a heartbeat for this long counts as dead and no longer blocks |
audit.page_size |
200 |
Products or categories loaded at once |
mmd_sylius_seo_suite_pro:
audit:
keep_runs: 3
max_subjects: 20000
stale_after_minutes: 10
page_size: 200
Cost
A run reads every enabled product and category of the channel once. Without SEO templates, a page of 200 products needs a constant number of queries (the ids, the products with all translations, the image counts, the channel). With templates the placeholder providers add their own queries (attribute values per product, {taxon.main} per product), exactly what rendering the product page costs. The number of queries then grows with the number of products. Writing the findings is one bulk insert per page. None of this runs on a shop request: the storefront and the Shop API never touch the audit tables.
Host steps
After every update of the plugin run mmd:seo-suite-pro:setup (see Installation), which creates the two audit tables. Without them the dashboard shows a hint instead of the figures, the console command reports the missing migration, the findings list answers with an error, and the 404 block keeps working. The storefront and the Shop API are not affected. If something goes wrong, see Troubleshooting.
Shop API / Headless
The audit is a back office feature: an administrator starts it and reads it in the admin, no visitor and no headless frontend consumes it. The logic lives in AuditRunnerInterface, shared by the console command and the dashboard button. There is therefore no API resource for it. What the Shop API delivers is the SEO resource of the pages themselves.