000%

Aktualisiert

NOTES · SEO / GEO / AEO09 · 2026.10

Öffentlichen Inhalt, Metadaten und strukturierte Daten gemeinsam implementieren

Erzeugen Sie Inhalt, Titel, kanonische URL und strukturierte Daten aus dem Text einer öffentlichen Abfragefunktion. Vergleichen Sie im selben Beispiel SSR pro Anfrage mit beim Build erzeugten Archivseiten.

§ 01SEO / GEO / AEO

Konfigurationsorte bestimmen und Web, Server und Infrastruktur verbinden

Ziel ist ein öffentlicher Nutzungshinweis mit übereinstimmendem Titel, Inhalt, Adresse, Daten und Belegen. Die Suche kann Thema und kanonische Adresse verarbeiten; webgestützte Antworten können Fakten und Bedingungen nutzen. Neue Zeiten im Inhalt, aber alte in Zusammenfassung und JSON-LD erzeugen Widersprüche. Der folgende Code leitet deshalb alle Ausgaben aus derselben veröffentlichten Revision ab.

Bestimmen Sie zuerst, wann öffentliche Seiten wechseln. Selten geänderte Hinweise können beim Build entstehen; häufig geänderte Artikel lesen serverseitig die aktuelle Revision oder aktualisieren Caches bei Veröffentlichung. Öffentliche Seiten und Verwaltung können eine Anwendung teilen, behalten aber getrennte Abfrageberechtigungen und Cache-Bedingungen.

Die folgende Dateianordnung ist ein Beispiel für eine Next.js-Website mit CMS. In Repositories mit src liegen app und lib darunter. Funktionsnamen wie getPublicGuide werden passend zum Projekt gewählt.

app/ layout.tsx Gemeinsame Oberfläche und Standardmetadaten guides/[slug]/page.tsx Öffentliche Abfrage, Inhalt, Detailmetadaten robots.ts Crawling-Hinweise sitemap.ts Öffentliche kanonische URLs lib/ guides.ts Veröffentlichungsbedingungen und Abfragen identity-time.mjs Daten, Öffentlichkeit, kanonische URLs site.ts Produktionsdomain next.config.ts Framework- und Bereitstellungskonfiguration package.json Pakete sowie build- und start-Befehle

Build-Befehle stehen in scripts der package.json. Ein direkt betriebener Node.js-Server kann mit next build erzeugt und mit next start gestartet werden. Statische Exporte kommen auf statisches Hosting. Verwaltete Plattformen nutzen die Build- und Ausführungseinstellungen des Projekts; prüfen Sie Ausgaben und unterstützte Serverfunktionen.

Verbinden Sie die Produktionsdomain über DNS und Zertifikat und lassen Sie öffentliche Pfade durch CDN und Firewall. Der Server liest öffentliche Artikel und liefert HTML, Metadaten und Sitemap. Prüfen Sie anschließend Cache-Updates durch Veröffentlichungsereignisse. Dieselbe URL über Verbindung bis Inhalt anzufragen macht abweichende Einstellungen leichter auffindbar.

Änderungsorte je Konfiguration
Inhalt, Titel, Links
Änderungsort
page.tsx, Inhaltskomponenten, CMS-Text
Prüfergebnis
Überschriften, Absätze und a href im Antwort-HTML
Öffentlichkeit und Daten
Änderungsort
Serverabfrage, DB-Bedingungen, CMS-Veröffentlichung
Prüfergebnis
Ergebnisse öffentlicher, noch nicht freigegebener und zurückgezogener Artikel
Kanonische Adresse und Suchanweisungen
Änderungsort
generateMetadata, layout, robots, Serverheader
Prüfergebnis
Dokument-canonical sowie meta- und HTTP-Suchanweisungen
Auffindbare URLs
Änderungsort
sitemap.ts, Listenseiten, verwandte Artikel
Prüfergebnis
Vollständige öffentliche Liste stimmt mit Sitemap und internen Links überein
Domain und Zertifikat
Änderungsort
DNS-Anbieter und Hosting-Domainverwaltung
Prüfergebnis
Funktionierendes DNS, HTTPS und kanonische Weiterleitung
Sperren, Weitervermittlung, Cache
Änderungsort
CDN-, WAF-, Proxy- und Servercache-Einstellungen
Prüfergebnis
Öffentlicher Inhalt, korrekter Status, Aktualisierungszeitpunkt
Ausführung und Geheimnisse
Änderungsort
package.json, Plattform-Build, Umgebungsvariablen
Prüfergebnis
Produktionsbuild, Serverstart, keine Geheimnisse im Browser
Fehlerprüfung
Änderungsort
Hosting-, CDN- und Anwendungsprotokolle
Prüfergebnis
Fehlerrate, Verarbeitungszeit und Version je Pfad
§ 02SEO / GEO / AEO

