Making-of: Mein SEO- und GEO-Tool

Wie aus 25 Jahren SEO-Praxis ein schnelles, kostenloses SEO/GEO-Analyse-Tool wurde — mit echten Code-Beispielen, wie Score und Checks tatsächlich rechnen.

von ·

Ich wollte schon immer ein SEO-Tool, das schnell und ohne Umwege das Wesentliche auf den Punkt bringt — keine Abo-Falle, kein Login, keine zwölf Dashboards, durch die ich mich klicke, um eine simple Frage zu beantworten: Ist diese Seite technisch sauber, und wird sie von klassischen Suchmaschinen und von KI-Systemen verstanden? Also habe ich es gebaut. Dieser Artikel ist das Making-of: die Beweggründe, die Architektur und vor allem die echte Rechen-Logik dahinter — mit Code, den du eins zu eins übernehmen kannst. Das fertige Werkzeug läuft kostenlos unter jpkc.com/tools/seo/.

Warum überhaupt ein eigenes Tool

Nach gut 25 Jahren im Web kenne ich die SEO-Suiten alle. Sie können viel, kosten monatlich und verstecken die zwei, drei Zahlen, die wirklich zählen, hinter Funktionsbergen, die ich zu 90 % nie brauche. Für einen schnellen Blick auf eine einzelne URL — „passt das Fundament?" — ist das, als würde ich einen Bagger mieten, um ein Loch für eine Tomatenpflanze zu graben.

Was mir fehlte, war ein Werkzeug mit drei Eigenschaften:

  • Sofort und ohne Konto. URL rein, Analyse raus. Keine Registrierung, kein Tracking, kein „14 Tage gratis, dann 99 €/Monat".
  • Ehrlich gewichtet. Nicht 200 Mikro-Signale gleich laut, sondern die Dinge vorne, die ich aus Erfahrung als ranking- und zitierrelevant einschätze.
  • SEO und GEO. Klassische Suchmaschinen-Optimierung und Generative Engine Optimization in einem Durchlauf — denn 2026 entscheidet beides über Sichtbarkeit. Was GEO überhaupt ist, habe ich in Was ist GEO? ausführlich beschrieben.

Der entscheidende Vorteil von „selbst bauen": Ich lege offen, wie gerechnet wird. Bei den großen Tools ist der Score eine Blackbox. Hier ist jeder Punkt nachvollziehbar — und genau das zeige ich dir jetzt.

Die Architektur: Proxy plus Browser

Die erste Grundsatzentscheidung war technischer Natur. Eine SEO-Analyse muss den rohen HTTP-Verkehr einer Seite sehen: Status-Code, Redirect-Kette, Response-Header, SSL-Zertifikat, Timing. Aus dem Browser heraus geht das nicht — die Same-Origin-Policy und CORS verbieten es, fremde Seiten samt Header einfach zu laden. Also brauchte es einen serverseitigen Proxy in PHP, der die Zielseite holt und das Ergebnis an das Frontend zurückgibt.

Ein Proxy, der beliebige URLs abruft, ist allerdings ein klassisches SSRF-Risiko (Server-Side Request Forgery): Jemand könnte http://127.0.0.1/ oder interne Cloud-Metadaten-Endpunkte anfragen und so an Dinge kommen, die nie öffentlich sein sollten. Deshalb prüft der Proxy jede URL — auch jeden einzelnen Redirect-Hop — gegen eine Allowlist-Logik, bevor er sie anfasst:

