News

SEO Performance optimieren für Unternehmen in Dortmund: Core Web Vitals, PageSpeed Insights & Screaming Frog

Von Erol Demirkoparan
11 min
SEO Performance optimieren für Unternehmen in Dortmund: Core Web Vitals, PageSpeed Insights & Screaming Frog - Cloudox Software Agentur Blog

1) Performance-SEO in Dortmund systematisch aufsetzen (Messmodell → Maßnahmen → Wirkung)

Schritt 1: Zielmetriken festlegen (Core Web Vitals + Business-KPIs)

Für lokale Unternehmen in Dortmund ist Performance-SEO dann sinnvoll, wenn es direkt auf Conversions einzahlt (Anfragen, Calls, Terminbuchungen). Technisch übersetzen wir das in ein Messmodell aus Core Web Vitals und SEO-Kennzahlen:

  • LCP (Largest Contentful Paint): Ziel i.d.R. ≤ 2,5s
  • INP (Interaction to Next Paint): Ziel i.d.R. ≤ 200ms
  • CLS (Cumulative Layout Shift): Ziel i.d.R. ≤ 0,1
  • Page Authority: indirekte Erfolgskennzahl (stärker durch Content/Links, aber Performance beeinflusst Crawl/UX)
  • Keyword Density: Qualitäts-Guardrail (nicht „hochdrehen“, sondern natürlich + entitätenbasiert steuern)

Wichtig: Core Web Vitals sind sowohl UX- als auch Ranking-Signale. In umkämpften lokalen SERPs (z.B. „Steuerberater Dortmund“, „Zahnarzt Dortmund“, „Bauunternehmen Dortmund“) kann Performance den Ausschlag geben, wenn Inhalte vergleichbar sind.

Schritt 2: Priorisieren nach Seitentyp (Money Pages zuerst)

Für den Target-Cluster rund um SEO Agentur priorisieren Sie:

  • Landingpages (Leistung + Standortbezug Dortmund)
  • Kontakt/Termin (Conversion-Pfade)
  • Ratgeber/Blog (Traffic-Aufbau, interne Verlinkung)

2) PageSpeed Insights richtig nutzen (Daten lesen, nicht nur Score jagen)

Schritt 1: Lab vs. Field Data verstehen

PageSpeed Insights zeigt zwei Datenwelten:

  • Field Data (CrUX): echte Nutzerdaten, direkt relevant für Core Web Vitals
  • Lab Data (Lighthouse): synthetisch, ideal zum Debuggen

In Dortmund-relevanten Zielgruppen (mobile Nutzer, wechselnde Netzqualität) ist es üblich, dass Lab „gut“ aussieht, Field aber schwankt. Optimieren Sie daher auf stabile Field-Werte: weniger JS, effizientere Bilder, schnelleres Rendering.

Schritt 2: Typische Performance-Bremsen (und schnelle Wins)

  • Render-blocking CSS/JS → Critical CSS, Defer/Async, Code-Splitting
  • Zu große Hero-Bilder → AVIF/WebP, korrektes Sizing, Preload für LCP-Element
  • Drittanbieter-Skripte (Chat, Tracking) → Consent-gesteuert nachladen, Tag-Reduktion
  • Fonts → self-host, preload, font-display: swap

Schritt 3: INP gezielt verbessern (nicht nur „weniger JS“, sondern „weniger Main-Thread“)

INP leidet oft an langen Tasks durch Frameworks, Tag-Manager und unoptimierte Event-Handler. Maßnahmen:

  • Interaktionen identifizieren (Menü, Filter, Formular)
  • Long Tasks in kleinere Chunks splitten
  • Third-Party Scripts reduzieren und später laden

3) Screaming Frog Analysis: Technisches SEO-Audit für Performance & Indexierbarkeit

Schritt 1: Crawl-Setup für Dortmund-SEO

Eine saubere Screaming Frog Analysis verbindet Technik + Inhalte. Crawl-Ziele:

  • Indexierbare Seiten vs. Noindex/Canonical-Konflikte
  • Response Codes (3xx/4xx/5xx) und Redirect-Ketten
  • Duplicate Titles/Meta Descriptions
  • Interne Linktiefe (wichtige Seiten zu tief?)
  • Thin Content und Keyword-Kannibalisierung (z.B. mehrere „Dortmund“-Landingpages mit ähnlichem Fokus)

