BlogFindings

Google deep links into a section, and 55 of 245 headings had an id

Google's snippet documentation, carrying Last updated 2026-04-20 UTC, describes a read more deep link that lands a searcher on a specific section of a page, and names removing the hash fragment from the URL as something that breaks it. Counted with an HTML parser over five real pages this scanner captured on 15 July 2026, 55 of 245 headings carried an id of their own, and 26 headings could not be reached by any element identifier at all.

19 min read Lantad

So we counted the addresses. Five real pages sit in this repository as stored captures, fetched on 15 July 2026 and unchanged since, and earlier work has already counted what their heading structure looks like and how much of it our own extractor throws away. This count is narrower: how many of those headings carry an id attribute, how far the nearest identified ancestor sits from the ones that do not, and how many sections of these pages an AI crawler or a search result could not link to even if it wanted to. The answer is 55 of 245, and the uncomfortable half is again ours: nothing in this scanner reads an id, so nothing we print would have told anyone.

In short

  • Google's snippet documentation, carrying Last updated 2026-04-20 UTC, defines a read more deep link as a link within a snippet that leads users to a specific section on that page, and lists three best practices for it, none of which is adding an identifier to the section.
  • Counted with an HTML parser on 21 August 2026 over the stored raw HTML of five real pages captured on 15 July 2026, 55 of 245 heading elements carried a non-empty id attribute, and the split by page ran from 27 of 28 on the Wikipedia article to 0 of 39 on webflow.com.
  • Of the 190 headings with no id of their own, 7 sat within three elements of an ancestor that had one, 157 had their nearest identified ancestor 4 to 15 levels of markup away, and 26 had none anywhere above them.
  • The same five captures held 2,174 id attributes, of which 23 were empty strings, all on one page, and 22 non-empty values appeared more than once for 59 duplicate copies, against the rule on MDN that an id must be unique within the entire document.
  • Lantad's extractor in core/src/extract.ts reads six HTML attributes, being type, lang, name, content, rel and href, and the string id appears in that file zero times, so no Lantad score, defect or report row says anything about whether a section of your page has an address.
PageHeadingsWith own idAncestor id onlyNo id above
astro.build3118130
gymshark.com13721350
developer.mozilla.org10820
webflow.com3901326
en.wikipedia.org282710
Counted with an HTML parser on 21 August 2026 over the stored raw.html of five real pages this scanner captured on 15 July 2026. An ancestor id means the nearest enclosing element carrying a non-empty id, which may be a wrapper as large as the page body.

What Google documents about a read more deep link

Two Google pages describe a searcher landing part way down a document, they describe it differently, and the difference is the whole problem for a site owner deciding whether any of this needs work.

The first is Google's snippet documentation, which carries Last updated 2026-04-20 UTC and holds a section headed best practices for read more deep links in Google Search. It defines the thing in one sentence: a read more deep link is a link within a snippet that leads users to a specific section on that page. It then gives three practices to increase the likelihood that such links appear. Make sure content is immediately visible on the page to a human, and not hidden behind an expandable section or tabbed interface. Avoid using JavaScript to control the user's scroll position on page load, and it gives forcing the scroll position to the top of the page as its example. And if you make history API calls or window.location.hash modifications on page load, do not remove the hash fragment from the URL, because that breaks deep linking behavior.

Read those three again and notice what is not among them. None of the three says to give the section an identifier. Two of the three are about not undoing something after the page loads, and the third is about whether the text is visible without a click. The hash fragment is named explicitly, which tells you the mechanism runs through the URL fragment, and the page stops there.

The second is Google's featured snippets documentation, which carries Last updated 2025-12-10 UTC and answers a related question in the opposite direction. Clicking a featured snippet, it says, takes the user directly to the section of the page that appeared in the featured snippet, and scrolling to that position happens automatically, without any additional annotation by the site. It also states the fallback: if a browser does not support the underlying technology, or if Google's systems cannot confidently determine exactly where within a page to direct a click, the click takes the user to the top of the source page.