function isPublicUrl( string $url, ?string &$error = null ): bool {

    if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
        $error = 'Invalid URL format';
        return false;
    }

    // Nur http/https — kein file://, gopher://, dict:// etc.
    if ( ! str_starts_with( $url, 'http://' ) && ! str_starts_with( $url, 'https://' ) ) {
        $error = 'Only http/https URLs are allowed';
        return false;
    }

    $host     = parse_url( $url, PHP_URL_HOST );
    $bareHost = trim( (string) $host, '[]' );   // IPv6 kommt in [Klammern]

    // Hostname auflösen und JEDE zurückgegebene Adresse prüfen
    $ips = @gethostbynamel( $bareHost ) ?: [];

    foreach ( $ips as $ip ) {
        if ( ! isPublicIpAddr( $ip ) ) {       // blockt 127.0.0.0/8, 10/8, 192.168/16, ::1 …
            $error = 'IP address is not publicly routable';
            return false;
        }
    }

    return ! empty( $ips );
}

Der wichtige Teil ist nicht der Happy Path, sondern dass die Hostname-Auflösung vor dem Request passiert und alle aufgelösten IPs geprüft werden — sonst umgeht ein DNS-Eintrag, der auf 10.0.0.5 zeigt, die Sperre. Die Redirect-Kette wird Hop für Hop neu validiert, denn eine harmlose öffentliche URL darf nicht über einen 302-Redirect auf etwas Internes zeigen.

Alles, was danach kommt — das HTML parsen, Überschriften zählen, Schema lesen — passiert dann wieder client-seitig im Browser über den nativen DOMParser. Das hält den Server schlank: Er holt nur Bytes, die eigentliche Analyse-Engine ist JavaScript. Diese Aufteilung — minimaler, abgesicherter PHP-Proxy plus dicke JS-Analyse — ist das Rückgrat des ganzen Tools.

Das Grundprinzip: ein Check ist nur vier Werte

Bevor ein einziger SEO-Wert berechnet wird, brauchte ich eine Form, in die jeder Check passt — vom HTTPS-Häkchen bis zur Lesbarkeit. Ich habe mich auf vier Werte festgelegt, und das ist vielleicht die wichtigste Designentscheidung im ganzen Tool, weil sie alles andere einfach hält:

// Ein Check produziert immer: erreichte Punkte, ein Status, ein Hinweis.
function scoreCheck( name, maxPoints, earned, status, hint ) {
	return { name, maxPoints, earned, status, hint: hint || "" };
}
// status ∈ { "pass", "partial", "fail" }

status ist bewusst dreiwertig, nicht binär. Die halbe Stufe partial ist der Grund, warum der Score sich „fair" anfühlt: Eine Seite mit einem 65 Zeichen langen Title hat nicht „versagt", sie ist nur nicht optimal — also gibt es Teilpunkte, nicht null. Genau das machen die meisten Pass/Fail-Checker falsch.

Mit diesem Baustein wird jeder einzelne Check zu einer kleinen, isolierten Funktion, die ich unabhängig verstehen und testen kann. Vier davon zeige ich dir jetzt im Detail.

SEO-Score: vier Beispiele aus der Praxis

Der SEO-Score besteht aus rund 40 solcher Checks in acht Kategorien (Technical, On-Page, Crawling, Social, Performance, Accessibility, Content). Hier sind vier, die das Prinzip gut zeigen.

Title-Länge: Teilpunkte statt Schwarz-Weiß

Der Seitentitel ist eines der stärksten On-Page-Signale. Zu kurz verschenkt Kontext, zu lang wird in der Suche abgeschnitten. Ich bewerte in drei Stufen — optimal, akzeptabel, daneben:

function checkTitleLength( title ) {
	const len = title ? title.length : 0;

	const optimal    = len >= 50 && len <= 60;   // ideale Anzeigelänge
	const acceptable = len >= 30 && len <= 70;   // brauchbar, aber nicht ideal

	return scoreCheck(
		"Title (50–60 Zeichen)",
		8,
		optimal ? 8 : acceptable ? 4 : 0,
		optimal ? "pass" : acceptable ? "partial" : "fail",
		title ? `${len} Zeichen` : "Kein <title>-Tag gefunden"
	);
}
Title-Länge Status Punkte (von 8)
50–60 Zeichen pass 8
30–70 Zeichen partial 4
alles andere / fehlt fail 0