Versionen und Beispieldaten festlegen

Das App-Router-Beispiel läuft mit Next.js 16.3.8, React 19.2.5, TypeScript 5.9.3 und Node.js 22. cacheComponents wird in next.config.ts auf false gesetzt, um die folgenden dynamic-Einstellungen zu verwenden. Mit aktivierten Cache Components ist eine separate Konfiguration über use cache, cacheLife und die zugehörigen Optionen erforderlich.

Die Daten bestehen aus einem öffentlichen Bibliothekshinweis und einem Entwurf. Ein festes Array macht Abfragen und Ausgabe ohne Netzwerk oder reale Datenbank nachvollziehbar. Im Betrieb wird das Arraylesen durch eine Server-Datenbankabfrage mit denselben Öffentlichkeitsbedingungen ersetzt.

Speichern Sie die Dateien mit den angegebenen Namen in einem leeren Verzeichnis. Erstellen Sie die Seiten- und Abfragedateien dieses Kapitels sowie die späteren robots- und Sitemap-Dateien, bevor Sie installieren und bauen. Next.js kann die TypeScript-Konfiguration beim ersten Start erzeugen. Vergleichen Sie bei bestehenden Projekten zuerst installierte Versionen und Einstellungen.

Die nächsten drei Blöcke sind package.json, next.config.ts und Terminalbefehle. Verwenden Sie Node.js 22 und behalten Sie nach der Installation package-lock.json. Zur Reproduktion der Umgebung gehören angegebene Versionen und Lockdatei zusammen.

{ "name": "seo-guide-sample", "private": true, "scripts": { "build": "next build --webpack", "start": "next start", "typecheck": "tsc --noEmit" }, "dependencies": { "next": "16.3.8", "react": "19.2.5", "react-dom": "19.2.5" }, "devDependencies": { "typescript": "5.9.3", "@types/node": "^22", "@types/react": "^19", "@types/react-dom": "^19" } }

import type { NextConfig } from 'next' const config: NextConfig = { cacheComponents: false } export default config

npm install npm run build npm run start -- --port 3027

npm install installiert deklarierte Pakete, build erstellt die Produktionsausgabe, start führt sie aus. Die Übung läuft auf http://localhost:3027; example.com ist die exemplarische kanonische Bereitstellungsadresse. Setzen Sie SITE_ORIGIN im Betrieb auf die bestätigte Domain.

§ 03SEO / GEO / AEO

Öffentliche Abfrage und Wiederverwendung innerhalb einer Anfrage implementieren

Die kanonische Domain steht in lib/site.ts, Bedingungen und Abfragen in lib/guides.ts. React cache verwendet Ergebnisse gleicher Argumente während derselben Serverrendering-Anfrage wieder. Die Lebensdauer unterscheidet sich von CDN- und Datencaches über mehrere Besuche.

export const SITE_ORIGIN = 'https://example.com'