Schritt 2: Performance-Signale aus Crawl-Daten ableiten

Auch ohne echte Core-Web-Vitals-Messung liefert ein Crawl starke Hinweise:

  • Große HTML-Seiten (DOM-Bloat) → oft INP/CLS-Risiko
  • Viele Skripte/Requests → häufig LCP/INP-Probleme
  • Bild-URLs ohne moderne Formate → LCP-Quick-Win

Schritt 3: Page Authority über interne Architektur hebeln

Page Authority steigt nicht nur durch externe Links, sondern auch durch eine klare interne Linkstruktur:

  • Starke Themencluster (z.B. „SEO Dortmund“ → Unterseiten für Technik, Content, Local SEO)
  • Interne Links von Traffic-Seiten auf Money Pages (z.B. /seo-agentur)
  • Breadcrumbs & Kontextlinks (semantisch, nicht nur Footer)

4) Keyword Density datenbasiert steuern (ohne Over-Optimization)

Schritt 1: Keyword-Set definieren (Hauptkeyword + Entitäten)

Für Dortmund kombinieren Sie:

  • Transaktional: „SEO Agentur Dortmund“, „SEO Beratung Dortmund“
  • Informational: „Core Web Vitals verbessern“, „PageSpeed Insights interpretieren“
  • Entitäten/Co-Occurences: „Core Web Vitals“, „PageSpeed Insights“, „Screaming Frog“, „Page Authority“, „Keyword Density“

Die Keyword Density sollte als Plausibilitätscheck dienen, nicht als Zielwert. Entscheidend ist thematische Abdeckung (Suchintention + Entitäten + Beispiele).

Schritt 2: Python Script for Keyword Analysis

Dieses Script crawlt eine URL-Liste (z.B. Seiten aus Screaming Frog exportiert), extrahiert Text, berechnet Keyword Density für ein Set von Begriffen und gibt eine CSV aus.

import csv
import re
import time
from collections import Counter
from dataclasses import dataclass
from typing import Dict, List, Tuple

import requests
from bs4 import BeautifulSoup


@dataclass
class PageResult:
    url: str
    word_count: int
    densities: Dict[str, float]
    top_terms: List[Tuple[str, int]]


def normalize_text(text: str) -> str:
    text = text.lower()
    text = re.sub(r"[^a-z0-9äöüß\s-]", " ", text)
    text = re.sub(r"\s+", " ", text).strip()
    return text


def extract_visible_text(html: str) -> str:
    soup = BeautifulSoup(html, "html.parser")

    for tag in soup(["script", "style", "noscript", "svg", "header", "footer", "nav"]):
        tag.decompose()

    text = soup.get_text(separator=" ")
    return normalize_text(text)


def count_terms(text: str) -> Counter:
    tokens = [t for t in text.split(" ") if len(t) > 1]
    return Counter(tokens)


def keyword_density(text: str, keyword: str) -> float:
    tokens = text.split(" ")
    if not tokens:
        return 0.0

    # Keyword kann aus mehreren Wörtern bestehen
    kw = normalize_text(keyword)
    kw_tokens = kw.split(" ")
    if not kw_tokens:
        return 0.0

    hits = 0
    for i in range(0, len(tokens) - len(kw_tokens) + 1):
        if tokens[i : i + len(kw_tokens)] == kw_tokens:
            hits += 1

    return (hits / len(tokens)) * 100.0


def analyze_page(url: str, keywords: List[str], timeout: int = 20) -> PageResult:
    r = requests.get(
        url,
        timeout=timeout,
        headers={"User-Agent": "KeywordDensityBot/1.0 (+internal SEO audit)"},
    )
    r.raise_for_status()

    text = extract_visible_text(r.text)
    tokens = [t for t in text.split(" ") if t]
    counts = count_terms(text)

    densities = {kw: round(keyword_density(text, kw), 4) for kw in keywords}
    top_terms = counts.most_common(15)

    return PageResult(
        url=url,
        word_count=len(tokens),
        densities=densities,
        top_terms=top_terms,
    )


