Teaser - SEO mit Expression

SEO mit ExpressionEngine

Willkommen zurück! Aus gegebenem Anlass will ich heute einen Beitrag einschieben, in dem ich euch an meinen SEO Maßnahmen teilhaben lasse. Ich habe mich die letzten Tage etwas in die Materie eingearbeitet und will meine Erfahrungen nun mit euch teilen. Die Umsetzung ist dabei nicht ausschließlich an ExpressionEngine gebunden. Viele der vorgestellten Maßnahmen gelten allgemein, die Anwendung mit ExpressionEngine ist aber teilweise etwas verzwickt.

SEO kurz und knapp
Was ist nun SEO? Unter SEO versteht man allgemein die Optimierung bestimmter Eigenschaften von Webseiten, um es Suchmaschinen zu erleichtern, den eigenen Inhalt zu finden und zu verstehen. In den meisten Fällen passiert all das, ohne dass die Leser bzw. Benutzer einer Seite etwas davon mitbekommen. Es ist also ein wenig Arbeit, die man nicht direkt sehen kann, die sich aber lohnt wenn ihr mehr Besucher auf die Seite lotsen wollt.

In den folgenden Abschnitten stelle ich nun die einzelnen Maßnahmen genauer vor. Einige davon kann auch ein Laie problemlos umsetzen, andere wiederum erfordern etwas technisches Verständnis. Ich versuche mein Bestes, um auch die komplizierteren Teile verständlich zu erklären und mit Beispiele zu versehen. Um den Beitrag aber nicht zu sehr in die Länge zu ziehen, setze ich ein Grundwissen zur Arbeit mit ExpressionEngine voraus.

Überschriften

Fast schon eine Selbstverständlichkeit ist die Verwendung von Überschriften. Dabei solltet ihr auch die Ebenen der Überschriften beachten. Während ihr für den jeweiligen Titel einer Seite am besten h1 verwendet, eignen sich für Zwischenüberschriften dann eher h2 - h6, je nachdem wie tief ihr verschachteln wollt. Ich habe mir bei den Beiträgen angewöhnt nur h3 und h5 für die (Unter-)Abschnitte zu verwenden. Für Suchmaschinen wichtig ist nur, dass man nur einmal h1 verwendet, weil darin das Thema der Seite vermutet wird. Solltet ihr also für jeden Abschnitt die höchste Ebene der Überschrift verwenden, kann das Suchmaschinen verwirren und das mögen sie nicht wirklich, was in einer schlechteren Einstufung der Seite endet.

Titel und Meta Tags

Auch eine seit vielen Jahren bekannte und angewendete Maßnahme zur Optimierung der Suchergebnisse eurer Seite sind sog. Meta-Tags und der Titel eurer Seite. Die HTML-Tags dafür befinden sich üblicherweise im head bereich eures HTML-Markups und sind für Besucher nicht zu sehen. Suchmaschinen wiederum werten diese Informationen aus, um eure Seite einschätzen zu können.

Hier die wichtigsten Tags am Beispiel von Next Direction:

<title>Next Direction</title>

<meta charset="utf-8">
<meta http-equiv="content-Language" content="de" />
<meta name="publisher" content="Next Direction" />
<meta http-equiv="Reply-to" content="info@next-direction.de" />
<meta name="revisit-after" content="2 days" />
<meta name="description" content="Auf Next Direction werden aktuelle Themen..." />
<meta name="keywords" content="blog, web, development, web, tech, nodejs, php" />

Diese Infos befinden sich bei mir auf jeder Seite. Bestimmte Inhalte können über ExpressionEngine Template Variablen ausgelesen werden, damit sie sich einfach und zentral ändern lassen. Ein Beispiel dafür ist der Titel der Seite. Diesen können wir auch mit {site_name} direkt aus den Einstellungen übernehmen.

Dynamische Seitentitel