So one Google page says the site needs no annotation, and another Google page says a hash fragment is load bearing. Both statements can be true of different features, and neither page says which mechanism a read more deep link uses. That is not a criticism of the documentation so much as a description of what a reader can and cannot conclude from it, and it is the reason the rest of this post counts what is on the pages rather than asserting what Google does with it. The reference page on getting cited by Google AI Overviews collects the controls that are documented in one place.

Google pageLast updatedWhat it states about landing mid pageWhat it asks of the site
Snippet documentation2026-04-20 UTCA read more deep link leads users to a specific section on that pageDo not remove the hash fragment from the URL
Featured snippets documentation2025-12-10 UTCScrolling to the position happens automaticallyWithout any additional annotation by the site
Two Google documentation pages read on 21 August 2026, each carrying its own Last updated date. Quoted phrases are the pages' own wording.

How many of 245 headings carried an id

The count was run on 21 August 2026 with htmlparser2, which is the parser this project already depends on, walking the stored raw.html of each of the five captures and recording, for every h1 to h6 element, whether it carried a non-empty id attribute and, if not, how many levels of markup separated it from the nearest ancestor that did. The five pages are astro.build, gymshark.com, the MDN reference page for HTML, webflow.com and the English Wikipedia article on web crawlers. A sixth capture in the same set, allbirds.com, is excluded throughout because it answered the baseline fetch with HTTP 429 and stores no usable document, which is the same exclusion the count of what JavaScript added to these pages made for the same reason.

The set holds 245 heading elements, which is the same total that earlier count of heading structure reported, and 55 of them carry an id of their own. That is 22.4 percent, and the average hides the shape completely, because the per page split runs from one extreme to the other. The Wikipedia article carries an id on 27 of its 28 headings. The MDN reference page carries one on 8 of 10. astro.build carries one on 18 of 31, and the split inside that page is itself informative: all 14 of its h3 elements have an id and 4 of its 16 h2 elements do. Then gymshark.com carries an id on 2 of 137 headings, and webflow.com carries one on 0 of 39.

The two ends of that range are two different kinds of document. The Wikipedia article and the MDN page are reference documents whose publishing systems generate a stable fragment for every section, because their own tables of contents are built out of those fragments. The two commercial homepages are assembled from design system components where a heading is a styled text node inside a section wrapper, and nothing in that pipeline has a reason to name it. Neither is a fault in itself. It becomes a fact worth knowing only once something outside the site starts pointing at sections rather than pages, which is what the Google documentation in the previous section describes.

One caveat on the total before the next section refines it. A heading with no id of its own is not automatically unreachable, because a fragment can resolve to any element, and a heading sitting inside a section that carries an id is still somewhere a link can land near. That is why the hero table has three columns rather than two, and why the next section measures the distance rather than reporting a binary. Our methodology page describes what a capture stores, and the earlier count of JSON-LD in the same five files describes the same corpus from a different angle.

  • Headings with a non-empty id of their own 55 22.4 percent of 245
  • Nearest identified ancestor within three levels 7 A fragment lands close to the heading
  • Nearest identified ancestor 4 to 15 levels up 157 A fragment lands somewhere in an enclosing block
  • No id on the heading or anything above it 26 All 26 on webflow.com
Heading elements across the stored raw.html of five real pages captured 15 July 2026, counted 21 August 2026. The four categories are exclusive and sum to 245.

How far the nearest identified ancestor sits, and what rendering changed

Of the 190 headings with no id of their own, only 7 have an identified ancestor within three levels of markup. For 157 of them the nearest one is between 4 and 15 levels up, and for 26 there is none anywhere between the heading and the root of the document. All 26 of those are on webflow.com.