def main() -> None:
    input_file = "urls.txt"  # eine URL pro Zeile (z.B. Export aus Screaming Frog)
    output_file = "keyword_density_report.csv"

    keywords = [
        "seo agentur",
        "dortmund",
        "core web vitals",
        "pagespeed insights",
        "screaming frog",
        "page authority",
        "keyword density",
    ]

    with open(input_file, "r", encoding="utf-8") as f:
        urls = [line.strip() for line in f if line.strip() and not line.startswith("#")]

    rows = []
    for url in urls:
        try:
            result = analyze_page(url, keywords)
            row = {
                "url": result.url,
                "word_count": result.word_count,
            }
            for kw, dens in result.densities.items():
                row[f"density_{kw}"] = dens
            row["top_terms"] = "; ".join([f"{t}:{c}" for t, c in result.top_terms])
            rows.append(row)

            time.sleep(0.5)
        except Exception as e:
            rows.append({"url": url, "word_count": 0, "error": str(e)})

    fieldnames = sorted({k for r in rows for k in r.keys()})
    with open(output_file, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)


if __name__ == "__main__":
    main()

5) Umsetzung: Performance-Backlog (Dortmund-SEO) als klare Roadmap

FeatureDetails
Core Web Vitals FixesLCP über Hero-Optimierung (Preload + AVIF), INP durch JS-Reduktion/Chunking, CLS durch feste Größen für Medien/Ads
PageSpeed Insights WorkflowField Data priorisieren, Lab Findings in Tickets übersetzen, vor/nach Messung pro Template
Screaming Frog AnalysisIndexability, Redirects, Duplicate Metas, Linktiefe, Thin Content, Kannibalisierung; Export als Basis für Fix-Listen
Page Authority HebelInterne Verlinkung (Cluster), thematische Hubs, saubere Canonicals, konsistente URL-Struktur
Keyword Density GuardrailsAutomatisierter Report pro Landingpage, Fokus auf Entitäten statt Wiederholungen, Snippet-Optimierung ohne Stuffing

6) JSON Config for SEO Tools (inkl. TypeScript-Runner)

Schritt 1: Konfiguration als JSON (mit \n-Zeilenumbrüchen)

Dieses JSON kann als zentrale Konfig für Ihr Audit dienen (Keywords, URLs, Schwellenwerte für Core Web Vitals, Exporte). Hinweis: Die JSON-Strings enthalten explizit \n.

{
  "project": {
    "name": "Dortmund SEO Performance Audit",
    "targetUrl": "/seo-agentur",
    "primaryAnchor": "SEO Agentur",
    "location": "Dortmund"
  },
  "thresholds": {
    "coreWebVitals": {
      "lcpSeconds": 2.5,
      "inpMilliseconds": 200,
      "cls": 0.1
    },
    "keywordDensity": {
      "warningAbovePercent": 3.0,
      "minimumWords": 250
    }
  },
  "keywords": [
    "SEO Agentur Dortmund",
    "Core Web Vitals",
    "PageSpeed Insights",
    "Screaming Frog Analysis",
    "Page Authority",
    "Keyword Density"
  ],
  "inputs": {
    "urlListFile": "urls.txt",
    "screamingFrogExports": {
      "internalHtmlCsv": "screamingfrog_internal_html.csv",
      "responseCodesCsv": "screamingfrog_response_codes.csv"
    }
  },
  "outputs": {
    "keywordReportCsv": "keyword_density_report.csv",
    "summaryMarkdown": "audit_summary.md"
  },
  "notes": "Runbook:\n1) Export URLs from Screaming Frog\n2) Run Python keyword density script\n3) Validate PSI (Field + Lab) per template\n4) Prioritize fixes by conversion impact\n"
}

Schritt 2: TypeScript Runner, der die JSON-Config lädt und Checks ausführt

Das folgende TypeScript-Beispiel liest die Config, validiert Basiswerte und erstellt eine simple Audit-Zusammenfassung (als Ausgangspunkt für Automatisierung).

import { readFile, writeFile } from "node:fs/promises";

type CoreWebVitalsThresholds = {
  lcpSeconds: number;
  inpMilliseconds: number;
  cls: number;
};