Es ist üblich, auf Unterseiten bzw. in meinem Fall für die einzelnen Beiträge, Seitentitel zu verwenden, die auf den Inhalt der Seite schließen lassen. Wie können wir also den Seitentitel aus ExpressionEngine in das <title></title> Tag unseres Templates einfügen?

Dazu muss ich kurz etwas ausholen und den groben Aufbau meiner Seite beschreiben. Weil ich natürlich nicht für jede einzelne Seite immer wieder die grundlegende Struktur der Seite wiederholen möchte, gibt es dafür ein Layout. Das Layout beinhaltet die oben angesprochenen Meta-Tags und die nötigen CSS Definitionen und JavaScript Funktionen, die auf allen Seiten benötigt werden. Die Seiten meines Blogs erweitern dann dieses Layout und fügen den jeweiligen Inhalt ein, z.B. den Inhalt des Beitrags, den ihr gerade lest. ExpressionEngine bietet eine elegante Möglichkeit namens Layoutvariablen, um Informationen von diesen Seiten an das Layout weiterzureichen und dort zu verwenden.

Wir wollen also den Titel eines Beitrags im Titel der Seite verwenden, um ihn in Suchergebnissen entsprechend wiederzufinden. Dazu müssen wir zuerst das oben gezeigte Tag für den Titel etwas abändern:

<title>{if layout:page_title != ''}{layout:page_title} | {/if}{site_name}</title>

Zum Abschluss dieses Abschnitts fehlt also noch das Setzen der Information auf der Seite, die den Einzelbeitrag anzeigt. ExpressionEngine unterscheidet standardmäßig nicht zwischen der Anzeige eines einzelnen Beitrags oder aller Beiträge. Ich habe mich jedoch dazu entschieden, für beides jeweils ein eigenes Template zu erstellen, da es mir mehr Flexibilität bei der Anzeige bietet. Hier der Ausschnitt meines Templates zur Anzeige des Beitrages, indem auch dynamisch der Titel des Beitrags an das Layout weitergegeben wird:

{exp:channel:entries channel='posts' limit='1' require_entry='yes'}
    <article>
        <!-- Markup für Beitrag -->
    </article>
    {layout:set name="page_title"}{title}{/layout:set}
{/exp:channel:entries}

Geschafft! Jeder einzelne Beitrag hat nun einen individuellen Titel und taucht damit unterscheidbar für eure Besucher in den Suchergebnissen auf.

Sprechende URLs

Immer wieder Thema wenn es um die Optimierung eurer Seite für Suchmaschinen geht, sind sprechende URLs. Bei vielen CMS-Systemen braucht man hier Plugins und Erweiterungen. Auch bei Typo3 hat es diese Funktion erst mit Version 9 in den Standard geschafft. ExpressionEngine bietet diese Funktion direkt und ohne Umwege an. Jeder Beitrag den ich erstelle bekommt automatisch einen URL Titel zugewiesen, den ich bei Bedarf auch ändern kann. In der Regel handelt es sich dabei um den Titel des Beitrags, bei dem alle Sonderzeichen entfernt werden und nur Zeichen übrig bleiben, die in einer URL unbedenklich sind. Sprechende URLs sind wichtig, da sie in den Suchergebnissen meist sehr prominent direkt unterhalb des Titels angezeigt werden. Da lese ich lieber

https://next-direction.de/posts/seo-mit-expressionengine

als etwas kryptisches wie

https://next-direction.de/posts/?id=100

Wie ihr solche Links erzeugt? Ganz einfach! ExpressionEngine bietet eine Funktion, um einen Link zu einem Einzelbeitrag zu erzeugen. Hier wieder ein kleiner Ausschnitt aus meinem Template zur Anzeige der Liste von Beiträgen, in dem ihr sehen könnt, wie der Link erzeugt wird:

<section class="article-list">
    {exp:channel:entries channel="posts" limit="10"}
        <article>
            <div class="article-image">
                <a href="{route='posts/single-post' url_title='{url_title}'}">
                    <!-- Bild des Beitrags -->
                </a>
            </div>
            <div class="article-content">
                <h4><a href="{route='posts/single-post' url_title='{url_title}'}">{title}</a></h4>
                <!-- Kurzer Ausschnitt des Beitrags -->
                <div class="continue-reading">
                    <a href="{route='posts/single-post' url_title='{url_title}'}">weiterlesen</a>
                </div>
            </div>
        </article>
    {/exp:channel:entries}
</section>

Der entscheidende Teil ist der hier:

<a href="{route='posts/single-post' url_title='{url_title}'}">

Mit der {route=""} Funktion erzeugt ihr einen Link zur angegebenen Seite und darin wird der entsprechende URL Titel direkt aus dem Beitrag verwendet.

Bilder mit “alt” Attribut

Ein weiterer Punkt, der relativ leicht umzusetzen ist, sind alt Attribute für Bilder. Diese Attribute sind wichtig für Programme wie Screen Reader, die einem den Inhalt der Seite vorlesen. Die Abkürzung alt steht dabei für alternativ und wird immer dann wichtig, wenn jemand das Bild nicht wahrnehmen kann oder es einfach nicht vom Server geladen werden kann. Suchmaschinen legen großen Wert darauf, dass diese Information verfügbar ist.

Hier ein Codeschnipsel aus meinen Templates, um den Titel des hochgeladenen Bildes als alternativen Text zu verwenden:

<img src="{image}" alt="{image}{title}{/image}">

Das über ExpressionEngine angelegte Feld für das Bild heißt in diesem Fall image. Ihr könnt dem Feld aber auch einen beliebigen anderen Namen geben. Man sieht auch den Unterschied in der Verwendung. Das ist ein gebräuchliches Muster in ExpressionEngine. Manche Platzhalter können sowohl in der einfachen Variante {image} als auch in der erweiterten Variante {image}...{/image} verwendet werden. Mit der erweiterten Variante könnt ihr dann auf Meta-Informationen des Bildes zugreifen.

Mobile Welten

In der heutigen Zeit, ist eine der wichtigsten Eigenschaften eurer Seite das Verhalten auf mobilen Geräten bzw. auf kleinen Bildschirmen. Ich habe es mir angewöhnt, wenn ich mit dem Design einer Seite beginne, zuerst auf die kleinen Bildschirme zu optimieren und im Anschluss über CSS @media Queries kleinere Anpassungen für die Desktop Browser vorzunehmen. Ich empfehle in diesem Fall immer mit min-width zu Arbeiten.

Auf manchen Seiten reicht es, bestimmte maximale Breiten oder Höhen festzulegen. Oft kann man auch die Anordnung von Elementen über grid bzw. flex ändern um ein Layout zu erhalten, das auf kleine bzw. große Bildschirme ausgerichtet ist.

Da dieser Beitrag aber keine Einführung in das Design von responsiven Webseiten sein soll, belasse ich es an dieser Stelle mit der kurzen Einführung.

Wer ist dieser JSON-LD

Wer in der Welt der Entwicklung unterwegs ist, wird über kurz oder lang auf das JSON Format stoßen. Es ist ein sehr leichtgewichtiges Format, um Daten zwischen Systemen auszutauschen. Auch für den Menschen ist es einigermaßen leicht zu verstehen, wodurch es in den letzten Jahren quasi zum Standard geworden ist, der von fast allen Programmiersprachen unterstützt wird.

JSON-LD ist eine Erweiterung davon und dient zur Bereitstellung von strukturierten Daten, die den Inhalt einer Seite näher beschreiben. Seit einigen Jahren gibt es auch eine Empfehlung des W3C, den eigenen Seiteninhalt damit zu beschreiben. Obwohl Suchmaschinen über die Jahre immer intelligenter geworden sind und viele der gefundenen Inhalte richtig einsortiert haben, ist es nochmal was anderes, wenn der Seitenbetreiber exakte Informationen über den Inhalt bereitstellt.