import { cache } from 'react' import { describeDocument } from './identity-time.mjs' import { SITE_ORIGIN } from './site' export type Guide = { id: string previousSlugs: string[] publicFrom: string | null slug: string title: string summary: string paragraphs: string[] sourceUrl: string | null validFrom: string | null validUntil: string | null status: 'draft' | 'published' | 'withdrawn' publishedAt: string | null contentModifiedAt: string | null deletedAt: string | null revision: number } // Feste Übungsdaten. Im Betrieb durch Datenbankabfragen ersetzen. const guides: Guide[] = [ { id: 'hours-current', previousSlugs: ['library-hours'], publicFrom: '2026-10-01T00:00:00+09:00', slug: 'opening-hours', title: 'Öffnungszeiten der Bibliothek', summary: 'Sonntagsöffnungszeiten und Schließtage.', paragraphs: [ 'Sonntags von 10 bis 17 Uhr geöffnet.', 'Am ersten Sonntag jedes Monats und an Feiertagen geschlossen. Dieser Zeitplan gilt ab Oktober 2026.', ], sourceUrl: '/policies/opening-hours', validFrom: '2026-10-01T00:00:00+09:00', validUntil: null, status: 'published', publishedAt: '2026-10-01', contentModifiedAt: '2026-10-23T09:00:00+09:00', deletedAt: null, revision: 2, }, { id: 'draft-example', previousSlugs: [], publicFrom: null, slug: 'draft-guide', title: 'Unveröffentlichte Anleitung', summary: 'Entwurf', paragraphs: ['Übungsentwurf, der nicht in öffentlichen Antworten erscheinen darf.'], sourceUrl: null, validFrom: null, validUntil: null, status: 'draft', publishedAt: null, contentModifiedAt: null, deletedAt: null, revision: 1, }, ] function toPublic(g: Guide, asOf: string) { const info = describeDocument({ ...g, path: `/guides/${g.slug}`, eventDate: null }, asOf, SITE_ORIGIN) return info ? { ...g, info } : null } // Inhalt und metadata einer Anfrage lesen dieselbe Zeit und öffentliche Revision. const snapshot = cache(async () => { const asOf = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z') return guides.map(g => toPublic(g, asOf)).filter(g => g !== null) }) export const getPublicGuide = cache(async (slug: string) => { return (await snapshot()).find(g => g.slug === slug || g.previousSlugs.includes(slug)) ?? null }) export const listPublicGuides = snapshot

lib/guides.ts liest feste Daten und berechnet mit describeDocument ID, canonical, Daten, Öffentlichkeit und Gültigkeit. React cache an snapshot verwendet das Ergebnis einer Serveranfrage wieder; getPublicGuide und listPublicGuides teilen es. Der Umfang unterscheidet sich von langfristigen DB- und CDN-Caches. React cache

sourceUrl ist ein tatsächlich erreichbarer Belegpfad und verweist hier auf das unten erzeugte /policies/opening-hours. validFrom und validUntil beschreiben die Gültigkeit des Zeitplans unabhängig von publicFrom als Beginn der Serverfreigabe.

Speichern Sie zunächst identity-time.mjs aus Dokumente und Zeit als lib/identity-time.mjs. guides.ts ruft describeDocument tatsächlich auf. snapshot legt die Anfragezeit einmal fest und liefert öffentliche Texte samt Beschreibung. Auch der alte slug library-hours findet dasselbe Dokument, doch page und generateMetadata rufen permanentRedirect auf und senden 308 nach opening-hours. @id entsteht aus hours-current und bleibt bei Adressänderungen stabil.

SITE_ORIGIN wird auch als drittes Argument an describeDocument übergeben. Kanonische Adresse und stabile @id entstehen aus derselben Einstellung, sodass bei Domainwechseln die Standardbeispieladresse im Datumsmodul nicht separat geändert werden muss.

Das gemeinsame snapshot reduziert doppelte Abfragen und Inhaltswidersprüche. Ändert sich die öffentliche Revision zwischen Titel- und Inhaltsabfrage, könnten verschiedene Fassungen in einer Antwort stehen. Das Beispiel verwendet die innerhalb der Anfrage bestimmte öffentliche Liste wieder, sodass Titel, Inhalt und Datum dieselben Daten beschreiben. Dadurch bleibt die Bedeutung des an die Suche gelieferten Dokuments konsistent.

§ 04SEO / GEO / AEO

Dieselbe öffentliche Revision auf der SSR-Detailseite ausgeben

app/layout.tsx legt Dokumentsprache und Basis-URL fest. app/guides/[slug]/page.tsx liest das öffentliche Material für den slug. force-dynamic wählt anfragebezogenes Rendering in dieser Konfiguration mit deaktivierten Cache Components.

