We use Google Analytics cookies to understand which pages and tools are useful and improve the site. Privacy policy.

Next.js SEO

How do you build a crawlable table of contents with scrollspy?

By bumpit Editorial2026-07-264 min read

Render ordinary anchor links to stable heading IDs, then add scrollspy as progressive enhancement. Keep navigation usable without JavaScript and below sticky headers.

Written for next.js developers building long-form article navigation that works for crawlers, keyboards, mobile readers, and active-section tracking.

Key facts

  • Crawlable links use real anchor elements with href values that resolve to section fragments.
  • Stable unique heading IDs let browsers navigate without JavaScript.
  • IntersectionObserver can enhance the current-section state without owning the navigation itself.

A useful rule: make each important claim understandable and verifiable without requiring the reader to reconstruct your meaning from the rest of the page.

What HTML should the table of contents use?

Render a `nav` with an accessible label, an ordered or unordered list, and ordinary anchors such as `href="#how-it-works"`. Each target heading needs a unique stable `id`. The links and headings should exist in server HTML so browsers, crawlers, keyboard users, and assistive technology can understand the article structure before hydration. Do not use buttons for navigation to document fragments and do not attach click handlers to noninteractive text. Native links also preserve expected copy, open-in-new-tab, and history behaviour.

  • Use a labelled nav and real anchors.
  • Generate unique deterministic heading IDs.
  • Keep the complete list in initial HTML.

Sources: 1, 2

How should heading IDs be generated?

Prefer IDs stored with the article or generated once during content processing from controlled headings. A slugifier must handle punctuation, Unicode, repeated headings, and future edits. If an editor changes a heading, preserve the old fragment when external links may exist or provide an alias target. Do not compute IDs separately in the table and article render because small normalization differences create broken links. Add a content test that every TOC href resolves to exactly one element and every included section has one matching entry.

  • Use one shared heading-ID source.
  • Detect duplicates during content validation.
  • Preserve useful legacy fragments after editorial changes.

Sources: 1, 2

How does scrollspy work without replacing links?

A small client component can observe section elements with IntersectionObserver and mark the most relevant link using `aria-current="location"` or a visual data attribute. Clicking still follows the anchor normally. Update the URL fragment deliberately; aggressive `replaceState` calls during scrolling can create noisy history or analytics. When IntersectionObserver is unavailable, the table remains fully functional with no active highlight. This progressive-enhancement boundary keeps most article rendering on the server and limits client JavaScript to optional state.

  • Observe existing targets after hydration.
  • Use aria-current only for the active link.
  • Leave native fragment navigation intact.

Sources: 2, 3

How should sticky positioning behave?

On wide layouts, place the TOC in a sidebar with `position: sticky` and a top offset that clears the site header. Ensure no ancestor's overflow or transform unintentionally disables sticky behaviour. Constrain height and allow internal scrolling only when the list is genuinely long. On small screens, move the TOC into normal flow or use a native disclosure pattern without trapping content. Add `scroll-margin-top` to headings so fragment navigation does not hide the section title under a sticky header.

  • Test sticky behaviour within the actual layout ancestors.
  • Set heading scroll margin for fixed headers.
  • Use a simpler in-flow pattern on narrow screens.

Sources: 2, 3

What should accessibility and SEO QA cover?

Disable JavaScript and confirm every link reaches the correct visible heading. Navigate by keyboard, inspect focus visibility, and test screen-reader labels. Verify that scrolling does not move keyboard focus unexpectedly and that active-state colour meets contrast requirements. Crawl the raw HTML to confirm anchors are present and URLs are valid. Test direct visits to each fragment, back-button behaviour, reduced motion, long headings, and mobile widths. The TOC succeeds when it improves orientation without hiding content, generating duplicate IDs, or forcing a client-rendered article. Repeat these checks after layout changes.

  • Test no-JavaScript and keyboard navigation.
  • Verify focus, contrast, fragments, and browser history.
  • Crawl heading links as part of article validation.

Sources: 1, 2, 3

Put it to work

Find the highest-impact fix on your site.

Check crawlable fragment links, unique heading IDs, sticky behaviour, and progressive scrollspy enhancement.

Audit article navigation

Sources

  1. 1.Google Search Central: Make links crawlableChecked 2026-07-26
  2. 2.Next.js documentation: Server and Client ComponentsChecked 2026-07-26
  3. 3.Google Search Central: SEO Starter GuideChecked 2026-07-26
Published 2026-07-26 · Last reviewed 2026-07-26 · Review due 2026-10-26Search systems and content quality