000%

Aktualisiert

NOTES · SEO / GEO / AEO07 · 2026.10

Der Server für öffentliche Texte und Daten

Der Server prüft Veröffentlichungsstatus, Zeitpunkt und Revision, bevor er Material zurückgibt. Verbinden Sie Abfragen und Veröffentlichung so, dass Inhalt, Metadaten und Suchmaterial dieselbe veröffentlichte Fassung verwenden.

§ 01SEO / GEO / AEO

Öffentliche Texte auf Server, in Datenbank und CMS speichern und abfragen

Angenommen, die Anmeldefrist wird vom 5. auf den 6. November geändert, aber nur im Inhalt; die Suchzusammenfassung nennt weiter den 5. Such- und Antwortwerkzeuge erhalten dann je nach gelesenem Abschnitt unterschiedliche Fristen. Eine festgelegte öffentliche Revision, aus der Inhalt, Zusammenfassung und Daten gemeinsam entstehen, reduziert solche Widersprüche. Die Speicher- und Veröffentlichungsstruktur des CMS bestimmt die extern vermittelten Fakten.

Server bezeichnet das Programm, das Netzwerkanfragen beantwortet, und die ausführende Hardware. Ein Webserver liefert Dateien oder leitet Anfragen an eine Anwendung; ein Anwendungsserver führt Logik wie Datenabfragen und HTML-Erzeugung aus. Bei verwaltetem Hosting kann die Plattform beide Rollen übernehmen.

Eine Datenbank speichert Artikel, Veröffentlichungszustand, Daten und Beziehungen. Ein CMS (Content Management System) ermöglicht deren Bearbeitung und Veröffentlichung. Das Datenbankschema definiert Felder, Typen und Beziehungen. SQL dient zum Abfragen und Ändern relationaler Datenbanken; ein ORM verbindet Codeobjekte mit Datensätzen. Unabhängig vom Werkzeug gehören Veröffentlichungsstatus und -zeit in die Abfragebedingungen.

Ein Datenbankindex ist eine Struktur zum schnellen Finden von Zeilen und unterscheidet sich vom Suchmaschinenindex. Wählen Sie Indizes anhand tatsächlicher Ausführungspläne für Abfragen nach slug oder Status und Veröffentlichungsdatum. Unnötige vollständige Abfragen auf jeder Seite verlängern SSR-Antworten und erhöhen die Datenbanklast.

Eine Transaktion lässt mehrere Datenbankänderungen gemeinsam gelingen oder zurückrollen. Eine Revision ist eine Textversion. Werden Inhalt, Titel und Datum beim Wechsel der öffentlichen Revision gemeinsam umgeschaltet, sinkt das Risiko unterschiedlicher Fassungen in Oberfläche und Metadaten. Übertragungen an externe Suchspeicher werden als separate Aufgaben protokolliert und bei Fehlern wiederholt.

Authentifizierung prüft die Identität, Autorisierung die erlaubte Nutzung von Daten. Eine Sitzung hält den Anmeldestatus; ein Cookie speichert einen Wert, den der Browser dem Server zurücksenden kann. Öffentliche Artikel sind ohne Anmeldung lesbar, Entwürfe und Verwaltungsabfragen werden serverseitig auf Identität und Rechte geprüft. Liefert dieselbe URL nutzerspezifische Inhalte, dürfen diese nicht in gemeinsamen Caches vermischt werden.

§ 02SEO / GEO / AEO

Öffentlichkeit an einer Stelle bestimmen

Wenden Sie Veröffentlichungsbedingungen in der serverseitigen Abfrageschicht an. Der folgende TypeScript-Code importiert isPublic aus identity-time.mjs in Dokumente und Zeit. Speichern Sie die Datei unter lib/identity-time.mjs. Date.parse kann den 30. Februar auf ein anderes Datum normalisieren; eine erfolgreiche Zahlenumwandlung allein validiert daher kein Kalenderdatum.