Acht Punkte für den Title, weil er real viel zur Klickrate beiträgt. Das Gewicht ist eine Erfahrungsentscheidung, keine universelle Wahrheit — aber genau solche Entscheidungen wollte ich selbst treffen statt sie einem fremden Algorithmus zu überlassen.

TTFB: gestufte Bewertung statt eines Schwellwerts

Bei der Time to First Byte zeigt sich derselbe Gedanke noch deutlicher. Ein einzelner Grenzwert wäre brutal — 199 ms voll, 201 ms nichts. Stattdessen gibt es zwei Stufen:

function checkTtfb( ttfbMs ) {
	if ( ttfbMs === null ) {
		return scoreCheck( "TTFB < 500 ms", 4, 0, "fail", "Keine Timing-Daten" );
	}

	const earned = ttfbMs < 200 ? 4 : ttfbMs < 500 ? 2 : 0;
	const status = ttfbMs < 200 ? "pass" : ttfbMs < 500 ? "partial" : "fail";

	return scoreCheck( "TTFB < 500 ms", 4, earned, status, `${ttfbMs} ms` );
}

Unter 200 ms ist exzellent (volle 4 Punkte), unter 500 ms noch in Ordnung (2 Punkte), darüber zu langsam. Warum TTFB überhaupt zählt und wie er mit den Core Web Vitals zusammenhängt, steht in Core Web Vitals & Performance.

Security-Header: zählen statt einzeln prüfen

Manche Checks sind eine Mengenfrage. Bei den Security-Headern interessiert weniger, welcher genau fehlt, sondern ob die Seite das Thema grundsätzlich ernst nimmt. Also zähle ich, wie viele aus einer Liste relevanter Header gesetzt sind:

const SECURITY_HEADERS = [
	"Strict-Transport-Security", "Content-Security-Policy",
	"X-Frame-Options", "X-Content-Type-Options",
	"Referrer-Policy", "Permissions-Policy",
	"Cross-Origin-Opener-Policy", "Cross-Origin-Resource-Policy",
];

function checkSecurityHeaders( headers ) {
	const present = SECURITY_HEADERS.filter( h => Boolean( headers[ h ] ) ).length;

	const earned = present >= 4 ? 3 : present >= 2 ? 1 : 0;
	const status = present >= 4 ? "pass" : present >= 2 ? "partial" : "fail";

	return scoreCheck( "Security-Header ≥ 4", 3, earned, status,
		`${present}/8 Security-Header vorhanden` );
}

Vier oder mehr von acht: voller Punkt. Zwei bis drei: halbe Anerkennung. Darunter: Nachholbedarf. Diese „zähl die guten Signale"-Logik taucht im Tool an mehreren Stellen auf — bei Open-Graph-Tags, bei semantischen HTML-Elementen, bei den AI-Crawlern.

Lesbarkeit: die sprachabhängige Flesch-Formel

Das ist mein liebster Teil, weil hier echte Sprachwissenschaft im Code steckt. Lesbarkeit lässt sich messen — über den Flesch Reading Ease. Die Formel setzt die durchschnittliche Satzlänge und die durchschnittliche Silbenzahl pro Wort ins Verhältnis. Der Haken: Die Konstanten gelten nur fürs Englische. Deutsche Wörter sind länger und silbenreicher; mit der englischen Formel sähe jeder deutsche Text künstlich „schwer" aus.

Deshalb erkennt das Tool die Sprache aus <html lang> und rechnet mit der passenden Variante. Fürs Deutsche ist das die Amstad-Formel:

// Durchschnittliche Satzlänge (ASL) und Silben pro Wort (ASW)
const asl = wordCount / Math.max( 1, sentenceCount );
const asw = syllableCount / Math.max( 1, wordCount );