Vielleicht habt ihr schon bemerkt, dass Google über die letzten Jahre für immer mehr gesuchte Inhalte spezielle Boxen am rechten Rand anzeigt.

Anzeige strukturierter Daten

Diese Informationen könnt auch ihr für eure Seite bereitstellen. Die dazu verwendete Auszeichnungssprache heißt, genau, ihr habt es erraten, JSON-LD. Ihr müsst euch aber nicht komplett selbst überlegen, wie ihr diese Information aufzubauen habt. Es gibt ein Projekt namens schema.org, das eine riesige Menge an möglichen Inhaltstypen beschreibt. In meinem Beispiel bieten sich die beiden Typen Blog und BlogPosting an. Es gibt aber auch Schemata für Personen oder Kochrezepte. Am einfachsten verwendet ihr eine Suchmaschine eures Vertrauens und sucht nach Inhaltselementen, die auf eurer Seite zu finden sind. Ich bin mir sicher, euer Schema ist auch bereits definiert.

Tipp
Google bietet eine Seite an, mit der ihr die strukturierten Daten eurer Webseiten testen könnt. Das Ganze findet ihr hier.


JSON-LD und ExpressionEngine

Die Verwendung von strukturierten Daten in ExpressionEngine ist sicherlich das komplexeste Thema, das ich in diesem Beitrag behandle. Es gibt zwar eine Erweiterung, die sich mit der Thematik beschäftigt, allerdings hat mir die nach meinen Tests nicht gefallen und ich habe mich dazu entschieden das Ganze mit Bordmitteln umzusetzen. Ich bin auch ein Verfechter davon, so wenig wie möglich Abhängigkeiten in einem Projekt zu verwenden.

Zur Umsetzung verwenden wir eine Funktion, die ich bereits vorgestellt habe, die Templatevariablen. In dem folgenden Beispiel beschreibe ich mein Vorgehen, um meinen Blog und die Beiträge auf der Startseite zu beschreiben. Da sich die Beiträge ändern können, werden diese Informationen dynamisch an das Template weitergegeben und dort entsprechend aufbereitet.

Hier nun als erstes der Teil, der die Informationen an das Layout weiterreicht:

<section class="articles">
    {exp:channel:entries channel="posts" limit="4"}
        <!-- Hier das Markup für die Darstellung der Beiträge -->
        {layout:set:append name="json_list_title"}{title}{/layout:set:append}
        {layout:set:append name="json_list_image"}{image}{/layout:set:append}
        {layout:set:append name="json_list_short_text"}{short_text}{/layout:set:append}
        {layout:set:append name="json_list_full_text"}{full_text}{/layout:set:append}
        {layout:set:append name="json_list_author"}{author}{/layout:set:append}
        {layout:set:append name="json_list_keywords"}{keywords}{/layout:set:append}
        {layout:set:append name="json_list_url_title"}{url_title}{/layout:set:append}
        {layout:set:append name="json_list_published"}{entry_date format="%Y-%m-%dT%H:%i:00+00:00"}{/layout:set:append}
        {layout:set:append name="json_list_edited"}{edit_date format="%Y-%m-%dT%H:%i:00+00:00"}{/layout:set:append}
    {/exp:channel:entries}
</section>

Im Gegensatz zur Syntax, die ich bei den Seitentiteln verwendet habe, kommt hier eine leichte Abwandlung zum Einsatz layout:set:append. Diese sorgt dafür, dass kein einzelner Wert sondern ein Array, also eine Liste von Werten, übergeben werden kann. Das ist notwendig, da ich für jeden Beitrag der Startseite eine eigene Beschreibung erstellen will.

Es ist üblich, diese Informationen, wie die Meta-Tags auch, im head Bereich der Seite unterzubringen. Deshalb habe ich das Ganze über Bedingungen in mein Layout eingefügt. Nur wenn die angezeigte Seite Informationen bereitstellt, wird über das Layout auch die JSON-LD Beschreibung dazu erzeugt. Der Code, der für die Erzeugung zuständig ist, sieht folgendermaßen aus:

