Same H1 used on several pages
What is this issue?
This issue reports a page whose main heading (H1) is the same as another page's.
The H1 is the headline of a page: it tells a reader, and a search engine, what this particular page is about. When several pages share one H1, that headline no longer tells them apart.
For a page to pass this check:
- No other indexable page in the scan uses the same H1.
Example: every product page in a shop shows <h1>Product details</h1> above the
product name, or every location page reads <h1>Find a store</h1>.
Headings are compared ignoring case and extra spaces: "Our Products", "OUR PRODUCTS" and " our products " are the same heading. Only each page's first non-empty H1 is compared.
Why it matters
- It blurs which page answers which query. The H1 is one of the clearest statements of a page's topic. Pages that share one look like competing versions of the same answer.
- It usually means the template, not the content, wrote the heading. A generic H1 such as "Product details" or "Blog" puts the page's real subject somewhere less prominent.
- Readers lose their bearings. Someone arriving from search, or switching tabs, reads the H1 to confirm they are on the right page.
- AI search and answer engines use headings to segment and label content. A heading repeated across pages is a weak label for any one of them.
This is a STANDARD issue — one grade below duplicate titles. The H1 is a weaker relevance signal than the title, but a repeated H1 is still worth fixing.
How to fix it
Rewrite the H1 to say what is specific to this page — the product name, the place, the topic of the article — rather than the section or the site.
<!-- Before --> <h1>Product details</h1> <!-- After --> <h1>Stainless 1.7L kettle</h1>Fix it in the template. When the duplicate is a template label, change the template so the H1 is drawn from the page's own data. Keep the label if you want, as a smaller heading or a breadcrumb.
If the pages really are the same, consolidate them: merge and redirect, or point the copies at the main page with
rel="canonical".Do not hide the H1 to make the finding go away. A page with no H1 is a worse problem than a page with a generic one.
Examples
Example 1: A template label as the H1 (reported)
<!-- https://example.com/products/kettle -->
<h1>Product details</h1>
<!-- https://example.com/products/toaster -->
<h1>Product details</h1>Both pages are reported, each naming the other.
Corrected:
<h1>Stainless 1.7L kettle</h1>
<h1>Four-slice toaster</h1>Example 2: Same words, different case (reported)
<h1>Contact Us</h1> on /contact and <h1>CONTACT US</h1> on /support/contact.
Case and spacing are ignored, so these are the same heading.
Example 3: A paginated series (not reported)
/blog and /blog/page/2 both show <h1>Blog</h1>, and /blog/page/2 declares
<link rel="prev" href="/blog">. Continuation pages are not compared.
Example 4: A canonicalised variant (not reported)
/shoes?colour=red and /shoes both show <h1>Running shoes</h1>, and the variant
declares <link rel="canonical" href="https://example.com/shoes">. The variant has
already declared itself a copy.
How PixyScan detects this
This is a whole-site comparison, so it runs after the crawl, once every page has been analysed.
Takes each page's first non-empty H1 from the headings recorded during the crawl. A page with several H1s is compared on the first; having more than one is a separate finding.
Considers only pages asking to be indexed in their own right:
- the page answered with a 2xx status;
- it does not carry
noindex(in a robots meta tag or anX-Robots-Tagheader); - it is not a later page of a paginated series (it declares no
rel="prev"). Page 2 of a blog listing repeating page 1's "Blog" heading is how pagination is built, not a choice to fix per page; - it does not declare a canonical pointing at another address. Such a page has already said "I am a copy", so it is left out entirely — not reported, and not counted towards anybody else's group. The same rule the duplicate body content check uses.
Normalises the heading: surrounding spaces trimmed, runs of whitespace collapsed to one, and case ignored.
Groups pages by that heading and reports every page in a group of two or more. Each finding names up to 20 of the other pages and gives the size of the whole group, which is never trimmed.
An empty H1 never forms a group — that is the separate empty-H1 finding.
What we store
Storage Level
Page Level — every page in a shared group gets its own finding.
Database Table / Prisma Model
Read from PageSeoBasicsData and Url. The finding is stored on audit_issues.details.
Fields Used
| Field | Type | Description |
|---|---|---|
| page_seo_basics_data.heading_tree | Json? | Every h1–h6 on the page with its text (trimmed to 200 characters). The first non-empty h1 is compared |
| page_seo_basics_data.indexability_noindex_absent | Boolean? | A page carrying noindex (false) is left out; an unmeasured value is not |
| page_seo_basics_data.pagination_prev | String? | A page declaring rel="prev" is a continuation page and is left out |
| page_seo_basics_data.canonical_url | String? | A page canonicalising to another address is left out |
| urls.url | String | The page's address |
| urls.status_code | Int? | Only pages answering 2xx are compared |
Stored Fields on the finding
| Field | Type | Description |
|---|---|---|
| message / recommendation | String | The shared heading, the other pages, and what to change |
| url | String | The page being reported |
| duplicateH1 | String | This page's H1, trimmed and whitespace-collapsed |
| occurrences / duplicateCount | Int | The whole group, this page included — never trimmed |
| duplicateUrlIds / duplicateUrls | Array | Up to listingCap of the other pages |
| listingCap | Int | How many other pages a finding lists (20) |
| comparison | String | first-h1-case-insensitive |
| scope | String | pages-in-this-scan |
Detection Dependencies
- HTML Document — the headings, robots meta tag,
rel="prev"and canonical - HTTP Response — the status code and the
X-Robots-Tagheader