let flesch;
if ( lang === "de" ) {
	flesch = 180 - asl - 58.5 * asw;            // Amstad (Deutsch)
} else if ( lang === "fr" ) {
	flesch = 207 - 1.015 * asl - 73.6 * asw;    // Kandel-Moles (Französisch)
} else if ( lang === "es" ) {
	flesch = 206.84 - 1.02 * asl - 60.0 * asw;  // Fernández Huerta (Spanisch)
} else {
	flesch = 206.835 - 1.015 * asl - 84.6 * asw; // Original-Flesch (Englisch)
}

const score = Math.round( Math.min( 100, Math.max( 0, flesch ) ) );

Das Silbenzählen ist seinerseits sprachabhängig. Für deutsche Wörter werden Umlaute und ß erst phonetisch ersetzt, dann Vokalgruppen gezählt:

function countSyllablesDE( word ) {
	word = word.toLowerCase();
	if ( word.length <= 3 ) return 1;

	// Umlaute/ß phonetisch auflösen, damit Vokalgruppen stimmen
	word = word.replace( /ä/g, "ae" ).replace( /ö/g, "oe" )
		.replace( /ü/g, "ue" ).replace( /ß/g, "ss" )
		.replace( /[^a-z]/g, "" );

	const groups = word.match( /[aeiouy]{1,2}/g );   // zusammenhängende Vokale = grob eine Silbe
	return groups ? Math.max( 1, groups.length ) : 1;
}