import type { Metadata } from 'next' import type { ReactNode } from 'react' import { SITE_ORIGIN } from '../lib/site' export const metadata: Metadata = { metadataBase: new URL(SITE_ORIGIN) } export default function Layout({ children }: { children: ReactNode }) { return <html lang="de"><body>{children}</body></html> }

import type { Metadata } from 'next' import { notFound, permanentRedirect } from 'next/navigation' import { getPublicGuide } from '../../../lib/guides' import { SITE_ORIGIN } from '../../../lib/site' import { formatContentDate } from '../../../lib/identity-time.mjs' export const dynamic = 'force-dynamic' type Props = { params: Promise<{ slug: string }> } export async function generateMetadata({ params }: Props): Promise<Metadata> { const { slug } = await params const guide = await getPublicGuide(slug) if (!guide) notFound() if (slug !== guide.slug) permanentRedirect(`/guides/${guide.slug}`) const canonical = guide.info.canonical return { title: guide.title, description: guide.summary, alternates: { canonical }, robots: { index: true, follow: true }, openGraph: { title: guide.title, description: guide.summary, url: canonical }, } } export default async function Page({ params }: Props) { const { slug } = await params const guide = await getPublicGuide(slug) if (!guide) notFound() if (slug !== guide.slug) permanentRedirect(`/guides/${guide.slug}`) const canonical = guide.info.canonical const structured = { '@context': 'https://schema.org', '@type': 'WebPage', '@id': guide.info.id, url: canonical, name: guide.title, description: guide.summary, ...(guide.info.datePublished ? { datePublished: guide.info.datePublished } : {}), ...(guide.sourceUrl ? { citation: new URL(guide.sourceUrl, SITE_ORIGIN).href } : {}), ...(guide.info.dateModified ? { dateModified: guide.info.dateModified } : {}), } const format = formatContentDate return <main><article data-revision={guide.revision}> <h1>{guide.title}</h1><p>{guide.summary}</p> {guide.publishedAt && <p data-date="datePublished"><span data-label>Veröffentlicht</span> <time dateTime={guide.publishedAt}> {format(guide.publishedAt)}</time></p>} {guide.contentModifiedAt && <p data-date="dateModified"><span data-label>Geändert</span> <time dateTime={guide.contentModifiedAt}> {format(guide.contentModifiedAt)}</time></p>} <p>Gültigkeit: {({ current: "Aktuell", historical: "Historisch", scheduled: "Geplant", unknown: "Beginn unbekannt" })[guide.info.applicability]}</p> {guide.validFrom && <p>Gültig ab <time dateTime={guide.validFrom}> {format(guide.validFrom)}</time></p>} {guide.validUntil && <p>Gültig bis <time dateTime={guide.validUntil}> {guide.validUntil}</time> (dieser Zeitpunkt ausgeschlossen)</p>} {guide.paragraphs.map((text, i) => <p key={i}>{text}</p>)} {guide.sourceUrl && <p>Beleg: <a href={guide.sourceUrl}>Ursprünglicher Hinweis zu Bibliotheksöffnungszeiten</a></p>} <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structured).replace(/</g, '\\u003c'), }} /> </article></main> }

generateMetadata liefert Titel, Beschreibung und canonical; Page gibt Absätze derselben Daten aus. data-revision kennzeichnet die Ausgabeversion für den Vergleich. Die Anleitung verwendet WebPage. Artikeldienste wählen passende Typen wie Article samt tatsächlichen Autoren, Daten und Bildern.

Fehlen öffentliche Daten, wird notFound aufgerufen. Ladegrenzen oder Streaming können Statuscodes ändern; prüfen Sie deshalb tatsächliche 404-, noindex- und Inhaltsantworten. Fehler der Originalabfrage werden getrennt von fehlenden Artikeln als Serverfehler verfolgt.

Der erste Block ist app/layout.tsx, der zweite app/guides/[slug]/page.tsx. Der angefragte slug geht in die Abfrage; aus demselben guide entstehen canonical, Titel, Inhalt, Daten und Beleglink. format zeigt bestätigte Daten im Format dieser Sprachversion. dateTime bewahrt ursprüngliche Präzision als reines Datum oder Zeitwert mit Zone. contentModifiedAt verändert sichtbares Änderungsdatum und JSON-LD dateModified gemeinsam.