{
    "@context": "http://schema.org",
    "@type": "Blog",
    "name": "{site_name}",
    "url": "{site_url}",
    "description": "Auf Next Direction werden aktuelle Themen rund um die Web-Entwicklung vorgestellt. Hin und wieder werden auch Themen aus verwandten Gebieten aufgegriffen.",
    "sameAs": [
        "https://twitter.com/NextDirectionDE",
        "https://github.com/next-direction"
    ],
    "publisher": {
        "@type": "Organization",
        "name": "{site_name}",
        "logo": {
            "@type": "ImageObject",
            "name": "NextDirectionLogo",
            "width": "512",
            "url": "{site_url}images/Logo512.png"
        }
    }
    {if layout:json_list_title}
    ,
    "blogPost": [
        {layout:json_list_title}
            { 
                "@context": "http://schema.org", 
                "@type": "BlogPosting",
                "headline": "{exp:nd_util:htmlentities}{value}{/exp:nd_util:htmlentities}",
                "image": "{layout:json_list_image index='{index}'}",
                "keywords": "{layout:json_list_keywords index='{index}'}", 
                "publisher": {
                    "@type": "Organization",
                    "name": "{site_name}",
                    "logo": {
                        "@type": "ImageObject",
                        "name": "NextDirectionLogo",
                        "width": "512",
                        "url": "{site_url}images/Logo512.png"
                    }
                },
                "url": "{path='/posts'}/{layout:json_list_url_title index='{index}'}",
                "mainEntityOfPage": "{path='/posts'}/{layout:json_list_url_title index='{index}'}",
                "datePublished": "{layout:json_list_published index='{index}'}",
                "dateModified": "{layout:json_list_edited index='{index}'}",
                "description": "{exp:nd_util:htmlentities}{layout:json_list_short_text index='{index}'}{/exp:nd_util:htmlentities}",
                "author": {
                    "@type": "Person",
                    "name": "{layout:json_list_author index='{index}'}"
                }
                }
                {if count != total_results}
                ,
                {/if}
        {/layout:json_list_title}
    ]
    {/if}
}

Das Ganze solltet ihr in ein <script type="application/ld+json"></script> Tag fassen.

Puh, ganz schön viel auf einmal oder? Ich möchte an dieser Stelle nicht zu sehr ins Detail von JSON-LD und schema.org einsteigen, sondern nur die Teile beschreiben, die sich auf ExpressionEngine beziehen. Ich habe versucht, soviel wie möglich aus den Einstellungen zu laden, z.B. den Titel der Seite oder auch die URL zu Next Direction.

Der interessante Teil beginnt bei {if layout:json_list_title}. Wenn eine Seite diese Informationen an das Layout übergibt, wird für jeden übergebenen Beitrag eine JSON-LD Beschreibung mit dem BlogPosting Schema erstellt. Dafür ist der folgende Ausschnitt verantwortlich, der so oft durchlaufen wird, wie es Beiträge auf der Startseite gibt:

{layout:json_list_title}
    <!-- BlogPosting Markup -->
{/layout:json_list_title}

Zum Abschluss dieses Abschnitts möchte ich noch kurz auf zwei Besonderheiten eingehen

  • {exp:nd_util:htmlentities}{value}{/exp:nd_util:htmlentities}: Hier wird der Titel des Inhalts ausgegeben. Da es ein Problem gab, wenn in einem Text ein Anführungszeichen vorkam, musste ich hier ein eigenes Plugin schreiben, welches diese Passagen entschärft. Solltet ihr Interesse daran haben, könnt ihr mich gerne kontaktieren.
  • {layout:json_list_keywords index='{index}'}: Über das obige Markup, welches die Beiträge durchläuft, wird nur die Liste der Titel durchlaufen. Um dann im jeweiligen Durchlauf auf die weiteren Elemente des Beitrags zuzugreifen, gibt es den Platzhalter {index}. Der beinhaltet immer die Nummer des aktuellen Beitrags und erlaubt somit den Zugriff auf die zugehörigen Eigenschaften des Beitrags, in diesem Fall die Schlüsselwörter.

