Skip to content

Introduction

Some organic search results in Google have additional links below the meta description. Others may include a link titled "Jump to <text section> —" at the beginning of the meta description. This kind of link is added by Google, especially if the search query is more specific.

If a user follows such a jump link, they will be taken directly to the corresponding text segment on the landing page. URL fragments (#section) are used to provide this behavior.

Getting these anchor links are valuable for both users and website owners:

  • Google will use the subheadings of your website as anchor texts. This information lets the user better assess the relevance of your site directly in the search results. This will eventually lead to a higher dwell time on your site. Besides, the likelihood that the user will return to the search results after visiting your site is lower (Return-to-SERP-Rate).
  • Usability advantages on the website itself: the contents will be well-structured and can be accessed through a table of contents (I'll get to that in a moment).
  • The additional links will make your page more prominent in the search engine result pages.
  • As mentioned, the anchor links lead the user directly to the relevant text section.

Google has been offering this feature since 2009. I'll explain the general requirements and then show you how you can implement it in TYPO3.

Requirements

Google's original announcement states that setting up the anchors is especially worthwhile for long, multi-topic pages. Their contents must be well-structured and divided into logical sections with subheadings.
The subheadings have to describe the respective content – "Section B" will not be sufficient.

Furthermore, you must meet the following technical requirements to be able to receive anchor links in Google search results:

  1. Assign anchors: You have to assign the URL fragments as id attributes directly to the headings.
  2. Provide a table of contents: Your landing page must contain links with jump anchors to the headings. Such a table of contents will improve usability, especially for longer articles.
  3. Don't use URL paths: The links of your table of contents must only contain the URL fragments.

1. Assign anchors

A heading with a correctly associated anchor could look like this:

<h2 id="requirements">Requirements</h2>
<!-- or perhaps: -->
<h2 id="ce123">Requirements</h2>

Whether the anchor is human-readable (as in the first example) is irrelevant for the output in the Google search result.

Each content element in TYPO3 has a wrapper with id="c123". However, this anchor in the wrapping <div> is not sufficient to obtain jump links in the search result.

Addendum 07/2023: Google has since revised its instructions on anchor markup. The use of <section> elements is now recommended:

<section id="requirements">
  <h2>Requirements</h2>
  <!-- Content of this section -->
</section>

To be semantically correct, the section would have to include the entire content (belonging to the heading). Since this content can span several content elements in TYPO3, you mustn't define the section in the Fluid templates of every content element. Theoretically, you could provide the comprising section as a container element, for example - but that would unnecessarily complicate content maintenance.
According to Google, adding the anchor directly at the heading is still acceptable. And this is precisely the approach I can unreservedly recommend you.

2. Provide a table of contents

A table of contents (TOC) might look like the one in this article (scroll up a bit). Position and styling aren't relevant to Google. You can place the TOC at the beginning, middle, or end of the page or source code. You could use a fixed position to keep it visible at all times or put it in a collapsible component. Just keep it user-friendly.

The most important thing for Google is that a table of contents with jump links to the text sections (read: subheadings) of the current page exists.

Don't use URL paths

The table of contents may only use the URL fragments as jump links! Relative or absolute paths will not interfere with the navigation. But for Google, they are a criterion for exclusion in this case.

<!-- correct: -->
<a href="#requirements">Requirements</a>
<a href="#ce123">Requirements</a>

<!-- wrong: -->
<a href="/news/article/#requirements">Requirements</a>
<a href="https://www.domain.com/news/article/#requirements">Requirements</a>

Good to know

  • As always, Google has the final say. Whether jump anchors are displayed in the search result will largely depend on the user's search query.
  • When using anchor links, no PageRank is transferred. You won't split your PageRank between the individual jump links.

To meet these requirements in TYPO3, we have to make a few adjustments to the Fluid templates of the content elements (fluid_styled_content). For the table of contents, we will create a new TYPO3 content element.

I have combined these customizations into a small extension for you:

SEO Table of contents on GitHub

Adapt headings of TYPO3 content elements

Partials/Header/All.html

We have to add the UID to the list of arguments for the partial Header.html.

<f:if condition="{data.header_layout} != 100">
    <f:if condition="{data.header} || {data.subheader} || {data.date}">
        <header>
            <f:render partial="Header/Header" arguments="{
                header: data.header,
                layout: data.header_layout,
                positionClass: '{f:if(condition: data.header_position, then: \'ce-headline-{data.header_position}\')}',
                link: data.header_link,
                uid: data.uid,
                default: settings.defaultHeaderType}" />
        </header>
    </f:if>
</f:if>

Partials/Header/Header.html

We assign an id attribute with the UID of the content element to every heading. Since the prefix "c" is already used in the wrapper of each content element, we'll opt for "ce".

In f:defaultCase we have to pass the variable uid again. If you forget this, the value will be missing for headings with the layout "Default".

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">

<f:if condition="{header}">
    <f:switch expression="{layout}">
        <f:case value="1">
            <h1 id="ce{uid}" class="{positionClass}">
                <f:link.typolink parameter="{link}">{header}</f:link.typolink>
            </h1>
        </f:case>
        <f:case value="2">
            <h2 id="ce{uid}" class="{positionClass}">
                <f:link.typolink parameter="{link}">{header}</f:link.typolink>
            </h2>
        </f:case>
        <f:case value="3">
            <h3 id="ce{uid}" class="{positionClass}">
                <f:link.typolink parameter="{link}">{header}</f:link.typolink>
            </h3>
        </f:case>
        <f:case value="4">
            <h4 id="ce{uid}" class="{positionClass}">
                <f:link.typolink parameter="{link}">{header}</f:link.typolink>
            </h4>
        </f:case>
        <f:case value="5">
            <h5 id="ce{uid}" class="{positionClass}">
                <f:link.typolink parameter="{link}">{header}</f:link.typolink>
            </h5>
        </f:case>
        <f:case value="6">
            <h6 id="ce{uid}" class="{positionClass}">
                <f:link.typolink parameter="{link}">{header}</f:link.typolink>
            </h6>
        </f:case>
        <f:case value="100">
            <f:comment> -- do not show header --</f:comment>
        </f:case>
        <f:defaultCase>
            <f:if condition="{default}">
                <f:render partial="Header/Header" arguments="{
                    header: header,
                    layout: default,
                    positionClass: positionClass,
                    uid: uid,
                    link: link}"/>
            </f:if>
        </f:defaultCase>
    </f:switch>