Nach dem Start muss /guides/opening-hours Zeiten, Schließung am ersten Sonntag, Veröffentlichungs- und Änderungsdaten sowie Beleglink zeigen. Fehlen Daten, prüfen Sie null-Werte und time-Zweige. Fehlen Bedingungen trotz JSON-LD im Text, korrigieren Sie paragraphs. Unbekannte slugs und draft-guide müssen 404 und noindex liefern.

Veröffentlichungs- und Änderungszeilen besitzen data-date, Labels data-label. Die Prüfung vergleicht Zeilenbedeutung, time datetime und sichtbares Datum getrennt. info.applicability erscheint ebenfalls, damit ein historischer Eintrag mit neuer Revision nicht aktuell wirkt. Reine publishedAt-Daten zeigt formatContentDate ohne Zeitzonenverschiebung.

Das map für p-Elemente verwandelt Bibliothekszeiten in sichtbares HTML. generateMetadata liefert separat Thema und canonical. Wegen dieser Arbeitsteilung müssen nicht sämtliche Zeiten und Ausnahmen im title stehen. Die Antwort gehört vollständig in den Text, Titel und Beschreibung fassen sie korrekt zusammen. Prüfen Sie nach Änderungen, ob body und head dieselbe Anleitung beschreiben.

§ 05SEO / GEO / AEO

Detailartikel aus einer öffentlichen Liste auffindbar machen

Auch die Liste verwendet dieselben Öffentlichkeitsbedingungen. Link in app/page.tsx erzeugt tatsächliche Detailadressen. Entwürfe dürfen weder in der Liste stehen noch bei direktem Aufruf öffentlichen Inhalt liefern.

import Link from 'next/link' import { listPublicGuides } from '../lib/guides' export const dynamic = 'force-dynamic' export default async function Home() { const guides = await listPublicGuides() return <main><h1>Bibliothekshinweise</h1><ul> {guides.map(g => <li key={g.slug}> <Link href={`/guides/${g.slug}`}>{g.title}</Link> </li>)} </ul></main> }

Durchlaufen Sie guides mit map und erzeugen Sie Link mit echten href-Werten. Speichern Sie als app/page.tsx und öffnen Sie über „Öffnungszeiten der Bibliothek“ die Detailseite. Erscheinen unveröffentlichte Hinweise, prüfen Sie listPublicGuides. Listenweg und Direktaufruf müssen beide geprüft werden, um nur oberflächlich versteckte Entwürfe zu erkennen.

Gibt Link auf der Startseite ein echtes href aus, können Crawler die Detailadresse entdecken. Bei Artikeln, die nur über Suchfeldeingaben oder Klickereignisse öffnen, bleibt der Pfad für Abrufe ohne diese Aktionen möglicherweise unsichtbar. Listenlinks und Sitemap schaffen gemeinsam Wege zur Entdeckung neuer Artikel. Crawlbare Links

§ 06SEO / GEO / AEO

Einen festen historischen Eintrag als statische Seite erstellen

Für den einzelnen Archiveintrag der Übung wird app/archive/opening-2025/page.tsx angelegt. Fester Dateiinhalt ohne Anfrageabfrage ergibt eine statische Seite im Build. /archive/missing hat keine Route und fällt auf die normale 404-Behandlung zurück.

import type { Metadata } from 'next' import { SITE_ORIGIN } from '../../../lib/site' import { describeDocument, example } from '../../../lib/identity-time.mjs' // Erstveröffentlichung und Änderung des Archivs sind unbekannt; keine unbelegten Daten ausgeben. const info = describeDocument({ ...example, publishedAt: null, contentModifiedAt: null }, '2026-10-24T09:00:00+09:00', SITE_ORIGIN)! export const metadata: Metadata = { title: 'Aufzeichnung der Bibliotheksöffnungszeiten 2025', alternates: { canonical: info.canonical }, } export default function Archive() { const structured = { '@context': 'https://schema.org', '@type': 'WebPage', '@id': info.id, url: info.canonical, name: 'Aufzeichnung der Bibliotheksöffnungszeiten 2025' } return <main><article> <h1>Aufzeichnung der Bibliotheksöffnungszeiten 2025</h1> <p>Dieser fiktive Zeitplan galt vom 1. Januar bis 31. Dezember 2025.</p> <p>Damals war sonntags von 11 bis 16 Uhr geöffnet.</p> <p>Gültigkeit: {info.applicability === 'historical' ? 'Historisch' : 'Gültigkeitszeitraum prüfen'}</p> <p>Erstveröffentlichungs- und Änderungsdatum dieses Materials sind unbekannt.</p> <a href="/guides/opening-hours">Aktuelle Öffnungszeiten und Schließtage</a> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structured).replace(/</g, '\\u003c'), }} /> </article></main> }

