Strukturierte Daten, Templates und das Modell – wie skalierbare Seiten entstehen, die trotzdem ranken
SerieGoogle AI / Gemini im Freelancer-Alltag
Teil 11 von 16
Programmatic SEO funktioniert, wenn drei Dinge zusammenpassen: ein solides Template, strukturierte Daten und ein Modell, das daraus menschlich lesbare Inhalte macht. Dieser Artikel zeigt, wie Gemini in diesen Prozess eingebaut wird – von der Datenaufbereitung über das Prompt-Template bis zur automatischen Seitengeneration, die sich in bestehende Astro- oder Next.js-Projekte einfügt. Skalierbares Ranking ohne Qualitätsverlust ist kein Widerspruch, wenn der Prozess sauber aufgebaut ist.
Was Programmatic SEO ist und wann es funktioniert
Programmatic SEO bezeichnet die automatisierte Erstellung von Seiten auf Basis strukturierter Daten. Statt jede Seite einzeln zu schreiben, definiert man einmal ein Template und generiert daraus hunderte oder tausende Seiten – je eine pro Datensatz.
Das funktioniert hervorragend bei Themen mit klarer Struktur und hohem Wiederholungspotenzial:
- Lokale Landingpages (“Klempner in [Stadt]”)
- Produkt-Vergleichsseiten (“[Produkt A] vs. [Produkt B]”)
- Kategorie- und Filter-Kombinationen für E-Commerce
- FAQ-Seiten auf Basis von Datensätzen
- Stellenbörsen, Immobilienportale, Rezeptseiten
Google wertet diese Inhalte als Spam, wenn sie inhaltlich identisch sind und keinen echten Mehrwert bieten. Der entscheidende Unterschied liegt darin, ob jede generierte Seite einzigartigen, nützlichen Content hat – oder ob nur der Stadtname ausgetauscht wird.
Genau hier kommt Gemini ins Spiel: Das Modell verwandelt strukturierte Daten in natürlich klingende, einzigartige Beschreibungen.
Die drei Bausteine
Programmatic SEO mit Gemini basiert auf drei klar getrennten Komponenten.
Die Trennung ist wichtig: Strukturierte Daten, Template und Build-Prozess ändern sich unabhängig voneinander. Neue Städte hinzufügen bedeutet, die CSV zu erweitern – nicht das Template oder den Build-Prozess anzufassen.
Daten vorbereiten
Welches Datenformat ist besser, CSV oder JSON? JSON ist zu bevorzugen, weil es hierarchische Daten sauber abbildet und direkt in JavaScript-Builds eingebunden werden kann.
Beispiel: Städte-Daten für lokale Handwerker-Landingpages
[
{
"slug": "muenchen",
"city": "München",
"state": "Bayern",
"population": 1512491,
"districts": ["Schwabing", "Maxvorstadt", "Neuhausen", "Bogenhausen"],
"landmarks": ["Marienplatz", "Englischer Garten", "Olympiapark"],
"region_description": "Landeshauptstadt mit hoher Bevölkerungsdichte und stark nachgefragtem Handwerkermarkt",
"avg_response_time_hours": 2.5,
"coverage_radius_km": 30
},
{
"slug": "nuernberg",
"city": "Nürnberg",
"state": "Bayern",
"population": 515543,
"districts": ["Altstadt", "Gostenhof", "Langwasser", "Gibitzenhof"],
"landmarks": ["Kaiserburg", "Hauptmarkt", "Christkindlesmarkt"],
"region_description": "Zweitgrößte Stadt Bayerns, starke Industrie- und Dienstleistungsregion",
"avg_response_time_hours": 1.8,
"coverage_radius_km": 25
}
]
Die Spalten, die sich pro Datensatz unterscheiden, werden zu Platzhaltern im Prompt-Template. Je mehr einzigartige Daten pro Datensatz vorliegen, desto einzigartiger wird der generierte Content.
Das Prompt-Template aufbauen
Das Template ist der Kern des Systems. Es enthält die fixen Bestandteile (Ton, Format, Einschränkungen) und die variablen Platzhalter.
# System Instruction
Du bist Texter für einen regionalen Handwerksbetrieb.
Schreibe natürliche, informative Texte für lokale Landingpages.
Regeln:
- Kein Marketing-Sprech ("die besten", "günstigsten", "kompetentesten")
- Konkrete Fakten aus den gelieferten Daten verwenden
- Ortsbezüge müssen natürlich wirken, nicht aufgesetzt
- Tonalität: sachlich, vertrauenswürdig, lokal verankert
- Keine Wiederholungen innerhalb des Textes
# User Prompt Template
Erstelle eine Landingpage-Beschreibung für:
Stadt: {{city}}, {{state}}
Einwohner: {{population}}
Stadtteile: {{districts | join(", ")}}
Bekannte Orte: {{landmarks | join(", ")}}
Regionale Besonderheit: {{region_description}}
Reaktionszeit in der Region: {{avg_response_time_hours}} Stunden
Versorgungsradius: {{coverage_radius_km}} km
Schreibe:
1. Intro-Absatz (3-4 Sätze): Warum dieser Betrieb in {{city}} tätig ist, mit Bezug auf die Stadt
2. Leistungs-Absatz (3-4 Sätze): Konkrete Verfügbarkeit und Reaktionszeit
3. Stadtteile-Absatz (2-3 Sätze): Abgedeckte Stadtteile natürlich einweben
Kein JSON. Nur die drei Absätze als Fließtext, getrennt durch Leerzeilen.
Das Template mit Platzhaltern in doppelten geschweiften Klammern ist absichtlich einfach gehalten. In Python werden diese Platzhalter vor dem API-Aufruf durch die echten Daten ersetzt.
Integration in Astro
Astro mit Content Collections ist die sauberste Lösung für programmatisch generierten Content. Das Skript liest die JSON-Daten, ruft Gemini auf und schreibt MDX-Dateien.
Skript: scripts/generate-city-pages.ts
import * as fs from "fs";
import * as path from "path";
import { GoogleGenerativeAI } from "@google/generative-ai";
const genai = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const SYSTEM_INSTRUCTION = `Du bist Texter für einen regionalen Handwerksbetrieb.
Schreibe natürliche, informative Texte für lokale Landingpages.
Kein Marketing-Sprech. Konkrete Fakten. Sachlich und vertrauenswürdig.`;
interface CityData {
slug: string;
city: string;
state: string;
population: number;
districts: string[];
landmarks: string[];
region_description: string;
avg_response_time_hours: number;
coverage_radius_km: number;
}
async function generateCityContent(city: CityData): Promise<string> {
const model = genai.getGenerativeModel({
model: "gemini-2.5-pro",
systemInstruction: SYSTEM_INSTRUCTION,
generationConfig: { temperature: 0.4 },
});
const prompt = `
Erstelle eine Landingpage-Beschreibung für:
Stadt: ${city.city}, ${city.state}
Einwohner: ${city.population.toLocaleString("de-DE")}
Stadtteile: ${city.districts.join(", ")}
Bekannte Orte: ${city.landmarks.join(", ")}
Regionale Besonderheit: ${city.region_description}
Reaktionszeit in der Region: ${city.avg_response_time_hours} Stunden
Versorgungsradius: ${city.coverage_radius_km} km
Schreibe drei Absätze: Intro (3-4 Sätze), Verfügbarkeit (3-4 Sätze), Stadtteile (2-3 Sätze).
Nur die drei Absätze als Fließtext, getrennt durch Leerzeilen.`;
const result = await model.generateContent(prompt);
return result.response.text();
}
function buildMDX(city: CityData, content: string): string {
return `---
title: 'Klempner in ${city.city}'
description: 'Professioneller Klempnerservice in ${city.city} und Umgebung. Reaktionszeit ca. ${city.avg_response_time_hours} Stunden.'
city: '${city.city}'
slug: '${city.slug}'
draft: false
---
${content}
`;
}
async function main() {
const citiesData: CityData[] = JSON.parse(
fs.readFileSync("data/cities.json", "utf-8")
);
const outputDir = "src/content/cities";
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
for (const city of citiesData) {
const outputPath = path.join(outputDir, `${city.slug}.mdx`);
// Bereits generierte Dateien überspringen
if (fs.existsSync(outputPath)) {
console.log(`Überspringe ${city.city} (bereits vorhanden)`);
continue;
}
console.log(`Generiere Seite für ${city.city}...`);
const content = await generateCityContent(city);
const mdx = buildMDX(city, content);
fs.writeFileSync(outputPath, mdx, "utf-8");
console.log(`Erstellt: ${outputPath}`);
// Rate Limiting: 1 Sekunde Pause zwischen API-Aufrufen
await new Promise((resolve) => setTimeout(resolve, 1000));
}
console.log("Generierung abgeschlossen.");
}
main().catch(console.error);
Das Skript ausführen mit:
npx tsx scripts/generate-city-pages.ts
Anschließend normal bauen:
astro build
Integration in Next.js
In Next.js funktioniert der Ansatz ähnlich, nutzt aber getStaticPaths und getStaticProps. Hier ein kompakter Überblick:
// pages/klempner/[city].tsx
import { GetStaticPaths, GetStaticProps } from "next";
import citiesData from "../../data/cities.json";
export const getStaticPaths: GetStaticPaths = async () => {
const paths = citiesData.map((city) => ({
params: { city: city.slug },
}));
return { paths, fallback: false };
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const city = citiesData.find((c) => c.slug === params?.city);
// Content wurde vorab generiert und in data/generated-content.json gespeichert
const generatedContent = require("../../data/generated-content.json");
const content = generatedContent[city!.slug];
return { props: { city, content } };
};
Der Unterschied zur Astro-Lösung: In Next.js empfiehlt sich, den generierten Content in einer JSON-Datei zu bündeln statt einzelne MDX-Dateien zu schreiben. Das Script-Muster für die Generierung ist identisch.
Qualitätskontrolle bei Scale
Bei hundert generierten Seiten manuell zu prüfen ist nicht realistisch. Automatisierung braucht automatische Qualitätssicherung.
interface QualityResult {
passed: boolean;
issues: string[];
wordCount: number;
}
function checkQuality(
content: string,
city: string,
minWords = 200
): QualityResult {
const issues: string[] = [];
const words = content.split(/\s+/).filter((w) => w.length > 0);
const wordCount = words.length;
// Mindestlänge
if (wordCount < minWords) {
issues.push(`Zu kurz: ${wordCount} Wörter (Minimum: ${minWords})`);
}
// Stadt muss vorkommen
const cityMentions = (content.match(new RegExp(city, "gi")) || []).length;
if (cityMentions < 2) {
issues.push(`Stadt '${city}' nur ${cityMentions}x erwähnt`);
}
// Keine identischen Sätze aus dem Template
const templatePhrases = ["Schreibe drei Absätze", "Erstelle eine Landingpage"];
for (const phrase of templatePhrases) {
if (content.includes(phrase)) {
issues.push(`Template-Text im Output gefunden: "${phrase}"`);
}
}
// Mindestanzahl Absätze
const paragraphs = content.split(/\n\n+/).filter((p) => p.trim().length > 50);
if (paragraphs.length < 3) {
issues.push(`Zu wenige Absätze: ${paragraphs.length} (Minimum: 3)`);
}
return {
passed: issues.length === 0,
issues,
wordCount,
};
}
Was funktioniert, was nicht
Nicht jedes Thema eignet sich für programmatisches Vorgehen.
| Use Case | Eignung | Besonderheit |
|---|---|---|
| Lokale Landingpages | Sehr gut | Je mehr Lokal-Daten, desto besser |
| Produkt-Vergleichsseiten | Gut | Strukturierte Produktdaten nötig |
| E-Commerce Kategorieseiten | Gut | Daten aus Produktkatalog nutzen |
| Rezeptseiten | Gut | Strukturierte Zutaten- und Nährwertdaten |
| FAQ aus Datensatz | Gut | Frage-Antwort-Paare als JSON |
| Nachrichtenartikel | Schlecht | Kein Aktualitätsbezug möglich |
| Medizinische Ratgeber | Nicht empfohlen | YMYL, E-E-A-T kritisch |
| Finanzberatung | Nicht empfohlen | YMYL, rechtliche Risiken |
| Meinungsbeiträge | Nicht sinnvoll | Persönliche Stimme fehlt |
Die Faustregel: Wenn der Use Case klar strukturierte, verifizierbare Daten hat und der Nutzer eine informational oder commercial Suchabsicht hat, funktioniert Programmatic SEO. Wenn der Use Case persönliche Expertise, aktuelle Ereignisse oder medizinische Verantwortung erfordert, nicht.
Nicht alles sollte indexiert werden
Ein häufiger Fehler bei Programmatic SEO ist nicht die Generierung, sondern die Veröffentlichung. Nur weil tausend Seiten technisch erzeugt werden können, heißt das nicht, dass tausend Seiten in den Index gehören.
Nicht indexiert werden sollten Seiten dann, wenn sie zu wenig Datentiefe haben, sich inhaltlich nur minimal unterscheiden oder keinen klaren Suchnutzen stiften. Genau dort entsteht das, was später wie Duplicate Content oder “thin content” aussieht – auch wenn die Texte formal unterschiedlich sind.
Praktisch heißt das:
- Seiten mit sehr dünner Datenbasis besser
noindex - ähnliche Varianten lieber zusammenfassen statt vervielfachen
- erst die stärksten Cluster veröffentlichen, nicht sofort alles
- Indexierung als redaktionelle Entscheidung behandeln, nicht als Default
Die bessere Strategie ist fast immer: weniger Seiten, dafür mit tieferem Datensatz und klarerem Nutzen.
Einordnung
Programmatic SEO mit Gemini ist kein Ersatz für redaktionellen Content. Es ist ein Werkzeug für spezifische, skalierbare Anwendungsfälle – und es funktioniert gut, wenn der Prozess sauber ist.
Der entscheidende Faktor ist die Datenqualität. Ein Template kann nur so gut sein wie die Daten, die es befüllen. Wer in die Aufbereitung der strukturierten Daten investiert, bekommt Content, der rankt. Wer mit dünnen Daten arbeitet, bekommt dünnen Content.
Der nächste Artikel in dieser Serie wechselt die Perspektive: von AI Studio und der Gemini API hin zu Vertex AI – Googles Enterprise-Plattform für KI. Was der Wechsel bedeutet, wann er sinnvoll ist und was er kostet.