Technical SEO Audit WorkshopWriting tickets developers act on · Lesson 9 of 14

Anatomy of a great SEO ticket

Article · 13 min · 8 min lecture

Video lecture

Anatomy of a great SEO ticket

13 chapters · about 8 min · full transcript

Coming soon

Chapter 1 of 13

Anatomy of a great SEO ticket

  • Why vague tickets stall
  • A template and worked tickets
  • Binary acceptance criteria

The narrated lecture is in production

Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.

Chapters

Why most SEO recommendations are never implemented

Developers receive vague requests like "fix canonicals" or "improve site speed". They cannot estimate, test or close such tickets, so the work stalls. A good ticket reads like a small specification: what is wrong, where, why it matters, exactly what should change, and how to prove it is done.

The ticket template

Title:          [SEO] <Component>: <specific change>
Type / Priority: Bug | Improvement — P1/P2/P3 (from roadmap score)
Summary:        One or two sentences a non-SEO can understand.
Business impact: Why it matters, linked to revenue templates/markets.
Current behaviour: What happens now, with 2–5 example URLs.
Expected behaviour: Precise rule, including edge cases.
Acceptance criteria: Testable statements (Given/When/Then or checklist).
How to test:    Commands, tools, URLs to verify.
Out of scope:   What not to change.
Dependencies / risks: Related tickets, rollback notes.
References:     Google documentation links, evidence IDs.

Example ticket 1: cross-market canonicals (RC1)

Title: [SEO] Product & blog templates: canonical must self-reference the current market URL

Summary: Product and journal pages in /en-pk/, /en-ae/ and /ar-ae/ currently set their
canonical to the /en-gb/ equivalent. Google therefore treats non-UK pages as duplicates
and shows UK pages (with GBP prices) to users in Pakistan and the UAE.

Business impact: Affects all product and journal URLs in three markets that account for
most revenue (per GA4). Evidence: E-08, F-03, F-11.

Current behaviour:
  /en-ae/products/brass-lantern-large → canonical: /en-gb/products/brass-lantern-large

Expected behaviour:
  - The canonical equals the page's own absolute URL: protocol https, host www, current
    market folder, no query string, lowercase, trailing slash rule as per site standard.
  - Parameterised variants (?utm_*, ?variant=) canonicalise to the clean URL of the same market.
  - hreflang tags continue to list all four markets plus x-default (no change).

Acceptance criteria:
  [ ] For every product and journal URL in each market, rendered HTML contains exactly one
      canonical link element in the head, equal to its own clean URL.
  [ ] Canonical is present in the server-rendered (raw) HTML, not only after hydration.
  [ ] No canonical points to a URL that redirects or returns non-200.
  [ ] Automated test added to the template test suite.

How to test:
  curl -s https://staging.kiranhome.example/en-ae/products/brass-lantern-large | grep -i canonical
  Crawl staging product list (list provided) and check canonical = address.

Out of scope: Changing URL structure, hreflang values.
Risk: Low. Rollback = revert template commit.

Example ticket 2: facets (RC2)

Title: [SEO] Category filters: stop exposing crawlable parameter links; block low-value parameters

Expected behaviour:
  1. Filter options for size, price, sort and multi-select combinations update the listing
     without outputting crawlable anchor links to parameter URLs (use buttons with JS
     state or URL fragments). Brand and colour filters for the approved list (attached)
     continue to link to clean static URLs, e.g. /en-gb/collections/cushions/indigo/.
  2. Parameter order is normalised: brand, colour, size, price.
  3. Zero-result filter combinations return HTTP 404.
  4. robots.txt adds (after ticket SEO-12 confirms no approved facets use these parameters):
       Disallow: /*?*sort=
       Disallow: /*?*price=
       Disallow: /*?*size=
       Disallow: /*/search
Acceptance criteria:
  [ ] Rendered category HTML contains no anchor href with sort=, price= or size= parameters.
  [ ] Approved facet pages return 200, self-canonical, unique title/H1.
  [ ] /en-gb/collections/cushions/?size=xl&price=0-10 with no results returns 404.
  [ ] robots.txt changes deployed only after staging tests pass.

Writing good acceptance criteria

Acceptance criteria must be binary (pass/fail) and checkable by a QA engineer without SEO knowledge:

  • Good: "Returns HTTP 404 for zero-result combinations."
  • Bad: "Handle empty filters properly."
  • Good: "The LCP image on category templates has no loading=lazy attribute and has fetchpriority=high."
  • Bad: "Improve LCP."

Give developers the why — briefly

Link to Google's documentation for context (canonicalisation, faceted navigation, redirects) and keep the explanation short. Developers respect specificity; long SEO essays inside tickets are skipped.

Sizing and splitting

If a ticket would take more than a sprint, split it by template or by market. Tickets that can ship independently get value live sooner and are easier to roll back.

Example ticket 3: CDN bot rule (RC8), written as Gherkin

Some teams prefer acceptance criteria as Given/When/Then scenarios that QA can automate:

Feature: Search and AI search crawlers can reach public pages
  Scenario Outline: Verified crawler requests a public product page
    Given a request from a verified <bot> IP address
    And the User-Agent header contains "<token>"
    When it requests "/en-ae/products/brass-lantern-large/"
    Then the response status is 200
    And no challenge or CAPTCHA page is served

    Examples:
      | bot            | token          |
      | Googlebot      | Googlebot      |
      | Bingbot        | bingbot        |
      | OAI-SearchBot  | OAI-SearchBot  |
      | PerplexityBot  | PerplexityBot  |

  Scenario: Protected paths stay protected
    Given any automated client
    When it requests "/account/" or "/checkout/"
    Then bot protection rules still apply

Pair it with the written AI crawler policy (which bots are allowed on which paths) so the rule is a business decision, not an SEO whim.

Worked example 2: rewriting a vague request

A Lahore agency (illustrative) receives back a ticket its junior wrote: "Fix hreflang issues on the site." The developer asked five questions and parked it. Rewritten: title "[SEO] Product template: hreflang set must be complete and reciprocal"; three example URLs with current output; expected behaviour listing the four market codes plus x-default, absolute URLs, self-reference, and targets that return 200 and are self-canonical; acceptance criteria as a checklist plus a crawl-based test; out of scope: journal templates (separate ticket). It was estimated and shipped in the next sprint.

Common mistakes

  • Tickets without example URLs.
  • Expected behaviour that ignores edge cases (parameters, trailing slashes, uppercase, pagination).
  • Mixing several unrelated fixes into one ticket.
  • Not specifying whether a tag must be in the raw HTML or can be added client-side.

Key takeaways

  • A great ticket states current vs expected behaviour with example URLs and precise rules.
  • Acceptance criteria must be binary and testable by someone without SEO knowledge.
  • Specify edge cases and whether output must exist in the raw server-rendered HTML.
  • Split large work by template or market so value ships sooner and rollback is easier.

Check your understanding

Quick questions to lock in the lesson. They don’t count towards your certificate.

  1. Which acceptance criterion is testable?
  2. Why specify that the canonical must be in the raw server-rendered HTML?

Put it into practice

Rewrite a vague SEO recommendation you have seen (e.g. 'fix redirects') into a complete ticket using the template.

Enrol for free to save your progress

Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.