Der Dateipfad bestimmt die historische URL; metadata erzeugt ein selbstreferenzierendes canonical. Der Inhalt erhält den Zeitraum 2025 und verlinkt die aktuelle Anleitung. publishedAt und contentModifiedAt sind null; entsprechende Datumsanzeigen werden übersprungen.

Prüfen Sie im Build, ob /archive/opening-2025 statisch und /guides/[slug] anfragegeneriert ist. Änderungen an festen Dateien verlangen einen neuen Build. Bei vielen Archiven kann generateStaticParams die Pfade liefern. Prüfen Sie dabei unbekannte slugs, neue Dokumente, Rücknahme und Regenerierung separat. Statische Routen erzeugen

Das Archiv ruft describeDocument mit dem example aus „Dokumente und Zeit“ und unbekannten Veröffentlichungs- und Änderungsdaten auf. Unbekannte Werte fehlen sowohl im Text als auch in JSON-LD. Das Gültigkeitsende ist bekannt; historischer Zustand und eigene Adresse bleiben erhalten. Der feste Stichtag dient hier zur statischen Erzeugung eines bereits abgeschlossenen Eintrags. Noch wechselnde Gültigkeit wird pro Anfrage oder durch Regenerierung am Grenzzeitpunkt behandelt.

§ 07SEO / GEO / AEO

Auch das Belegdokument tatsächlich bereitstellen

Speichern Sie den folgenden Code als app/policies/opening-hours/page.tsx. Die Seite liefert Öffnungszeiten, Schließtage und Gültigkeitsbeginn; sourceUrl der Detailseite verweist hierhin.

import type { Metadata } from 'next' import { SITE_ORIGIN } from '../../../lib/site' export const metadata: Metadata = { title: 'Ursprüngliche Bibliotheksöffnungszeiten — Übungsmaterial', alternates: { canonical: new URL('/policies/opening-hours', SITE_ORIGIN).href }, } export default function Policy() { return <main><article><h1>Ursprüngliche Bibliotheksöffnungszeiten — Übungsmaterial</h1> <p>Öffnungshinweise der fiktiven Bibliothek ab 1. Oktober 2026.</p> <p>Sonntags 10:00–17:00 Uhr. Am ersten Sonntag jedes Monats und an Feiertagen geschlossen.</p> <p>Bekannt gegeben <time dateTime="2026-09-30">30. September 2026</time></p> <a href="/guides/opening-hours">Zurück zur Nutzungsanleitung</a> </article></main> }

sourceUrl führt zu diesem Pfad. Öffnen Sie ihn und vergleichen Sie Zeiten, Ausnahmen und Gültigkeitsbeginn. Ein 200-Link zu einem anderen Jahr oder Standort ist kein passender Beleg. Auch die Quellseite bietet ihre eigene Adresse und einen Rückweg zur Anleitung.

§ 08SEO / GEO / AEO

Gestreamte Metadaten lesen

title, description und canonical der Details verwenden dieselben Daten wie der Inhalt. Der Titel nennt das Thema, die Zusammenfassung Zielgruppe und Informationen. Bei Next.js-Metadatenstreaming kann head nach Abschluss der Abfrage ergänzt werden. Vergleichen Sie Dokumentwerte erst nach Empfang der gesamten Antwort. Next.js-Metadaten

§ 09SEO / GEO / AEO

Absatzarrays, Markdown und Medien behandeln