Das ist eine Heuristik, kein perfekter Silbentrenner — aber für eine relative Lesbarkeits-Einordnung über einen ganzen Text völlig ausreichend. Der resultierende Wert wird in Stufen übersetzt (90+ „Sehr leicht", 60–69 „Standard", unter 30 „Sehr schwer") und fließt mit zwei Punkten in den Content-Score ein. Warum verständliches Schreiben gerade für KI-Systeme zählt, vertiefe ich in Schreiben für KI.

Im selben Schritt entstehen nebenbei noch die Top-Keywords mit Dichte — nach Abzug von Stoppwörtern in mehreren Sprachen:

const freq = {};
for ( const w of words ) {
	const lower = w.toLowerCase();
	if ( lower.length >= 3 && ! STOPWORDS[ lower ] ) {
		freq[ lower ] = ( freq[ lower ] || 0 ) + 1;
	}
}

const topKeywords = Object.keys( freq )
	.sort( ( a, b ) => freq[ b ] - freq[ a ] )
	.map( word => ( {
		word,
		count: freq[ word ],
		density: Math.round( freq[ word ] / wordCount * 1000 ) / 10,   // Prozent, 1 Nachkommastelle
	} ) );

Vom Check zum Score: die Summe

Sind alle Checks gelaufen, ist der Gesamt-Score trivial — und das ist der Punkt. Weil jeder Check seine maxPoints und earned selbst kennt, ist die Aggregation eine einzige Schleife:

function totalScore( checks ) {
	let earned = 0, max = 0;
	for ( const c of checks ) {
		earned += c.earned;
		max    += c.maxPoints;
	}
	return Math.round( earned / max * 100 );   // 0–100
}

Daraus wird zuletzt ein Schulnoten-Grade: A ab 90, B ab 80, C ab 60, D ab 40, sonst F. Keine Magie, keine geheime Gewichtung im Hintergrund — die ganze „Intelligenz" steckt in den einzelnen, lesbaren Check-Funktionen. Genau so wollte ich es: nachvollziehbar bis auf den einzelnen Punkt.

GEO-Score: warum ein zweiter, unabhängiger Wert

Hier wird es interessant, denn das ist der eigentliche Grund, warum ich das Tool 2026 noch einmal angefasst habe. Klassisches SEO sagt dir, ob Google deine Seite mag. Aber ob Claude, ChatGPT oder Perplexity deinen Inhalt sauber lesen, zuordnen und zitieren können, ist eine andere Frage — mit anderen Signalen. Deshalb gibt es einen zweiten, völlig eigenständigen GEO-Score, der den SEO-Score nie beeinflusst und umgekehrt. Eine Seite kann technisch top und KI-blind sein, oder umgekehrt.

Der GEO-Score nutzt denselben scoreCheck-Baustein, prüft aber andere Dinge.

Schema-Vielfalt: rekursiv durch den @graph

Strukturierte Daten sind für KI-Zitate Gold (Hintergrund: Structured Data & Technical GEO). Es reicht aber nicht, ob JSON-LD da ist — interessant ist, wie viele verschiedene Typen ausgezeichnet sind. Moderne Seiten verschachteln das in einem @graph, deshalb sammle ich die @type-Werte rekursiv ein:

function collectSchemaTypes( node, types = new Set() ) {
	if ( ! node || typeof node !== "object" ) return types;

	const t = node[ "@type" ];
	if ( Array.isArray( t ) ) t.forEach( x => types.add( x ) );
	else if ( t ) types.add( t );

	for ( const key of Object.keys( node ) ) {
		if ( key === "@context" ) continue;
		const val = node[ key ];
		if ( val && typeof val === "object" ) collectSchemaTypes( val, types );  // tiefer graben
	}
	return types;
}

function checkSchemaVariety( jsonLdBlocks ) {
	const types = new Set();
	jsonLdBlocks.forEach( block => collectSchemaTypes( block, types ) );
	const n = types.size;

	return scoreCheck( "Schema-Vielfalt", 3,
		n >= 3 ? 3 : n >= 2 ? 2 : n >= 1 ? 1 : 0,
		n >= 3 ? "pass" : n >= 1 ? "partial" : "fail",
		n > 0 ? `${n} Typ(en): ${[ ...types ].slice( 0, 5 ).join( ", " )}` : "Keine Schema-Typen" );
}

Drei oder mehr Typen — etwa Article plus Person plus BreadcrumbList — sprechen für eine bewusst maschinenlesbar ausgezeichnete Seite. Genau das honoriert der Check.

llms.txt und die AI-Crawler

Zwei GEO-Checks drehen sich um die Frage, ob du KI-Systeme überhaupt hereinlässt. Das Tool holt automatisch die llms.txt aus dem Domain-Root (die Markdown-Datei, die einer KI Zweck und Struktur der Seite erklärt) und prüft die robots.txt gezielt gegen die gängigen KI-Crawler — nicht gegen den generischen User-agent: *:

const AI_CRAWLERS = [
	"GPTBot", "ChatGPT-User", "OAI-SearchBot", "Google-Extended",
	"ClaudeBot", "anthropic-ai", "PerplexityBot", "CCBot",
];

function checkAiCrawlers( robotsTxt, pageUrl ) {
	const blocked = AI_CRAWLERS.filter(
		bot => ! isAllowedForAgent( robotsTxt, pageUrl, bot )
	);

	const earned = blocked.length === 0 ? 3
		: blocked.length <= 3 ? 2
		: blocked.length <= 6 ? 1 : 0;

	return scoreCheck( "AI-Crawler erlaubt", 3, earned,
		earned === 3 ? "pass" : earned >= 1 ? "partial" : "fail",
		blocked.length ? `blockiert: ${blocked.join( ", " )}` : "Alle großen AI-Crawler erlaubt" );
}

Das ist ein bewusst neutraler Check: Er bewertet das Zulassen positiv, weil das Tool aus der Perspektive „ich will in KI-Antworten vorkommen" gebaut ist. Wer Inhalte vor dem Training schützen will, liest hier trotzdem schwarz auf weiß, welche Bots er gerade aussperrt — und das ist genauso wertvoll.

Kurze Absätze: was LLMs lieber lesen

Ein letztes, kleines, aber sprechendes Beispiel: KI-Systeme extrahieren aus kurzen, fokussierten Absätzen zuverlässiger als aus Textwänden. Also misst das Tool die durchschnittliche Absatzlänge:

function checkParagraphLength( wordCount, paragraphCount ) {
	if ( ! paragraphCount ) {
		return scoreCheck( "Kurze Absätze (Ø < 150 Wörter)", 2, 0, "fail", "Keine Absätze erkannt" );
	}
	const avg = Math.round( wordCount / paragraphCount );

	return scoreCheck( "Kurze Absätze (Ø < 150 Wörter)", 2,
		avg < 150 ? 2 : avg < 250 ? 1 : 0,
		avg < 150 ? "pass" : avg < 250 ? "partial" : "fail",
		`${paragraphCount} Absätze, Ø ${avg} Wörter` );
}

Unter 150 Wörtern pro Absatz: voller Punkt. Das ist dieselbe Empfehlung, die ich in Schreiben für KI gebe — hier nur als messbare Zahl gegossen.

Was bewusst fehlt

Genauso wichtig wie das, was drin ist, ist das, was ich weggelassen habe. Es gibt kein Konto, kein Tracking, kein Crawl-Budget-Monitoring über tausende URLs, keine Rank-Tracking-Historie. Das ist Absicht, nicht Faulheit. Das Tool beantwortet eine Frage — „ist diese eine Seite sauber für Suche und KI?" — und zwar in Sekunden. Alles, was diese Frage nicht direkt bedient, hätte es nur langsamer, komplizierter und erklärungsbedürftiger gemacht. YAGNI als Feature: Der Verzicht ist das Produkt.

FAQ

Kostet das Tool etwas?

Nein. Der SEO- & GEO-Analyzer ist kostenlos und ohne Konto nutzbar. Es gibt keine Registrierung, kein Abo und kein Tracking. URL eingeben, Analyse ablesen — das war von Anfang an der ganze Sinn der Übung.

Was ist der Unterschied zwischen dem SEO- und dem GEO-Score?

Der SEO-Score bewertet die klassische Suchmaschinen-Tauglichkeit: HTTPS, Title, Meta-Description, Überschriften, Ladezeit, Security-Header und Ähnliches. Der GEO-Score bewertet die KI-Tauglichkeit: strukturierte Daten, llms.txt, ob KI-Crawler zugelassen sind, saubere Heading-Hierarchie und kurze Absätze. Beide Werte sind unabhängig — eine Seite kann beim einen glänzen und beim anderen durchfallen.

Wie genau ist der Score?

Jeder Punkt ist nachvollziehbar an eine konkrete, geprüfte Regel gebunden — keine Blackbox. Schwellen wie „Title 50–60 Zeichen" oder „TTFB < 200 ms" sind etablierte Best Practices, die Gewichtung beruht auf 25 Jahren Praxis. Der Score ist eine fundierte Einordnung, kein Naturgesetz: Er zeigt zuverlässig, wo eine Seite steht und was als Nächstes dran ist — er ersetzt aber kein redaktionelles Urteil über den Inhalt selbst.

Warum kein Login und keine gespeicherten Verläufe?

Weil der Sinn des Tools Geschwindigkeit und Ehrlichkeit ist. Ein Login wäre eine Hürde vor der eigentlichen Aufgabe, und gespeicherte Verläufe hätten Tracking und Datenhaltung bedeutet — beides wollte ich ausdrücklich nicht. Wer eine Analyse behalten will, kann das Ergebnis als JSON exportieren.

Weiterlesen

Den Rahmen für die KI-Seite liefert Was ist GEO?; die technische Tiefe dazu steht in Structured Data & Technical GEO. Wie du Inhalte schreibst, die Suche und KI gleichermaßen mögen, klärt Schreiben für KI, und das Performance-Fundament behandelt Core Web Vitals & Performance. Und am schnellsten verstehst du das Tool, indem du eine eigene URL durchlaufen lässt: jpkc.com/tools/seo/.