</f:if>
</html>

Configure table of contents in TYPO3

The TYPO3 core already provides a navigation element of type "Section Index" which can list content elements of one or several pages. For the following three reasons, however, it is not advisable to use the Section Index for our TOC:

  1. It links to the wrapper of the content elements, for example "c123".
  2. It also lists content elements with hidden headings. You naturally can't set id attributes to a heading which is not present.
  3. As this navigation can list content elements of several different subpages, it will use relative URLs for all links.

You could adapt all this, but it would change the intended functionality of this navigation far too much.

Instead, we take Section Index as the basis for a new menu content element that is tailored to the requirements.

How to create your own content elements in TYPO3 is well described in the official documentation.

Below I will only show the parts that are relevant for the frontend. You can find the necessary TCA, registration, etc. in the finished extension.

TypoScript setup

In this reduced copy of the Section Index, we only use the current page (pidInList.data = page:uid). Therefore, we can completely omit the select field for pages in our new content element.

In the where clause, we additionally check if a value in the header field exists and if the layout is not hidden. Only under these conditions we're able to render and link a header with an id attribute.
Since we still query the status of sectionIndex, the editor can manually exclude individual content elements from the listing as usual.

tt_content.tx_seotableofcontents_menu_section_seo =< lib.contentElement
tt_content.tx_seotableofcontents_menu_section_seo {
    templateName = MenuSectionSeo
    templateRootPaths {
        1 = EXT:seo_tableofcontents/Resources/Private/Templates/
        10 = {$styles.templates.templateRootPath}
    }
    dataProcessing {
        10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
        10 {
            table = tt_content
            pidInList.data = page:uid
            as = content
            where (
                sectionIndex = 1
                AND header != ""
                AND header_layout != 100
            )
            orderBy = sorting
        }
    }
}

As links we only set the URL fragment, identical to the new id attribute in Header.html.

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">

<f:layout name="Default" />
<f:section name="Main">

    <f:if condition="{content}">
        <ul>
            <f:for each="{content}" as="element">
                <li>
                    <a href="#ce{element.data.uid}">
                        {element.data.header}
                    </a>
                </li>
            </f:for>
        </ul>
    </f:if>

</f:section>
</html>

Add-on: with speaking URL fragments

In April, I published an extension (EXT:content_slug) that brings human-readable anchor links into TYPO3. Of course, you can also use these anchors in the new SEO table of contents.

The necessary adjustments in the Fluid templates are a bit more complex here. Since the URL fragments are set manually by editors, we must additionally check for their existence. Logically, this applies to the templates of both the headings and the table of contents.

EXT:seo_tableofcontents already provides alternative Fluid templates that you can use in combination with EXT:content_slug. You can include them via TypoScript if needed (and customize them in the usual manner).

The alternative Header.html template is designed to stay fully compatible with the default menus of type "Section Index". Every content element can be listed properly with these menus and the new SEO table of contents:

  • If available, the human-readable URL fragment is used by all menus.
  • Fallback 1: The "Section Index" will use the anchor "c123" of the content element's div wrapper. This kind of link also is TYPO3's default behavior if EXT:content_slug is not installed.
  • Fallback 2: For the SEO table of contents, the anchor "ce123" in the heading is used if no individual fragment is set in the content element.

I won't list these adjustments here. However, the templates of EXT:seo_tableofcontents contain explanatory comments.

Conclusion

A well-structured page, meaningful headings, and a helpful table of contents will make the life of our website visitors easier — they can find the desired information faster. Google rewards our efforts with the ability to get jump links in search results.