import { isPublic } from './identity-time.mjs' type Post = { status: 'draft' | 'published' | 'withdrawn' publishedAt: string | null // Bekannte Erstveröffentlichung: Date oder DateTime publicFrom: string | null // Genauer Beginn des öffentlichen Zugriffs auf diesem Server deletedAt: string | null } export function publicPosts(posts: Post[], now = Date.now()) { return posts.filter(post => isPublic(post, now)) }

type Post definiert Eingabefelder und Typen. publishedAt ist das erste Veröffentlichungsdatum; möglicherweise ist nur der Tag bekannt oder das Datum unbekannt. publicFrom ist der genaue Beginn des öffentlichen Zugriffs auf diesem Server. Die Trennung bewahrt das historische Datum bei einem heutigen Import. isPublic prüft Datumsformate, veröffentlichten Status, fehlende Löschung und das Erreichen von publicFrom. Ungültige Eingaben werden als Fehler gemeldet, gültige Entwürfe aus Listen ausgeschlossen.

Definieren Sie die Bedingungen etwa in src/lib/public-content.ts und verwenden Sie sie mit gleicher Bedeutung für Listen, Details, Sitemap und öffentliche API. Bei Datenbankfilterung gelten dieselben SQL-Bedingungen. Übertragen Sie nicht alle Daten zur Filterung an den Client. Bei erneuter Veröffentlichung, Rücknahme oder Terminierung müssen Cache-Updates Listen und Details nach dem Zustandswechsel gemeinsam ändern.

Nach der Öffentlichkeitsprüfung folgt die Suchaufnahmepolitik. Wenn öffentliche Artikel suchbar sein sollen, lesen Listen, Details und Sitemap dieselbe veröffentlichte Revision. Verwaltungsansichten und Entwürfe prüfen serverseitig Anmeldung und Leseberechtigung.

Gemeinsame Bedingungen gleichen der Suche gemeldete Adressen mit tatsächlich lesbaren Artikeln ab. Verlinkt die Liste einen als öffentlich bewerteten Artikel, während das Detail ihn als privat behandelt und 404 liefert, führt die Entdeckung nicht zum Dokument. Dasselbe gilt für zu früh in der Sitemap auftauchende geplante Artikel. isPublic lässt die Pfade anhand gleicher Zeit- und Statuskriterien antworten.

§ 03SEO / GEO / AEO

Inhalt und abgeleitete Daten gemeinsam aus der veröffentlichten Revision erzeugen

Trennen Sie im CMS Arbeitstext und aktuell öffentlichen Text. Detailseiten, Metadaten und Sitemap lesen die veröffentlichte Revision, die Verwaltungsvorschau den Entwurf. Entwurfsänderungen dürfen nicht bei jeder Anfrage derselben URL in die öffentliche Ansicht gelangen.

content_id identifiziert beispielsweise das Dokument, revision die Inhaltsfassung. Erzeugen Sie Inhalt, Titel, Zusammenfassung, Quellen, Vorschaubild und Suchdaten aus der zu veröffentlichenden Revision und schalten Sie dann die aktive Fassung um. Änderungen derselben Datenbank können transaktional erfolgen. Bei externem Suchspeicher wird mit der Veröffentlichung eine Synchronisierungsaufgabe protokolliert; angewendete Revision, Fehler und Wiederholungsstatus werden getrennt gespeichert.

Interne Suche und Website-Chatbot erhalten Inhalt und Quellen der veröffentlichten Fassung. Suchen Sie unmittelbar nach Veröffentlichung denselben Satz, um den neuen Inhalt zu prüfen. Zurückgezogene Artikel müssen auch aus Suchergebnissen und dem für den Chatbot verfügbaren Material verschwinden.

Ändern Sie bei Rücknahme die Verfügbarkeit von Detailtext, Listen, Sitemap, verwandten Artikeln, interner Suche und Chatbot gemeinsam und invalidieren Sie zugehörige Caches. Prüfen Sie bei Wiederherstellung, ob Quellen und Medien der alten Fassung heute noch öffentlich sein dürfen. Abgelehnte oder gesperrte Originale dürfen nicht durch Wiederherstellung alter Revisionen erneut sichtbar werden.

Speichern Sie Anfrage-ID und erwartete Revision zur Unterscheidung von Wiederholungen. Schaltet publish-42 Revision 7 auf 8 um und scheitert nur die Antwortübertragung, liefert ein erneutes publish-42 die bereits erzeugte Revision 8. Hat eine andere Anfrage Revision 8 verändert, führt ein Speichern mit Erwartungswert 7 zum Konflikt. So erzeugen Netzwerkwiederholungen keine doppelte Veröffentlichung und überschreiben keine andere Bearbeitung.

Ruft ein eigenes RAG weiter die alte Fassung ab, kann der Chatbot trotz korrigierter Oberfläche eine alte Frist nennen. Die gespeicherte Veröffentlichungsrevision im Suchmaterial macht die verwendete Fassung nachvollziehbar. Vergleichen Sie die Revision des Chunks mit der öffentlichen Seite und korrigieren Sie bei Abweichungen die Synchronisierung. Externe Such- und KI-Speicher werden über erneute Abrufe und Verarbeitungsergebnisse des jeweiligen Dienstes geprüft.

§ 04SEO / GEO / AEO

Fehler zwischen öffentlicher Umschaltung und externem Update behandeln

Die Umschaltung der Datenbankrevision und die Aktualisierung eines externen CDN geschehen in verschiedenen Systemen. Ändert sich nur die Datenbank und scheitert der Update-Aufruf, bleiben manche Pfade veraltet. Die Veröffentlichungstransaktion kann zugleich eine Aufgabe mit Dokument-ID, Version und Zielpfaden speichern, deren Verarbeitung Erfolg und Wiederholungen protokolliert.

Kommt dieselbe Veröffentlichungsaufgabe erneut, prüfen Dokument-ID und erwartete Version, ob sie schon angewendet wurde. Änderungen schalten auf eine neue Revision; Rücknahme beendet die Rückgabe auf sämtlichen Abfragepfaden. Bei Caches sind auch Wege zu untersuchen, die gespeicherte Antworten vor einer Ursprungsabfrage liefern.

Übergeben Sie parallel erzeugtem Inhalt und Metadaten den öffentlichen Text aus derselben Anfrage. Auch zwei Aufrufe derselben Funktion können unterschiedliche Revisionen lesen, wenn jeder die Datenbank abfragt und dazwischen veröffentlicht wird. Das folgende Beispiel verwendet Abfrageergebnisse innerhalb einer Anfrage wieder und behandelt langfristiges Caching separat.

§ 05SEO / GEO / AEO

Dokumentfassungen und zeitliche Gültigkeit der Fakten gemeinsam erhalten

Die Inhalts-ID bezeichnet die Dokumentidentität, revision die bearbeitete Fassung, publishedAt die erste Veröffentlichung und contentModifiedAt die inhaltliche Änderung. Bestimmen Sie die aktive Revision, bevor Inhalt, metadata und Listen dieselben Daten erhalten. Überschreiben Sie deren Änderungsdatum nicht mit updatedAt eines gespeicherten Entwurfs.

Wird 2026 ein Tippfehler in einem Eintrag von 2025 korrigiert, steigt revision, während der Gültigkeitszeitraum 2025 bleibt. Dokumente und Zeit speichert diese Unterscheidung als Veröffentlichungszustand und Gültigkeitszeitraum.

§ 06SEO / GEO / AEO

Veröffentlichung, Änderung, Rücknahme und Wiederherstellung in einem kleinen CMS ausführen

Speichern Sie lifecycle.mjs und lifecycle.test.mjs neben identity-time.mjs und starten Sie node --test lifecycle.test.mjs. Die Daten bleiben im Arbeitsspeicher; ein lokaler HTTP-Server liefert JSON. /list liefert die Liste, /sitemap die Sitemap-Adressen, /guides/opening-hours die Details.

// Node.js 22. Eigenständige CMS-Speicherübung; Daten verschwinden beim Beenden. import { isPublic, describeDocument } from './identity-time.mjs'; export function createLibrary() { let draft = { id: 'hours-current', path: '/guides/opening-hours', revision: 0, title: 'Öffnungszeiten der Bibliothek', text: 'Sonntags 10–17 Uhr. Am ersten Sonntag und an Feiertagen geschlossen.', status: 'draft', deletedAt: null, publishedAt: null, publicFrom: null, contentModifiedAt: null, eventDate: null, validFrom: '2026-10-01T00:00:00+09:00', validUntil: null }; let active = structuredClone(draft); const history = [], cache = new Map(); function saveDraft(text) { draft = { ...draft, text }; } function change(action, at, { purge = true, revision } = {}) { if (!['publish', 'withdraw', 'restore'].includes(action)) throw new Error('Invalid action'); const selected = action === 'restore' ? history.find(x => x.revision === revision) : draft; if (!selected) throw new Error('Unknown revision'); const next = action === 'withdraw' ? { ...active, status: 'withdrawn' } : { ...selected, status: 'published', publishedAt: active.publishedAt ?? at, publicFrom: at, contentModifiedAt: active.publishedAt === null ? null : at }; next.revision = active.revision + 1; describeDocument(next, at); // Nach vollständiger Eingabeprüfung auf einmal umschalten. active = next; history.push(structuredClone(next)); draft = structuredClone(next); if (purge) cache.clear(); return next.revision; } function read(path, at, { bypass = false } = {}) { // Rücknahme prüft in diesem Modell den aktuellen Status bei Detailanfragen erneut. if (path === active.path && !isPublic(active, Date.parse(at))) return { status: 404, body: null }; if (!bypass && cache.has(path)) return structuredClone(cache.get(path)); const info = describeDocument(active, at); const body = path === '/list' ? (info ? [{ id: active.id, title: active.title, revision: active.revision }] : []) : path === '/sitemap' ? (info ? [info.canonical] : []) : path === active.path && info ? { ...active, ...info } : null; const result = { status: body === null ? 404 : 200, body }; if (!bypass) cache.set(path, result); return structuredClone(result); } return { saveDraft, change, read, clearCache: () => cache.clear() }; }

saveDraft ändert nur den Entwurf. change wählt Veröffentlichung, Rücknahme oder Wiederherstellung und weist vollständig datumsvalidierte Daten auf einmal active zu. history bewahrt Kopien älterer öffentlicher Revisionen. Wiederherstellung veröffentlicht alten Inhalt als neue Revision und erhält das Erstveröffentlichungsdatum; für den geänderten öffentlichen Inhalt wird die aktuelle Änderungszeit vermerkt.

read erzeugt Detail, Liste und Sitemap aus demselben active. Detailanfragen prüfen vor dem Cachezugriff erneut den aktuellen öffentlichen Status. Standardmäßig leert change alle Übungscaches. Reale Datenbanken und CDNs ändern sich nicht atomar im selben Speicher; der Betrieb braucht deshalb Fehlerprotokolle und Wiederholungen für Cache-Aktualisierungen.

import test from 'node:test'; import assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { createLibrary } from './lifecycle.mjs'; const at = '2026-10-24T09:00:00+09:00'; const later = '2026-10-24T10:00:00+09:00'; test('Entwurf→Veröffentlichung→Änderung→Rücknahme→Wiederherstellung per HTTP vergleichen', async () => { const cms = createLibrary(); let clock = at; const server = createServer((req, res) => { const result = cms.read(req.url, clock); res.writeHead(result.status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(result.body)); }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const origin = `http://127.0.0.1:${server.address().port}`; const rows = []; async function inspect(state, revision) { const responses = await Promise.all(['/guides/opening-hours', '/list', '/sitemap'].map(async path => { const r = await fetch(origin + path); return { path, status: r.status, body: await r.json() }; })); const [detail, list, sitemap] = responses; assert.equal(detail.status, revision === null ? 404 : 200); assert.equal(list.body.length, revision === null ? 0 : 1); assert.equal(sitemap.body.length, revision === null ? 0 : 1); if (revision !== null) { assert.equal(detail.body.revision, revision); assert.equal(list.body[0].revision, revision); assert.equal(sitemap.body[0], detail.body.canonical); } rows.push({ state, responses }); return detail.body; } try { await inspect('draft', null); cms.change('publish', at); const first = await inspect('published', 1); cms.saveDraft('Sonntags 10–16 Uhr. Am ersten Sonntag und an Feiertagen geschlossen.'); assert.equal((await inspect('draft edit remains private', 1)).text, first.text); clock = later; cms.change('publish', later); const revised = await inspect('revised', 2); assert.match(revised.text, /10–16/); assert.equal(revised.publishedAt, at); cms.change('withdraw', later); await inspect('withdrawn', null); cms.change('restore', later, { revision: 1 }); const restored = await inspect('restored', 4); assert.equal(restored.text, first.text); assert.equal(restored.publishedAt, at); assert.equal(restored.contentModifiedAt, later); console.log(JSON.stringify(rows)); } finally { await new Promise(resolve => server.close(resolve)); } }); test('Änderungs- und Rücknahmefehler ohne Invalidierung reproduzieren, dann alle Caches leeren', () => { const cms = createLibrary(); cms.change('publish', at); for (const path of ['/guides/opening-hours', '/list', '/sitemap']) cms.read(path, at); cms.saveDraft('Neue Öffnungszeiten'); cms.change('publish', later, { purge: false }); assert.notEqual(cms.read('/guides/opening-hours', later).body.text, cms.read('/guides/opening-hours', later, { bypass: true }).body.text); cms.change('withdraw', later, { purge: false }); assert.equal(cms.read('/guides/opening-hours', later).status, 404); assert.equal(cms.read('/list', later).body.length, 1); // Fälschlich verbliebene Liste assert.equal(cms.read('/sitemap', later).body.length, 1); cms.clearCache(); assert.deepEqual(cms.read('/list', later).body, []); assert.deepEqual(cms.read('/sitemap', later).body, []); });

Der erste Test beginnt mit leerer Liste und Detail-404 und prüft danach 200 bei Veröffentlichung, private Entwurfsänderungen, neuen öffentlichen Inhalt, 404 sowie leere Liste und Sitemap nach Rücknahme und die Wiederherstellung alten Inhalts. Der zweite injiziert mit purge: false einen Fehler: Das Detail ist zurückgezogen, Liste und Sitemap liefern jedoch alte Caches. Nach clearCache werden beide auf leere Ergebnisse geprüft, um den Korrekturumfang zu bestätigen.

Dass der Listencache nach Rücknahme Links liefert, entspricht einem Crawler, der weiterhin nicht mehr angebotene Artikel entdeckt. Verschwinden die Adressen nach dem Leeren aus Liste und Sitemap, sind neu bereitgestellte Entdeckungspfade korrigiert. Bereits extern gespeichertes Material wird in späteren Abrufprotokollen geprüft. Öffentlichkeitsprüfung im Code und externe Aktualisierung finden somit an verschiedenen Stellen statt.

§ 07SEO / GEO / AEO

Bildumwandlungen erst nach Öffentlichkeitsprüfung wiederverwenden

Die Foto-ID im CMS ist der Schlüssel zu Dateipfad und öffentlichem Status. Ein reales CMS prüft, ob ein aktuell öffentlicher Artikel das Bild referenziert, der Freigabezeitpunkt erreicht und das Bild nicht zurückgezogen ist. Wird nur der Text zurückgezogen, die Originaladresse aber offengelassen, können Suche, Teilen und direkte Anfragen die Datei weiterhin erreichen. Original- und Variantenpfade benötigen dieselbe Öffentlichkeitsprüfung.

Die folgende media-store.mjs hält ein Foto und seinen Status im Speicher. Nach der Öffentlichkeitsprüfung sucht sie Umwandlungen anhand von Originalhash, Breite und WebP-Einstellungen. Gleichzeitige Anfragen derselben Breite warten auf dasselbe Promise in einer Map. Nach der Umwandlung werden Status und Generationsnummer erneut geprüft, sodass während des Wartens zurückgezogene Anfragen blockiert werden.

import sharp from 'sharp'; import { createHash } from 'node:crypto'; // Übungsspeicher für ein Bild. Originale und Umwandlungen nicht im public-Verzeichnis speichern. export async function createMediaStore(input) { const source = Buffer.from(input); if (source.length > 20 * 1024 * 1024) throw new Error('Image exceeds 20 MiB'); const options = { limitInputPixels: 40_000_000 }; const meta = await sharp(source, options).metadata(); if (!['jpeg', 'png', 'webp'].includes(meta.format) || (meta.pages ?? 1) !== 1) throw new Error('Single JPEG, PNG or WebP required'); const hash = createHash('sha256').update(source).digest('hex'); const variants = new Map(); let published = true; let generation = 0; let transforms = 0; const headers = { 'Cache-Control': 'private, no-store, max-age=0' }; const missing = () => ({ status: 404, headers, body: Buffer.from('Not found') }); return { setPublished(value) { if (typeof value !== 'boolean') throw new Error('Boolean required'); published = value; generation++; }, stats: () => ({ transforms, variants: variants.size }), async read(width = null) { if (!published) return missing(); const observedGeneration = generation; if (width === null) return { status: 200, headers: { ...headers, 'Content-Type': 'image/' + meta.format }, body: source, }; if (![320, 640, 1280].includes(width)) return { status: 400, headers, body: Buffer.from('Unsupported width') }; const key = `${hash}:${width}:webp-q78-v1`; if (!variants.has(key)) { transforms++; const pending = sharp(source, options).rotate() .resize({ width, withoutEnlargement: true }).webp({ quality: 78 }) .toBuffer({ resolveWithObject: true }); variants.set(key, pending); pending.catch(() => variants.delete(key)); } const result = await variants.get(key); // Auch während der Umwandlung zurückgezogene oder neu veröffentlichte Anfragen stoppen. if (!published || generation !== observedGeneration) return missing(); return { status: 200, headers: { ...headers, 'Content-Type': 'image/webp' }, body: result.data, width: result.info.width, height: result.info.height }; }, }; }

Buffer.from kopiert Eingabebytes; der SHA-256-Hash identifiziert den Originalinhalt. rotate berücksichtigt die Orientierung. withoutEnlargement in resize verhindert das Vergrößern kleiner Originale. info von toBuffer liefert tatsächliche Ausgabebreite und -höhe. Verwenden Sie diese in srcset, damit ein kleines Original nicht fälschlich als 1280w bezeichnet wird. Größenänderung mit sharp

setPublished(false) bedeutet in der Übung Rücknahme. Ein Serverneustart löscht Original und Varianten. Ersetzen Sie bei CMS-Anbindung published durch eine Abfrage der öffentlichen Revision und lagern Sie Varianten ohne direkten externen Zugriff. Mehrere Server müssen denselben Veröffentlichungszustand lesen. Eingabegröße, Pixelzahl und Format werden im Code geprüft; Zeit- und Parallelitätsgrenzen ergänzen Sie in der Upload- und Umwandlungsumgebung.

Das Beispiel gibt auch die Originaladresse frei. Entscheiden Sie vorher, ob eingebettete Orts- und Aufnahmeinformationen ebenfalls bereitgestellt werden sollen. Bietet der Dienst nur metadatenbereinigte öffentliche Varianten an, beschränken Sie den Originalpfad auf Administratoren. Bereits übertragene Dateien oder laufende Antworten lassen sich durch spätere Rücknahme nicht zurückholen. Trennen Sie Bedingungen neuer Zugriffe vom Umgang mit bereits verteiltem Material.

Führen Sie diese Datei zusammen mit dem Server der Bildübung aus. Die Prüfungen im Verifikationskapitel reproduzieren Rücknahme während der Umwandlung und erneute Anfragen gespeicherter Varianten.

— FIN —