guide.paragraphs ist ein Array von Absätzen. map verwandelt sie in p-Elemente; key identifiziert React-Listeneinträge. Gewöhnliche Zeichenfolgen in geschweiften Klammern werden als Text verarbeitet. sourceUrl enthält eine geprüfte Belegadresse; öffnen Sie sie zur Bestätigung desselben Materials.

Markdown beschreibt Überschriften, Listen und Links mit einfachen Symbolen. HTML- und Markdown-CMS verwenden Renderer zur Umwandlung gespeicherter Texte in HTML. Externe Eingaben erhalten Regeln für erlaubte Tags, Attribute und URLs gegen Skriptinjektion. Endloslisten benötigen eindeutige URLs und Links zu weiteren Artikeln. Prüfen Sie Entdeckungspfade für Material, das nur nach „Mehr laden“ erscheint.

Bilder erhalten zum Text passende Beschreibungen und Alternativtexte. Wichtige Zeit- und Gebührentabellen dürfen nicht ausschließlich Bilder sein; bieten Sie lesbaren Text oder Tabellen. Bildschirmgerechte Größen sowie Breite und Höhe reduzieren Übertragung und Layoutverschiebung. Videometadaten gehören nur auf Seiten mit tatsächlichem Video; prüfen Sie Wiedergabeadresse, Vorschaubild und Erreichbarkeit.

§ 10SEO / GEO / AEO

Strukturierte Daten aus dem Inhalt ableiten

„Kim Minsu, 24. Oktober“ allein klärt nicht unbedingt, ob die Person Autor oder Gegenstand ist und ob das Datum Veröffentlichung oder Ereignis meint. Strukturierte Daten benennen Beziehungen wie author, about und datePublished. Google nutzt sie zum Inhaltsverständnis und bei unterstützten Typen für erweiterte Suchanzeigen. Aufgabe strukturierter Daten

„Am ersten Sonntag geschlossen“ ist die antwortende Tatsache; publisher und dateModified in JSON-LD beschreiben Herausgeber und Änderungszeit. Aus derselben Vorlage erzeugt, zeigen sie gemeinsam, wer wann welchen Hinweis bereitstellte. WebPage allein erzeugt keine Schließungsbedingung; der Satz wird deshalb in p-Elementen ausgegeben.

Wählen Sie passende Typen, etwa Article für Artikel und WebPage für Nutzungshinweise. article-jsonld.mjs erzeugt sichtbaren Titel, Veröffentlichungs- und Änderungsdaten, Bild und JSON-LD aus einem Artikeldatensatz. Speichern Sie die Datei neben identity-time.mjs. node article-jsonld.mjs gibt JSON aus; vollständiges HTML steht unter /news/new-hours des Bildübungsservers.