Sitemap

Der letzte Aspekt, den ich euch noch kurz vorstellen will, ist die Sitemap. Damit teilt ihr einer Suchmaschine mit, welche Seiten es bei euch gibt. Google bietet eine relativ einfache Möglichkeit über die Search Console, die Sitemap zu registrieren. Damit erleichtert ihr es einer Suchmaschine, bestimmte Seiten zu finden und in den Index aufzunehmen.

Für meinen Blog sieht das Template zur Erstellung einer solchen Sitemap wie folgt aus:

<?xml version="1.0" encoding="UTF-8"?>
<urlset
  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
  http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
    <url>
      <loc>{site_url}</loc>
      <lastmod>{current_time format="%Y-%m-%dT%H:%i:00+00:00"}</lastmod>
      <changefreq>weekly</changefreq>
    </url>
    {exp:channel:entries channel="posts"}
      <url>
        <loc>{path='/posts'}/{url_title}</loc>
        <lastmod>{entry_date format="%Y-%m-%dT%H:%i:00+00:00"}</lastmod>
        <changefreq>weekly</changefreq>
      </url>
    {/exp:channel:entries}
    <url>
      <loc>{path='/posts/all-posts'}</loc>
      <lastmod>2018-12-20T06:31:54+00:00</lastmod>
      <changefreq>weekly</changefreq>
    </url>
    {exp:channel:entries channel="site" entry_id="1|2"}
        <url>
          <loc>{path='/site'}/{url_title}</loc>
          <lastmod>{edit_date format="%Y-%m-%dT%H:%i:00+00:00"}</lastmod>
          <changefreq>monthly</changefreq>
        </url>
    {/exp:channel:entries}
    <url>
      <loc>{path='/rss'}</loc>
      <lastmod>2018-12-20T06:21:25+00:00</lastmod>
      <changefreq>weekly</changefreq>
    </url>
    <url>
      <loc>{path='/site/contact'}</loc>
      <lastmod>2018-12-20T06:31:55+00:00</lastmod>
      <changefreq>monthly</changefreq>
    </url>
</urlset>

Im Grunde werden alle Seiten, die für die Öffentlichkeit relevant sind, aufgelistet. Es werden bestimmte Informationen angegeben, wie der Zeitpunkt der letzten Bearbeitung oder aber die Frequenz mit der sich Inhalte ändern können.

Bei statischen Seiten, wie dem Kontaktformular, reicht es wenn man als Änderungsfrequenz monatlich übergibt. Bei dynamischen Inhalten wie den Beiträgen selbst, habe ich mich dazu entschieden auf wöchentliche Änderungen zu setzen. Diese Informationen helfen einer Suchmaschine beurteilen zu können, wie oft sie auf den gelisteten Seiten vorbeischauen soll.

Fazit

Wie immer, hier noch eine kurze Zusammenfassung der Thematik. Wenn ihr euren Webauftritt seriös gestalten möchtet, solltet ihr auf jeden Fall tiefer in das Thema SEO einsteigen. Für Firmen ist es auf jeden Fall eine Überlegung wert, sich an einen Experten zu wenden. Es gibt viele Feinheiten, die nicht immer irgendwo dokumentiert sind und jemand der sich jeden Tag mit der Thematik beschäftigt, wird hier auf jeden Fall noch mehr für euch rausholen. Für eher kleinere oder privatere Seiten, reicht es meiner Meinung nach, sich ein paar Stunden einzulesen und die wichtigsten Punkte, die ich beschrieben habe, umzusetzen.

Jetzt will ich euch aber nicht länger aufhalten. Hoffentlich bis zum nächsten Mal!