Distance matters because a URL fragment scrolls the identified element into view, not the heading inside it. An id on the element two levels above a heading puts a reader at the top of the block containing that heading. An id fifteen levels above it, which is the maximum observed here, puts a reader at the top of a layout container that may hold most of the page, which is functionally the same as landing at the top. Counting those as reachable would be generous to the point of being wrong, so the honest reading of this set is that 55 headings out of 245 are addressed by something that names them, another 7 are close enough that the distinction is academic, and the remaining 183 are not.

Running the same count over the stored rendered DOM rather than the raw HTTP response changes the picture in one direction only, and only on one page. The rendered totals are 247 headings and 56 with an id, so JavaScript created two headings on gymshark.com and gave one of them an id. The interesting movement is on webflow.com, where the 26 headings with nothing identified above them fall to 7, because the client side code adds identifiers the server never sent. That page's rendered DOM carries 137 non-empty id attributes against 72 in the raw response, and 44 distinct id values exist only after JavaScript has run. A browser therefore sees a page with more anchors in it than the document does. A crawler that does not execute JavaScript sees the raw number, and the difference between what those two clients receive is exactly the gap prose parity exists to measure. It is the same shape as the finding that text delivered only inside a hydration payload scored the same as no text at all: the content is there for one class of client and absent for another.

The practical consequence for anyone building on a component framework is that ids generated during hydration are not ids in the document. If deep linking matters to you, the id has to be in the server response, which for most stacks is a template change rather than a script change. The React guide covers where that boundary sits on a client rendered stack, and the reasoning generalises to any framework that assembles the page after delivery.

Raw HTTP response

  • 245 heading elements
  • 55 with a non-empty id of their own
  • 26 with no id anywhere above them
  • 2,174 id attributes, 23 of them empty
  • 59 duplicate copies of 22 id values

Rendered DOM

  • 247 heading elements
  • 56 with a non-empty id of their own
  • 7 with no id anywhere above them
  • 2,281 id attributes, 33 of them empty
  • 80 duplicate copies of 27 id values
The same five captures counted twice on 21 August 2026, once over the stored raw HTTP response and once over the stored rendered DOM.

Two ways a URL can point at part of a page

The gap between Google's two documentation pages is explained by there being two different mechanisms, and a site owner's exposure depends entirely on which one is in play.

The first is the document fragment, which has been in the web platform since the beginning. A URL ending in a hash and a name resolves to the element in that document carrying the matching id, and the browser scrolls it into view. MDN's page on text fragments states the limitation of that mechanism plainly while introducing the alternative: the issue with linking to specific document fragments is that the author of the linked page needs to put an anchor in place to actually link to, and not all documents have such anchors. That sentence is a fair description of 190 of the 245 headings counted above.

The second is the text fragment, which the same page describes as letting you link to a specific portion of text in a web page without requiring the page author to add an ID, and which it says lets you create deep links to content that you do not control and may not have IDs associated. Under that mechanism the URL carries the words to find rather than the name of an element, the client searches the rendered document for a match, and no cooperation from the site is needed at all. That is consistent with what Google's featured snippets page says about scrolling happening without any additional annotation by the site.

Nothing in Google's snippet documentation says which of the two a read more deep link uses. What it does say is that removing the hash fragment from the URL on page load breaks deep linking behavior, and both mechanisms live in the hash fragment, so that warning applies either way. What follows for a site owner is a decision under uncertainty rather than a fix: if your sections carry ids, both mechanisms can work; if they do not, you are relying entirely on the one that needs no cooperation, and no vendor page read for this post promises that one. Answer engines are a broader surface than Google Search, and answer engine optimisation as a discipline has the same problem in a harder form, because most engines document nothing at all about how a citation resolves to a position within a document.

Sample Illustrative, not a measurement of any real site.

How a URL fragment resolves, drawn from the MDN reference pages for the id attribute and for text fragments, read 21 August 2026. Illustrative of the mechanism, not a measurement of any engine's behaviour.

23 empty ids, 59 duplicates, and 162 anchors that all resolved

Counting ids at all turned up three things about the set that are worth reporting separately from the heading question, because two of them are defects by the specification's own rule and the third is the opposite of a defect.