type Config = {
  project: {
    name: string;
    targetUrl: string;
    primaryAnchor: string;
    location: string;
  };
  thresholds: {
    coreWebVitals: CoreWebVitalsThresholds;
    keywordDensity: {
      warningAbovePercent: number;
      minimumWords: number;
    };
  };
  keywords: string[];
  inputs: {
    urlListFile: string;
    screamingFrogExports: {
      internalHtmlCsv: string;
      responseCodesCsv: string;
    };
  };
  outputs: {
    keywordReportCsv: string;
    summaryMarkdown: string;
  };
  notes?: string;
};

function validateConfig(cfg: Config): string[] {
  const issues: string[] = [];

  if (!cfg.project.targetUrl.startsWith("/")) {
    issues.push("project.targetUrl should be a path starting with '/'.");
  }

  const cwv = cfg.thresholds.coreWebVitals;
  if (cwv.lcpSeconds <= 0 || cwv.inpMilliseconds <= 0 || cwv.cls <= 0) {
    issues.push("Core Web Vitals thresholds must be positive numbers.");
  }

  if (cfg.keywords.length === 0) {
    issues.push("keywords must not be empty.");
  }

  return issues;
}

async function main() {
  const raw = await readFile("seo-tools.config.json", "utf-8");
  const cfg = JSON.parse(raw) as Config;

  const issues = validateConfig(cfg);

  const lines: string[] = [];
  lines.push(`# ${cfg.project.name}`);
  lines.push("");
  lines.push(`**Standort:** ${cfg.project.location}`);
  lines.push(`**Zielseite:** ${cfg.project.targetUrl} (Anchor: ${cfg.project.primaryAnchor})`);
  lines.push("");

  lines.push("Schwellenwerte (Core Web Vitals)");
  lines.push(`- LCP ≤ ${cfg.thresholds.coreWebVitals.lcpSeconds}s`);
  lines.push(`- INP ≤ ${cfg.thresholds.coreWebVitals.inpMilliseconds}ms`);
  lines.push(`- CLS ≤ ${cfg.thresholds.coreWebVitals.cls}`);
  lines.push("");

  lines.push("Keyword-Set");
  for (const kw of cfg.keywords) {
    lines.push(`- ${kw}`);
  }
  lines.push("");

  if (issues.length) {
    lines.push("Config-Issues");
    for (const issue of issues) {
      lines.push(`- ${issue}`);
    }
    lines.push("");
  } else {
    lines.push("Status");
    lines.push("- Config looks valid. Next: run PSI checks and Screaming Frog exports.");
    lines.push("");
  }

  if (cfg.notes) {
    lines.push("Notes");
    lines.push(cfg.notes);
  }

  await writeFile(cfg.outputs.summaryMarkdown, lines.join("\n"), "utf-8");
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

7) Visual: Audit-Flow (von Crawl → PSI → Fix → Re-Check)

graph TD
  A[Screaming Frog Analysis: Crawl & Exporte] --> B[Technische Findings: Indexability, Redirects, Linktiefe]
  B --> C[Performance Hypothesen: LCP/INP/CLS Treiber]
  C --> D[PageSpeed Insights: Field + Lab Validierung]
  D --> E[Backlog: Quick Wins + Template-Fixes]
  E --> F[Deploy + Monitoring: Core Web Vitals]
  F --> G[SEO Impact: Rankings, CTR, Conversions]
  G --> A

PageSpeed Insights Screenshot einer Dortmunder Landingpage mit Hervorhebung von LCP/INP/CLS und „Opportunities“
PageSpeed Insights Screenshot einer Dortmunder Landingpage mit Hervorhebung von LCP/INP/CLS und „Opportunities“

Screaming Frog Screenshot mit Export „Response Codes“ und „Internal HTML“ inkl. Filter auf 3xx/4xx
Screaming Frog Screenshot mit Export „Response Codes“ und „Internal HTML“ inkl. Filter auf 3xx/4xx

Häufig gestellte Fragen

Autor

Erol Demirkoparan

Erol Demirkoparan

Senior Software Architect

Full-Stack & Cloud-Native Systems Expert. Spezialisiert auf AWS, Next.js und skalierbare SaaS-Architekturen. Building the future of automated SEO.

AWSNext.jsScalable SaaSSystem Architecture

Veröffentlicht am

9. Januar 2026

Das könnte Sie auch interessieren