import { pathToFileURL } from 'node:url'; import { describeDocument, formatContentDate } from './identity-time.mjs'; export const article = { id: 'new-hours', path: '/news/new-hours', status: 'published', deletedAt: null, title: 'Geänderte Sonntagsöffnungszeiten der Bibliothek', summary: 'Ab Oktober 2026 sonntags 10–17 Uhr geöffnet. Am ersten Sonntag und an Feiertagen geschlossen.', publishedAt: '2026-09-30T09:00:00+09:00', publicFrom: '2026-09-30T09:00:00+09:00', contentModifiedAt: '2026-10-01T09:00:00+09:00', eventDate: null, validFrom: '2026-10-01T00:00:00+09:00', validUntil: null, }; export const escapeHtml = value => String(value).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]); export function renderArticle(origin, image, now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z')) { const identity = describeDocument(article, now, origin); if (!identity) throw new Error('Article is not public'); // image erhält dieselbe öffentliche Bild-URL und tatsächliche Umwandlungsmaße. const imageUrl = new URL(image.path, origin).href; const structured = { '@context': 'https://schema.org', '@type': 'Article', '@id': identity.id, headline: article.title, mainEntityOfPage: identity.canonical, datePublished: identity.datePublished, dateModified: identity.dateModified, image: [imageUrl], author: { '@type': 'Organization', name: 'Redaktion der fiktiven Bibliothek' }, publisher: { '@type': 'Organization', name: 'Fiktive Bibliothek' }, }; const h = escapeHtml; const json = JSON.stringify(structured).replace(/</g, '\\u003c'); const html = `<!doctype html><html lang="de"><head><meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>${h(article.title)}</title><link rel="canonical" href="${h(identity.canonical)}"> <meta property="og:image" content="${h(imageUrl)}"> <script type="application/ld+json">${json}</script></head> <body><article><h1>${h(article.title)}</h1> <p>Veröffentlicht <time datetime="${h(identity.datePublished)}">${formatContentDate(identity.datePublished)}</time> · Geändert <time datetime="${h(identity.dateModified)}">${formatContentDate(identity.dateModified)}</time></p> <p>${h(article.summary)}</p><figure> <img src="${h(imageUrl)}" width="${image.width}" height="${image.height}" style="max-width:100%;height:auto" alt="Übungsbild für Bibliothekshinweise"> <figcaption>Beispiel zum Vergleich von Bildauslieferungswegen.</figcaption> </figure><p>Redaktion der fiktiven Bibliothek</p></article></body></html>`; return { structured, html }; } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { console.log(JSON.stringify(renderArticle('https://example.com', { path: '/media/photo?w=1280', width: 1280, height: 800, }).structured, null, 2)); }

@context bezeichnet die Vokabularadresse, @type den Dokumenttyp, headline den Titel und mainEntityOfPage die repräsentierende Seite. datePublished ist die Veröffentlichungszeit, dateModified die Inhaltsänderung. Article steht für Artikel, NewsArticle für Nachrichten, SocialMediaPosting für soziale Beiträge, VideoObject für Video. Wählen Sie Typen und Eigenschaften entsprechend dem tatsächlichen Format.

JSON-LD wird als application/ld+json-Skript ausgegeben. Serialisieren Sie enthaltene Nutzereingaben sicher, damit keine schließende script-Zeichenfolge eingeschleust wird. Das Next.js-JSON-LD-Beispiel zeigt die Ersetzung des Zeichens < in der serialisierten Zeichenfolge durch einen Unicode-Escape. Prüfen Sie anschließend JSON-Parsing und Übereinstimmung mit dem Inhalt.

<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structured).replace(/</g, '\\u003c') }} />

Prüfen Sie Typen und Eigenschaften sowie Titel, Autor und Daten gegen den Seitentext. Meldet das Suchwerkzeug Fehler, korrigieren Sie Wert und Datentyp der betroffenen Eigenschaft. Anforderungen an strukturierte Daten

JSON.stringify macht ein Objekt zur JSON-Zeichenfolge; replace ersetzt Zeichen. Der Unicode-Escape \u003c stellt in JSON das Zeichen < dar. dangerouslySetInnerHTML fügt Zeichenfolgen in React direkt als HTML ein. Übergeben Sie hier ausschließlich sicher serialisierte strukturierte Daten.

renderArticle prüft Öffentlichkeit und Daten mit describeDocument und erzeugt eine absolute Bildadresse für img, og:image und Article.image. escapeHtml behandelt Sonderzeichen in Attributen und Text. Die JSON-Ersetzung von < verhindert, dass ein script-Ende als HTML interpretiert wird. Der nachfolgende JSX-Block nutzt dieselbe Serialisierung in einer React-Seite.

Tatsächliche Bildbreite und -höhe reservieren in img Platz, bevor die Datei eintrifft. Veröffentlichung und Änderung haben eigene Labels; die Gültigkeit steht im Artikeltext. Damit werden Bild- und Datumsbeziehungen für die Suche angegeben und Belege der Änderung für Leser bereitgestellt. Googles Article-Anleitung empfiehlt ein zum Artikel passendes, crawlbares Hauptbild.

Für eine kleine Karte können 320px genügen, während ein Such-Hauptbild mehr Detailauflösung braucht. Die Übung zeigt die Beziehung mit derselben 1280px-Variante. Prüfen Sie in echten Artikeln Bildinhalt und Beschnitt und erstellen Sie bei Bedarf Formate wie 1:1, 4:3 und 16:9. Verwenden Sie nicht standardmäßig Szenen außerhalb des Textes oder Bilder anderer Artikel.

— FIN —