MDN's reference for the id attribute states that the attribute defines an identifier that must be unique within the entire document, and that its value must not contain ASCII whitespace characters. The five stored raw documents hold 2,174 id attributes between them. Of those, 23 are the empty string, and all 23 are on webflow.com. An empty id cannot be the target of a fragment and cannot be selected by an identifier, so it is an attribute that does nothing except exist, and in the rendered DOM the count of empty ids on that page rises to 29 with a further 4 appearing on gymshark.com.

Duplicates are the larger group. Twenty two non-empty id values appear more than once across the set, accounting for 59 extra copies. They cluster in three places and each cluster has a different cause. astro.build repeats three values ten times each, and all three are icon sprite identifiers of the form used by an icon library, which is a generated collision rather than an authoring mistake. gymshark.com repeats sixteen values twice each, and they are dropdown option identifiers built from a component instance key, so the same widget appears twice on the page. webflow.com repeats three values, one of them twelve times, one of them an SVG mask identifier five times. Wikipedia and MDN have none at all, which is what you would expect from documents whose fragments are part of the product. A duplicate id is not a rendering failure, but a fragment pointing at one of them reaches the first occurrence only, and the other copies are unreachable by definition.

The third finding runs the other way and deserves as much space. Across the same five documents there are 1,957 anchor elements, and 204 of them carry an href that is only a fragment, a figure that matches the earlier count of anchors and hrefs in this corpus exactly. Thirty four of those 204 are a bare hash with no target, which is the standard placeholder for a control that JavaScript will take over. The remaining 170 name a target, they resolve to 162 distinct names, and every single one of those 162 matches an element id present in the same document. Not one in page anchor on any of the five pages is broken. So this is not a story about sites shipping links to nowhere. It is a story about sites shipping addresses for the places they link to themselves, and no addresses for anything else. The same asymmetry showed up when alt text on 466 captured images was counted, and it is the general shape of markup that exists to serve the site's own features rather than an outside reader. If you build on a visual editor, the Framer guide covers where these attributes come from in that pipeline, and structured data is the other place the same components decide what a machine can see. The count of schema marks and dates in these captures found the same pattern in a different attribute set.

  • Empty id attributes 23 found All 23 on webflow.com. An empty value cannot be the target of a fragment, so the attribute does nothing.
  • Duplicate id values 22 values, 59 extra copies Icon sprites on astro.build, component instance keys on gymshark.com, an SVG mask and a repeated block on webflow.com. A fragment reaches the first copy only.
  • Fragment-only anchors 204 of 1,957 34 are a bare hash placeholder with no target. The other 170 name something.
  • Named fragment targets that resolve 162 of 162 Every distinct in page anchor target on all five pages matches an id present in the same document.
Identifier conditions found in the stored raw HTML of five real pages captured 15 July 2026, counted 21 August 2026 and checked against the rule on MDN that an id must be unique within a document.

What this scanner does not read, and what to check yourself

The measurement above was written for this post with a standalone script. None of it happens during a scan, and the reason is worth stating precisely rather than in general terms, because a vague admission is the kind that quietly stops being true.

Lantad's HTML extractor is a single sink in core/src/extract.ts, driven by htmlparser2, and it reads exactly six attributes off the elements it walks: type, lang, name, content, rel and href. The word id does not appear anywhere in that file. So no part of a scan records whether a heading has an identifier, no sub-score moves when one is missing, no defect is raised, and no row in a report mentions it. A site could ship 39 headings with no address between them, as one of these five pages does, and take an A grade from us with nothing said. That is not a bug to be filed against the extractor either, because adding a signal means deciding what it is worth, and we would be pricing a behaviour that no vendor documentation we have read commits to. The honest position, taken elsewhere on the crawlers we model and the ones we do not, is to describe what is observable and refuse to score what is not.

What a reader can do without any tool is short, and it is the sort of check that takes ten minutes. Open your own page, view source rather than the inspector so you are looking at what the server sent, and search the source for the opening tag of a heading. If the ids you find in the inspector are absent from the source, they are being added during hydration and a crawler that does not run JavaScript never sees them. Then take one section you would want a search result to land on, construct the URL with the hash and that id, and load it in a new tab. If it lands at the top of the page, there is nothing there to land on. Our scan of what GPTBot sees will show you the crawler view of the document for the first half of that check even though it will say nothing about the second half.

Two limits on everything above, both of which matter more than the numbers. Five pages captured in one afternoon are a count and not a rate, and four of the five are homepages or reference pages rather than the article templates where a deep link would most likely appear, so nothing here estimates how common this is on the web. And no experiment in this post connects an id to a citation, a click or a ranking: it measures what is in the markup, against what Google's documentation says the markup does, and stops there. The studies we do have collected on retrieval and citation behaviour sit on the research page, and the per stack guides for Next.js and Shopify cover where server rendered markup is produced on those platforms. The related question of what a crawler that never scrolls or clicks can reach at all is covered in what happens to scroll loaded content.

  • type Read on script elements to find JSON-LD, and on input elements to detect a password field.
  • lang Read on the html element and stored as the document language.
  • name and content Read together on meta elements, which is how the description and the robots directives reach the pipeline.
  • rel and href Read on link elements to find the canonical URL.
  • id Not read anywhere. The string does not appear in core/src/extract.ts, so no score, defect or report row can depend on it.
What a Lantad scan reads from the HTML it parses, taken from the attribute names present in core/src/extract.ts on 21 August 2026.

Written by

Lantad

Published .

Google publishes a small feature that changes what the addressable unit of a web page is. A read more deep link, in Google's own words, is a link within a snippet that leads users to a specific section on that page, and the page describing it names one thing that breaks the behaviour: removing the hash fragment from the URL. If a searcher can be dropped into the middle of your document rather than the top of it, then the thing being retrieved is a section, and a section can only be pointed at if it has an address.

Common questions

What is a read more deep link in Google Search?

Google's snippet documentation, carrying Last updated 2026-04-20 UTC, defines it as a link within a snippet that leads users to a specific section on that page. The same page lists three best practices for making one likely: keep the content immediately visible to a human rather than behind an expandable section or tabbed interface, avoid using JavaScript to control the scroll position on page load, and do not remove the hash fragment from the URL if you make history API calls or window.location.hash modifications, because that breaks deep linking behavior.

Do my headings need id attributes for Google to deep link into them?

Google does not say. Its snippet documentation names the hash fragment as load bearing but never asks for an identifier, and its featured snippets documentation, carrying Last updated 2025-12-10 UTC, says scrolling to the position in a snippet happens automatically without any additional annotation by the site. Two mechanisms exist: a document fragment resolves to an element with a matching id, and a text fragment carries the words to find and needs no id at all. Adding ids makes both work, so it is the choice that does not depend on which mechanism is in use.

Does a duplicate or empty id attribute cause a problem?

MDN's reference for the id attribute states that the value must be unique within the entire document. An empty id cannot be the target of a URL fragment, so it does nothing. A duplicate id means a fragment reaches only the first element carrying that value, and every later copy is unreachable through the URL. Across five pages this scanner captured on 15 July 2026 there were 23 empty ids and 59 duplicate copies of 22 values, and none of them broke an existing link on those pages, because every in page anchor on all five resolved.

Does Lantad check whether my sections have addresses?

No. The extractor in core/src/extract.ts reads six HTML attributes, being type, lang, name, content, rel and href, and it does not read id at all. No sub-score, defect or report row reflects whether a heading or a section carries an identifier, so a page with no addressable sections can still take a high grade from us. The count in this post was produced by a standalone script written for the post, over the stored captures in this repository, and it is not part of a scan.

See what AI can read on your site

Run a free scan and get a graded report of exactly what AI crawlers can and cannot read, with ranked fixes.