{% extends "base.html" %} {% block title %}Audyt SEO - {{ company.name }} - Norda Biznes Partner{% endblock %} {% block extra_css %} {% endblock %} {% block content %}

Audyt SEO Strony WWW

{{ company.name }}

Analiza SEO i wydajności strony WWW (Google PageSpeed Insights)
{% if company.website %} {% endif %}
Profil firmy {% if can_audit and company.website %} {% endif %}
{% if seo_data %} {# Unified 5-level color scale: 0-29 red, 30-49 orange, 50-69 amber, 70-89 lime, 90-100 green #} {% set score = seo_data.seo_score or 0 %}
{{ score }} / 100
{% if score >= 90 %} Doskonały wynik SEO (Google Lighthouse) {% elif score >= 70 %} Dobry wynik SEO (Google Lighthouse) {% elif score >= 50 %} Przeciętny wynik SEO (Google Lighthouse) {% elif score >= 30 %} Wynik SEO wymaga poprawy (Google Lighthouse) {% else %} Słaby wynik SEO (Google Lighthouse) {% endif %}

{% if score >= 90 %} Strona jest bardzo dobrze zoptymalizowana pod kątem SEO. Utrzymuj wysoki standard i monitoruj zmiany. {% elif score >= 70 %} Strona ma dobrą optymalizację SEO, ale są obszary do poprawy. Skup się na wydajności i dostępności. {% elif score >= 50 %} Strona wymaga pracy nad optymalizacją SEO. Warto poprawić wydajność i dostępność. {% else %} Strona ma poważne problemy z SEO. Priorytetowo popraw wydajność i optymalizację. {% endif %}

Wynik pochodzi z Google PageSpeed Insights i ocenia techniczne aspekty SEO (meta tagi, robots.txt, indeksowalność). Pełna ocena SEO, włączając lokalne SEO i widoczność, jest dostępna w analizie AI poniżej.

Ostatni audyt: {{ seo_data.audited_at.strftime('%d.%m.%Y %H:%M') if seo_data.audited_at else 'Brak danych' }}
{% if seo_data.url %}
{{ seo_data.url|truncate(40) }}
{% endif %}

Szczegółowe metryki

{% set seo = seo_data.seo_score %} {% set seo_class = 'good' if seo and seo >= 90 else ('medium' if seo and seo >= 50 else ('poor' if seo else 'none')) %}
Wynik SEO
{{ seo if seo else '-' }}
{% set perf = seo_data.performance_score %} {% set perf_class = 'good' if perf and perf >= 90 else ('medium' if perf and perf >= 50 else ('poor' if perf else 'none')) %}
Wydajność
{{ perf if perf else '-' }}
{% set acc = seo_data.accessibility_score %} {% set acc_class = 'good' if acc and acc >= 90 else ('medium' if acc and acc >= 50 else ('poor' if acc else 'none')) %}
Dostępność
{{ acc if acc else '-' }}
{% set bp = seo_data.best_practices_score %} {% set bp_class = 'good' if bp and bp >= 90 else ('medium' if bp and bp >= 50 else ('poor' if bp else 'none')) %}
Best Practices
{{ bp if bp else '-' }}
{% set findings_critical = [] %} {% set findings_important = [] %} {% set findings_improvement = [] %} {# --- KRYTYCZNE --- #} {% if seo_data.has_ssl == false %} {% set _ = findings_critical.append({'title': 'Brak certyfikatu SSL', 'desc': 'Strona nie jest zabezpieczona protokołem HTTPS. Zainstaluj certyfikat SSL.'}) %} {% endif %} {% if seo_data.seo_score is not none and seo_data.seo_score < 30 %} {% set _ = findings_critical.append({'title': 'Bardzo niski wynik SEO (' ~ seo_data.seo_score ~ '/100)', 'desc': 'Wynik SEO poniżej 30/100 wymaga pilnej interwencji.'}) %} {% endif %} {% if seo_data.is_indexable == false %} {% set _ = findings_critical.append({'title': 'Strona zablokowana przed indeksowaniem', 'desc': 'Wyszukiwarki nie mogą indeksować strony. Sprawdź meta robots i robots.txt.'}) %} {% endif %} {% if seo_data.performance_score is not none and seo_data.performance_score < 30 %} {% set _ = findings_critical.append({'title': 'Krytycznie niska wydajność (' ~ seo_data.performance_score ~ '/100)', 'desc': 'Strona ładuje się bardzo wolno. Zoptymalizuj obrazy i kod.'}) %} {% endif %} {# --- WAŻNE --- #} {% if not seo_data.meta_title %} {% set _ = findings_important.append({'title': 'Brak meta title', 'desc': 'Strona nie ma ustawionego tytułu. Dodaj meta title (50-60 znaków).'}) %} {% elif seo_data.meta_title|length < 30 %} {% set _ = findings_important.append({'title': 'Meta title za krótki (' ~ seo_data.meta_title|length ~ ' zn.)', 'desc': 'Optymalnie 50-60 znaków. Aktualny tytuł jest zbyt krótki.'}) %} {% elif seo_data.meta_title|length > 70 %} {% set _ = findings_important.append({'title': 'Meta title za długi (' ~ seo_data.meta_title|length ~ ' zn.)', 'desc': 'Optymalnie 50-60 znaków. Aktualny tytuł jest zbyt długi i zostanie obcięty w wynikach.'}) %} {% endif %} {% if not seo_data.meta_description %} {% set _ = findings_important.append({'title': 'Brak meta description', 'desc': 'Strona nie ma opisu. Dodaj meta description (150-160 znaków).'}) %} {% elif seo_data.meta_description|length < 120 %} {% set _ = findings_important.append({'title': 'Meta description za krótki (' ~ seo_data.meta_description|length ~ ' zn.)', 'desc': 'Optymalnie 150-160 znaków. Aktualny opis jest zbyt krótki.'}) %} {% elif seo_data.meta_description|length > 180 %} {% set _ = findings_important.append({'title': 'Meta description za długi (' ~ seo_data.meta_description|length ~ ' zn.)', 'desc': 'Optymalnie 150-160 znaków. Opis zostanie obcięty w wynikach wyszukiwania.'}) %} {% endif %} {% if seo_data.has_sitemap == false %} {% set _ = findings_important.append({'title': 'Brak sitemap.xml', 'desc': 'Dodaj mapę witryny, aby ułatwić wyszukiwarkom indeksowanie strony.'}) %} {% endif %} {% if seo_data.has_robots_txt == false %} {% set _ = findings_important.append({'title': 'Brak robots.txt', 'desc': 'Dodaj plik robots.txt z instrukcjami dla robotów wyszukiwarek.'}) %} {% endif %} {% if seo_data.h1_count is not none and seo_data.h1_count == 0 %} {% set _ = findings_important.append({'title': 'Brak nagłówka H1', 'desc': 'Każda strona powinna mieć dokładnie jeden nagłówek H1.'}) %} {% elif seo_data.h1_count is not none and seo_data.h1_count > 1 %} {% set _ = findings_important.append({'title': 'Wiele nagłówków H1 (' ~ seo_data.h1_count ~ ')', 'desc': 'Strona ma ' ~ seo_data.h1_count ~ ' nagłówków H1, powinien być jeden.'}) %} {% endif %} {% if seo_data.images_without_alt is not none and seo_data.images_without_alt > 0 %} {% set _ = findings_important.append({'title': 'Obrazy bez atrybutu alt (' ~ seo_data.images_without_alt ~ ')', 'desc': '' ~ seo_data.images_without_alt ~ ' obrazów nie ma opisu alternatywnego. Dodaj atrybuty alt.'}) %} {% endif %} {% if seo_data.lcp_ms is not none and seo_data.lcp_ms > 4000 %} {% set _ = findings_important.append({'title': 'Wolne ładowanie (LCP ' ~ '%.1f'|format(seo_data.lcp_ms / 1000) ~ 's)', 'desc': 'Largest Contentful Paint przekracza 4 sekundy. Cel: poniżej 2.5s.'}) %} {% endif %} {% if seo_data.inp_ms is not none and seo_data.inp_ms > 500 %} {% set _ = findings_important.append({'title': 'Niska interaktywność (INP ' ~ seo_data.inp_ms ~ 'ms)', 'desc': 'Interaction to Next Paint powinien być poniżej 200ms.'}) %} {% endif %} {% if seo_data.has_local_business_schema == false %} {% set _ = findings_important.append({'title': 'Brak schematu LocalBusiness', 'desc': 'Dodaj dane strukturalne Schema.org dla firmy lokalnej.'}) %} {% endif %} {% if seo_data.security_headers_count is not none and seo_data.security_headers_count < 2 %} {% set _ = findings_important.append({'title': 'Brak nagłówków bezpieczeństwa (' ~ seo_data.security_headers_count ~ '/4)', 'desc': 'Dodaj HSTS, CSP, X-Frame-Options, X-Content-Type-Options.'}) %} {% endif %} {# --- DO POPRAWY --- #} {% if seo_data.has_google_analytics == false %} {% set _ = findings_improvement.append({'title': 'Brak Google Analytics', 'desc': 'Nie monitorujesz ruchu na stronie. Zainstaluj GA4.'}) %} {% endif %} {% if seo_data.has_og_tags == false %} {% set _ = findings_improvement.append({'title': 'Brak tagów Open Graph', 'desc': 'Linki do strony nie będą ładnie wyglądać w mediach społecznościowych.'}) %} {% endif %} {% if seo_data.has_twitter_cards == false %} {% set _ = findings_improvement.append({'title': 'Brak Twitter Cards', 'desc': 'Brak tagów Twitter Card dla lepszego wyglądu linków na X.'}) %} {% endif %} {% if seo_data.has_canonical == false %} {% set _ = findings_improvement.append({'title': 'Brak tagu canonical', 'desc': 'Ustaw canonical URL, aby uniknąć duplikatów w indeksie.'}) %} {% endif %} {% if seo_data.has_google_maps_embed == false %} {% set _ = findings_improvement.append({'title': 'Brak mapy Google na stronie', 'desc': 'Osadzenie mapy Google Maps pomaga w lokalnym SEO.'}) %} {% endif %} {% if seo_data.nap_on_website == false %} {% set _ = findings_improvement.append({'title': 'Brak danych NAP na stronie', 'desc': 'Dodaj nazwę firmy, adres i telefon (NAP) na stronie.'}) %} {% endif %} {% if seo_data.content_freshness_score is not none and seo_data.content_freshness_score < 40 %} {% set _ = findings_improvement.append({'title': 'Nieaktualna treść (wynik: ' ~ seo_data.content_freshness_score ~ ')', 'desc': 'Treść strony nie była aktualizowana od dłuższego czasu.'}) %} {% endif %} {% if seo_data.modern_image_ratio is not none and seo_data.modern_image_ratio < 40 %} {% set _ = findings_improvement.append({'title': 'Stare formaty obrazów (' ~ '%.0f'|format(seo_data.modern_image_ratio) ~ '% nowoczesnych)', 'desc': 'Konwertuj obrazy do formatów WebP lub AVIF dla lepszej wydajności.'}) %} {% endif %} {% if seo_data.word_count_homepage is not none and seo_data.word_count_homepage < 300 %} {% set _ = findings_improvement.append({'title': 'Mało treści na stronie (' ~ seo_data.word_count_homepage ~ ' słów)', 'desc': 'Strona ma mało treści. Zalecane minimum to 300 słów.'}) %} {% endif %} {% set total_findings = findings_critical|length + findings_important|length + findings_improvement|length %}

Znalezione problemy

{{ total_findings }}

Na podstawie audytu SEO — najważniejsze do naprawienia

{% if total_findings == 0 %}
Nie znaleziono krytycznych problemów. Twoja strona jest dobrze zoptymalizowana.
{% else %} {% if findings_critical|length > 0 %}
Krytyczne ({{ findings_critical|length }})
{% for f in findings_critical %}
!
{{ f.title }}
{{ f.desc }}
{% endfor %}
{% endif %} {% if findings_important|length > 0 %}
Ważne ({{ findings_important|length }})
{% for f in findings_important %}
!
{{ f.title }}
{{ f.desc }}
{% endfor %}
{% endif %} {% if findings_improvement|length > 0 %}
Do poprawy ({{ findings_improvement|length }})
{% for f in findings_improvement %}
!
{{ f.title }}
{{ f.desc }}
{% endfor %}
{% endif %} {% endif %}
{% if seo_data.lcp_ms is not none or seo_data.inp_ms is not none or seo_data.cls is not none %}

Core Web Vitals

{% if seo_data.lcp_ms is not none %} {% set lcp = seo_data.lcp_ms %} {% set lcp_class = 'good' if lcp < 2500 else ('medium' if lcp < 4000 else 'poor') %}
LCP
{{ '%.1f'|format(lcp / 1000) }}s
Largest Contentful Paint
{% endif %} {% if seo_data.inp_ms is not none %} {% set inp = seo_data.inp_ms %} {% set inp_class = 'good' if inp <= 200 else ('medium' if inp <= 500 else 'poor') %}
INP
{{ inp }}ms
Interaction to Next Paint
{% endif %} {% if seo_data.cls is not none %} {% set cls = seo_data.cls %} {% set cls_class = 'good' if cls < 0.1 else ('medium' if cls < 0.25 else 'poor') %}
CLS
{{ '%.3f'|format(cls) }}
Cumulative Layout Shift
{% endif %}
{% endif %} {% if seo_data.crux_lcp_ms is not none %}

Dane z Chrome UX Report (realni użytkownicy)

Metryki p75 z realnych sesji użytkowników Chrome (ostatnie 28 dni)

{% set crux_lcp = seo_data.crux_lcp_ms %} {% set crux_lcp_class = 'good' if crux_lcp <= 2500 else ('medium' if crux_lcp <= 4000 else 'poor') %}
LCP (Field)
{{ '%.1f'|format(crux_lcp / 1000) }}s
{% if seo_data.crux_lcp_good_pct is not none %}
{{ '%.0f'|format(seo_data.crux_lcp_good_pct) }}% dobrych
{% endif %}
{% if seo_data.crux_inp_ms is not none %} {% set crux_inp = seo_data.crux_inp_ms %} {% set crux_inp_class = 'good' if crux_inp <= 200 else ('medium' if crux_inp <= 500 else 'poor') %}
INP (Field)
{{ crux_inp }}ms
{% if seo_data.crux_inp_good_pct is not none %}
{{ '%.0f'|format(seo_data.crux_inp_good_pct) }}% dobrych
{% endif %}
{% endif %} {% if seo_data.crux_cls is not none %} {% set crux_cls = seo_data.crux_cls %} {% set crux_cls_class = 'good' if crux_cls < 0.1 else ('medium' if crux_cls < 0.25 else 'poor') %}
CLS (Field)
{{ '%.3f'|format(crux_cls) }}
{% endif %} {% if seo_data.crux_fcp_ms is not none %} {% set crux_fcp = seo_data.crux_fcp_ms %} {% set crux_fcp_class = 'good' if crux_fcp <= 1800 else ('medium' if crux_fcp <= 3000 else 'poor') %}
FCP (Field)
{{ '%.1f'|format(crux_fcp / 1000) }}s
{% endif %} {% if seo_data.crux_ttfb_ms is not none %} {% set crux_ttfb = seo_data.crux_ttfb_ms %} {% set crux_ttfb_class = 'good' if crux_ttfb <= 800 else ('medium' if crux_ttfb <= 1800 else 'poor') %}
TTFB (Field)
{{ crux_ttfb }}ms
{% endif %}
{% endif %} {% if seo_data.gsc_clicks is not none %}

Google Search Console OAuth

Dane z Google Search za ostatnie {{ seo_data.gsc_period_days or 28 }} dni

Kliknięcia
{{ '{:,}'.format(seo_data.gsc_clicks)|replace(',', ' ') }}
Wyświetlenia
{{ '{:,}'.format(seo_data.gsc_impressions)|replace(',', ' ') }}
{% if seo_data.gsc_ctr is not none %}
CTR
{{ '%.1f'|format(seo_data.gsc_ctr) }}%
{% endif %} {% if seo_data.gsc_avg_position is not none %}
Średnia pozycja
{{ '%.1f'|format(seo_data.gsc_avg_position) }}
{% endif %}
{% if seo_data.gsc_top_queries %}

Top zapytania w Google

{% for q in seo_data.gsc_top_queries[:5] %} {% endfor %}
Zapytanie Kliknięcia Wyświetlenia CTR Pozycja
{{ q.query }} {{ q.clicks }} {{ q.impressions }} {{ '%.1f'|format(q.ctr) }}% {{ '%.1f'|format(q.position) }}
{% endif %} {% if seo_data.gsc_top_pages %}

Top strony w Google

{% for p in seo_data.gsc_top_pages[:10] %} {% endfor %}
Strona Klikniecia Wyswietlenia
{{ p.page|replace('https://', '')|replace('http://', '') }} {{ p.clicks }} {{ p.impressions }}
{% endif %} {% if seo_data.gsc_device_breakdown %}

Urzadzenia

{% set devices = seo_data.gsc_device_breakdown %} {% set total_clicks = (devices.get('desktop', {}).get('clicks', 0) or 0) + (devices.get('mobile', {}).get('clicks', 0) or 0) + (devices.get('tablet', {}).get('clicks', 0) or 0) %}
{% for device_name, device_data in devices.items() %} {% set pct = ((device_data.clicks / total_clicks * 100)|round(1)) if total_clicks > 0 else 0 %}
{{ pct }}%
{{ device_name|capitalize }}
{{ device_data.clicks }} klik.
{% endfor %}
{% endif %} {% if seo_data.gsc_trend_data %}

Trend (vs poprzedni okres)

{% set trend = seo_data.gsc_trend_data %} {% for metric_name, metric_label in [('clicks', 'Klikniecia'), ('impressions', 'Wyswietlenia'), ('ctr', 'CTR'), ('position', 'Pozycja')] %} {% if trend.get(metric_name) %} {% set m = trend[metric_name] %} {% set change = m.get('change_pct') %}
{{ metric_label }}
{% if metric_name == 'ctr' %}{{ '%.1f'|format(m.current) }}% {% elif metric_name == 'position' %}{{ '%.1f'|format(m.current) }} {% else %}{{ '{:,}'.format(m.current)|replace(',', ' ') }}{% endif %}
{% if change is not none %}
{{ '+' if change > 0 else '' }}{{ '%.1f'|format(change) }}%
{% endif %}
{% endif %} {% endfor %}
{% endif %} {% if seo_data.gsc_country_breakdown %}

Kraje

{% for c in seo_data.gsc_country_breakdown[:5] %} {{ c.country }}: {{ c.clicks }} klik. {% endfor %}
{% endif %} {% if seo_data.gsc_search_type_breakdown %}

Typ wyszukiwania

{% for type_name, type_data in seo_data.gsc_search_type_breakdown.items() %}
{{ type_data.clicks }}
{{ type_name|capitalize }}
{% endfor %}
{% endif %} {% if seo_data.gsc_index_status %}

Status indeksowania (URL Inspection)

{{ 'Zaindeksowana' if seo_data.gsc_index_status == 'PASS' else 'Oczekuje' if seo_data.gsc_index_status == 'NEUTRAL' else seo_data.gsc_index_status }}
{% if seo_data.gsc_last_crawl %} Ostatni crawl: {{ seo_data.gsc_last_crawl.strftime('%Y-%m-%d') if seo_data.gsc_last_crawl.strftime is defined else seo_data.gsc_last_crawl[:10] }} {% endif %} {% if seo_data.gsc_crawled_as %} Bot: {{ seo_data.gsc_crawled_as }} {% endif %}
{% endif %} {% if seo_data.gsc_sitemaps %}

Sitemaps w Search Console

{% for sm in seo_data.gsc_sitemaps %} {% endfor %}
Sciezka Bledow Ostrzezen Ostatnio
{{ sm.path|replace('https://', '')|replace('http://', '') }} {{ sm.errors or 0 }} {{ sm.warnings or 0 }} {{ sm.last_submitted[:10] if sm.last_submitted else '-' }}
{% endif %}
{% elif has_gsc_token %}

Google Search Console OAuth

Konto połączone, ale brak danych

Twoja strona może nie być jeszcze dodana do Google Search Console. Wykonaj poniższe kroki:

  1. Wejdź na search.google.com/search-console
  2. Kliknij Dodaj właściwość i wpisz adres strony: {{ company.website or '' }}
  3. Zweryfikuj właściwość (najłatwiej przez rekord DNS TXT lub tag HTML)
  4. Poczekaj 2-3 dni aż Google zbierze pierwsze dane
  5. Uruchom ponownie audyt SEO — dane pojawią się tutaj automatycznie
{% else %}

Połącz Google Search Console aby zobaczyć dane o widoczności w wyszukiwarce

Połącz Search Console
{% endif %} {% if seo_data.local_seo_score is not none %}

Local SEO

{% set lscore = seo_data.local_seo_score %}
{{ lscore }}
{% if lscore >= 70 %}Dobry Local SEO{% elif lscore >= 40 %}Przeciętny Local SEO{% else %}Słaby Local SEO{% endif %}

Ocena optymalizacji pod lokalne wyszukiwanie

{{ '✓' if seo_data.has_local_business_schema else '✗' }} Schema.org LocalBusiness
{{ '✓' if seo_data.has_google_maps_embed else '✗' }} Mapa Google na stronie
{{ '✓' if seo_data.has_local_keywords else '✗' }} Lokalne słowa kluczowe
{% if seo_data.nap_on_website %}
NAP na stronie (Nazwa, Adres, Telefon)
{% else %}
NAP na stronie (Nazwa, Adres, Telefon)
{% endif %}
{% if seo_data.local_keywords_found %}
Znalezione słowa kluczowe:
{% for kw in seo_data.local_keywords_found[:10] %} {{ kw }} {% endfor %}
{% endif %}
{% endif %} {% if seo_data.content_freshness_score is not none %}
{% set fresh = seo_data.content_freshness_score %} {% set fresh_class = 'good' if fresh >= 70 else ('medium' if fresh >= 40 else 'poor') %}
Świeżość treści
{{ fresh }}
{% if seo_data.last_modified_date %}
Ostatnia modyfikacja
{{ seo_data.last_modified_date.strftime('%d.%m.%Y') }}
{% endif %}
{% endif %} {% if seo_data.citations and seo_data.citations|length > 0 %}

Cytacje lokalne ({{ seo_data.citations_count or seo_data.citations|length }})

{% for citation in seo_data.citations %}
{{ citation.directory_name }}
{% if citation.status == 'found' %} Znaleziono {% if citation.listing_url %} Link {% endif %} {% elif citation.status == 'incorrect' %} Błędne dane {% else %} Nie znaleziono {% endif %}
{% endfor %}
{% endif %} {% if seo_data.meta_title or seo_data.meta_description or seo_data.load_time_ms is not none or seo_data.word_count_homepage is not none %}

Meta Tagi i Treść

{% if seo_data.meta_title %} {% set title_len = seo_data.meta_title|length %} {% set title_status = 'good' if title_len >= 50 and title_len <= 60 else ('medium' if title_len >= 30 and title_len <= 70 else 'poor') %}
Meta Title {{ title_len }} znaków {% if title_status == 'good' %}(idealnie){% elif title_len < 50 %}(za krótki){% else %}(za długi){% endif %}
{{ seo_data.meta_title }}
{% endif %} {% if seo_data.meta_description %} {% set desc_len = seo_data.meta_description|length %} {% set desc_status = 'good' if desc_len >= 150 and desc_len <= 160 else ('medium' if desc_len >= 120 and desc_len <= 180 else 'poor') %}
Meta Description {{ desc_len }} znaków {% if desc_status == 'good' %}(idealnie){% elif desc_len < 150 %}(za krótki){% else %}(za długi){% endif %}
{{ seo_data.meta_description }}
{% endif %} {% if seo_data.load_time_ms is not none or seo_data.word_count_homepage is not none %}
{% if seo_data.load_time_ms is not none %} {% set lt = seo_data.load_time_ms %} {% set lt_status = 'good' if lt < 1000 else ('medium' if lt < 3000 else 'poor') %}
Czas ładowania
{{ '%.1f'|format(lt / 1000) }}s
{% endif %} {% if seo_data.word_count_homepage is not none %} {% set wc = seo_data.word_count_homepage %} {% set wc_status = 'good' if wc >= 300 else ('medium' if wc >= 100 else 'poor') %}
Liczba słów
{{ wc }}
{% endif %}
{% endif %}
{% endif %} {% if seo_data.has_ssl is not none or seo_data.has_google_analytics is not none or seo_data.h1_count is not none or seo_data.total_images is not none %}

Technical SEO

{% if seo_data.has_ssl is not none %}
{{ '✓' if seo_data.has_ssl else '✗' }} Certyfikat SSL{% if seo_data.has_ssl and seo_data.ssl_expires_at %} (ważny do {{ seo_data.ssl_expires_at.strftime('%d.%m.%Y') }}){% endif %}
{% endif %} {% if seo_data.has_google_analytics is not none %}
{{ '✓' if seo_data.has_google_analytics else '✗' }} Google Analytics
{% endif %} {% if seo_data.has_google_tag_manager is not none %}
{{ '✓' if seo_data.has_google_tag_manager else '✗' }} Google Tag Manager
{% endif %} {% if seo_data.has_og_tags is not none %}
{{ '✓' if seo_data.has_og_tags else '✗' }} Open Graph Tags
{% endif %} {% if seo_data.has_twitter_cards is not none %}
{{ '✓' if seo_data.has_twitter_cards else '✗' }} Twitter Cards
{% endif %} {% if seo_data.h1_count is not none %} {% set h1_ok = seo_data.h1_count == 1 %}
{{ '✓' if h1_ok else '⚠' }} H1: {{ seo_data.h1_count }}{% if not h1_ok %} (powinien być 1){% endif %}{% if seo_data.h2_count is not none %}, H2: {{ seo_data.h2_count }}{% endif %}{% if seo_data.h3_count is not none %}, H3: {{ seo_data.h3_count }}{% endif %}
{% endif %} {% if seo_data.total_images is not none %} {% set alt_ok = (seo_data.images_without_alt or 0) == 0 %}
{{ '✓' if alt_ok else '⚠' }} Obrazy: {{ seo_data.total_images }}{% if not alt_ok %} ({{ seo_data.images_without_alt }} bez alt){% else %} (wszystkie z alt){% endif %}
{% endif %} {% if seo_data.internal_links_count is not none or seo_data.external_links_count is not none %} {% set broken = seo_data.broken_links_count or 0 %}
{{ '✓' if broken == 0 else '✗' }} Linki: {{ seo_data.internal_links_count or 0 }} wew., {{ seo_data.external_links_count or 0 }} zew.{% if broken > 0 %}, {{ broken }} uszkodzonych{% endif %}
{% endif %} {% if seo_data.has_sitemap is not none %}
{{ '✓' if seo_data.has_sitemap else '✗' }} Sitemap XML
{% endif %} {% if seo_data.has_robots_txt is not none %}
{{ '✓' if seo_data.has_robots_txt else '✗' }} Robots.txt
{% endif %} {% if seo_data.has_canonical is not none %}
{{ '✓' if seo_data.has_canonical else '✗' }} Canonical URL{% if seo_data.has_canonical and seo_data.canonical_url %}: {{ seo_data.canonical_url[:60] }}{% if seo_data.canonical_url|length > 60 %}...{% endif %}{% endif %}
{% endif %} {% if seo_data.is_indexable is not none %}
{{ '✓' if seo_data.is_indexable else '✗' }} Indeksowalność{% if not seo_data.is_indexable and seo_data.noindex_reason %} ({{ seo_data.noindex_reason }}){% endif %}
{% endif %} {% if seo_data.is_mobile_friendly is not none %}
{{ '✓' if seo_data.is_mobile_friendly else '✗' }} Mobile Friendly
{% endif %} {% if seo_data.viewport_configured is not none %}
{{ '✓' if seo_data.viewport_configured else '✗' }} Viewport Meta Tag
{% endif %} {% if seo_data.html_lang %}
HTML Lang: {{ seo_data.html_lang }}
{% endif %} {% if seo_data.has_hreflang is not none %}
{{ '✓' if seo_data.has_hreflang else '—' }} Hreflang{% if not seo_data.has_hreflang %} (opcjonalne){% endif %}
{% endif %} {% if seo_data.has_structured_data is not none %}
{{ '✓' if seo_data.has_structured_data else '✗' }} Dane strukturalne{% if seo_data.has_structured_data and seo_data.structured_data_types %}: {{ seo_data.structured_data_types|join(', ') }}{% endif %}
{% endif %}
{% if seo_data.h1_text %}
Tytuł H1: {{ seo_data.h1_text }}
{% endif %}
{% endif %} {% if seo_data.has_hsts is not none %}

Nagłówki bezpieczeństwa

{% set sec_count = seo_data.security_headers_count or 0 %}
{{ sec_count }}/4
{% if sec_count == 4 %}Wszystkie nagłówki bezpieczeństwa skonfigurowane{% elif sec_count >= 2 %}Częściowa ochrona — brakuje {{ 4 - sec_count }} nagłówków{% else %}Słaba ochrona — wymagana konfiguracja{% endif %}
{{ '✓' if seo_data.has_hsts else '✗' }} Strict-Transport-Security (HSTS)
{{ '✓' if seo_data.has_csp else '✗' }} Content-Security-Policy (CSP)
{{ '✓' if seo_data.has_x_frame_options else '✗' }} X-Frame-Options
{{ '✓' if seo_data.has_x_content_type_options else '✗' }} X-Content-Type-Options
{% endif %} {% if seo_data.modern_image_ratio is not none %}

Optymalizacja obrazów

{% set ratio = seo_data.modern_image_ratio %} {% set ratio_class = 'good' if ratio >= 80 else ('medium' if ratio >= 40 else 'poor') %}
{{ '%.0f'|format(ratio) }}%
nowoczesnych formatów
{% if seo_data.modern_image_count is not none %} WebP/AVIF/SVG: {{ seo_data.modern_image_count }} {% endif %} {% if seo_data.legacy_image_count is not none %} JPG/PNG/GIF: {{ seo_data.legacy_image_count }} {% endif %}
{% endif %} {% else %}

Brak danych audytu SEO

{% if company.website %}

Nie przeprowadzono jeszcze audytu SEO dla strony tej firmy. Uruchom audyt, aby sprawdzić optymalizację strony.

{% if can_audit %} {% endif %} {% else %}

Ta firma nie ma zdefiniowanej strony WWW. Dodaj adres strony w profilu firmy, aby móc przeprowadzić audyt SEO.

Przejdź do profilu firmy {% endif %}
{% endif %} {% if seo_data %} {% with audit_type='seo' %} {% include 'partials/audit_ai_actions.html' %} {% endwith %} {% endif %}

Audyt SEO w toku...

Analiza strony może potrwać do 30 sekund

Pobieranie strony i walidacja HTTP
Analiza on-page SEO (meta tagi, nagłówki, obrazy)
Technical SEO (robots.txt, sitemap, canonical)
Google PageSpeed Insights
Analiza Local SEO
Sprawdzanie katalogów firm
Analiza aktualności treści
Google Search Console
Obliczanie wyniku końcowego
{% endblock %} {% block extra_js %} const csrfToken = '{{ csrf_token() }}'; const companySlug = '{{ company.slug }}'; const hasGscToken = {{ 'true' if has_gsc_token else 'false' }}; /* ============================================================ STEPPER: Step definitions & icons ============================================================ */ const seoSteps = [ { id: 'step-fetch', label: 'Pobieranie strony i walidacja HTTP', delay: 0 }, { id: 'step-onpage', label: 'Analiza on-page SEO (meta tagi, nagłówki, obrazy)', delay: 2000 }, { id: 'step-technical', label: 'Technical SEO (robots.txt, sitemap, canonical)', delay: 5000 }, { id: 'step-pagespeed', label: 'Google PageSpeed Insights', delay: 8000 }, { id: 'step-local', label: 'Analiza Local SEO', delay: 18000 }, { id: 'step-citations', label: 'Sprawdzanie katalogów firm', delay: 20000 }, { id: 'step-freshness', label: 'Analiza aktualności treści', delay: 25000 }, { id: 'step-gsc', label: 'Google Search Console', delay: 26000 }, { id: 'step-score', label: 'Obliczanie wyniku końcowego', delay: 28000 } ]; const stepIcons = { pending: '', in_progress: '
', complete: '', error: '', skipped: '' }; let simulationTimers = []; let progressInterval = null; function updateStep(stepId, status, message) { const el = document.getElementById(stepId); if (!el) return; const iconEl = el.querySelector('.step-icon'); const textEl = el.querySelector('.step-text'); iconEl.className = 'step-icon ' + status; iconEl.innerHTML = stepIcons[status] || stepIcons.pending; textEl.className = 'step-text ' + status; if (message) textEl.textContent = message; } function resetSteps() { seoSteps.forEach((step, i) => { if (i === 0) { updateStep(step.id, 'in_progress', step.label); } else { updateStep(step.id, 'pending', step.label); } }); const bar = document.getElementById('progressBarFill'); if (bar) bar.style.width = '0%'; } function simulateSteps() { const totalDuration = 30000; const startTime = Date.now(); seoSteps.forEach((step, i) => { if (i === 0) return; // first step starts immediately as in_progress // Mark previous step complete and current step in_progress const timer = setTimeout(() => { // Complete previous step if (i > 0) { updateStep(seoSteps[i - 1].id, 'complete'); } // Start current step updateStep(step.id, 'in_progress'); }, step.delay); simulationTimers.push(timer); }); // Progress bar animation: 0% -> 90% over totalDuration progressInterval = setInterval(() => { const elapsed = Date.now() - startTime; const pct = Math.min(90, (elapsed / totalDuration) * 90); const bar = document.getElementById('progressBarFill'); if (bar) bar.style.width = pct + '%'; }, 200); } function stopSimulation() { simulationTimers.forEach(t => clearTimeout(t)); simulationTimers = []; if (progressInterval) { clearInterval(progressInterval); progressInterval = null; } } function markAllComplete() { seoSteps.forEach(step => { updateStep(step.id, 'complete'); }); const bar = document.getElementById('progressBarFill'); if (bar) bar.style.width = '100%'; } function enrichStepsFromResponse(data) { // API response structure: data.seo_audit.{pagespeed, technical, on_page, ...} const audit = data.seo_audit || {}; const pagespeed = audit.pagespeed || {}; const technical = audit.technical || {}; // Step 1: Fetch — show load time and HTTP status if (technical.load_time_ms) { const status = technical.http_status_code || 200; updateStep('step-fetch', 'complete', 'Strona pobrana (HTTP ' + status + ', ' + technical.load_time_ms + 'ms)'); } // Step 2: On-page — show SEO score from PageSpeed if (pagespeed.seo_score !== undefined && pagespeed.seo_score !== null) { updateStep('step-onpage', 'complete', 'On-page SEO: ' + pagespeed.seo_score + '/100'); } // Step 4: PageSpeed — show performance score if (pagespeed.performance_score !== undefined && pagespeed.performance_score !== null) { updateStep('step-pagespeed', 'complete', 'Wydajność: ' + pagespeed.performance_score + '/100'); } // Step 8: GSC if (!hasGscToken) { updateStep('step-gsc', 'skipped', 'Google Search Console (brak połączenia)'); } // Step 9: Overall score if (audit.overall_score !== undefined && audit.overall_score !== null) { updateStep('step-score', 'complete', 'Wynik końcowy: ' + audit.overall_score + '/100'); } } function showLoading() { resetSteps(); document.getElementById('loadingOverlay').classList.add('active'); simulateSteps(); } function hideLoading() { stopSimulation(); document.getElementById('loadingOverlay').classList.remove('active'); } function showInfoModal(title, body, isSuccess) { document.getElementById('modalTitle').textContent = title; document.getElementById('modalBody').textContent = body; const icon = document.getElementById('modalIcon'); icon.className = 'modal-icon ' + (isSuccess ? 'success' : 'info'); document.getElementById('infoModal').classList.add('active'); } function closeInfoModal() { document.getElementById('infoModal').classList.remove('active'); } document.getElementById('infoModal')?.addEventListener('click', (e) => { if (e.target.id === 'infoModal') closeInfoModal(); }); async function runAudit() { const btn = document.getElementById('runAuditBtn'); if (btn) btn.disabled = true; showLoading(); try { const response = await fetch('/api/seo/audit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ slug: companySlug }) }); const data = await response.json(); // Stop simulation, enrich with real data stopSimulation(); if (response.ok && data.success) { enrichStepsFromResponse(data); markAllComplete(); const finalScore = (data.seo_audit && data.seo_audit.overall_score) || 0; updateStep('step-score', 'complete', 'Wynik końcowy: ' + finalScore + '/100'); // Wait so user can read the completed steps await new Promise(r => setTimeout(r, 3000)); hideLoading(); showInfoModal('Audyt zakończony', 'Audyt SEO został zakończony pomyślnie. Strona zostanie odświeżona.', true); setTimeout(() => location.reload(), 1500); } else { // Mark current in_progress step as error const currentStep = seoSteps.find(s => { const el = document.getElementById(s.id); return el && el.querySelector('.step-icon.in_progress'); }); if (currentStep) { updateStep(currentStep.id, 'error', data.error || 'Błąd audytu'); } await new Promise(r => setTimeout(r, 3000)); hideLoading(); showInfoModal('Błąd', data.error || 'Wystąpił nieznany błąd podczas audytu.', false); if (btn) btn.disabled = false; } } catch (error) { stopSimulation(); hideLoading(); showInfoModal('Błąd połączenia', 'Nie udało się połączyć z serwerem: ' + error.message, false); if (btn) btn.disabled = false; } } /* ============================================================ AI AUDIT ACTIONS ============================================================ */ const companyId = {{ company.id }}; const auditType = 'seo'; function simpleMarkdown(text) { return text .replace(/&/g, '&').replace(//g, '>') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/^### (.+)$/gm, '

$1

') .replace(/^## (.+)$/gm, '

$1

') .replace(/^- (.+)$/gm, '
  • $1
  • ') .replace(/(
  • .*<\/li>)/gs, '') .replace(/\n/g, '
    '); } async function runAIAnalysis(force) { const prompt = document.getElementById('aiAnalyzePrompt'); const loading = document.getElementById('aiLoading'); const results = document.getElementById('aiResults'); const btn = document.getElementById('aiAnalyzeBtn'); if (btn) btn.disabled = true; if (prompt) prompt.style.display = 'none'; if (results) results.style.display = 'none'; if (loading) loading.style.display = 'block'; // Start timer let seconds = 0; const timerEl = document.getElementById('aiTimer'); const timerInterval = setInterval(() => { seconds++; if (timerEl) timerEl.textContent = seconds + 's'; }, 1000); try { const response = await fetch('/api/audit/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ company_id: companyId, audit_type: auditType, force: !!force }) }); const data = await response.json(); clearInterval(timerInterval); if (loading) loading.style.display = 'none'; if (data.success) { renderAIResults(data); } else { if (prompt) prompt.style.display = 'none'; if (btn) btn.disabled = false; const results = document.getElementById('aiResults'); results.innerHTML = `

    Błąd analizy AI

    ${escapeHtml(data.error || 'Nieznany błąd')}

    `; results.style.display = 'block'; } } catch (error) { clearInterval(timerInterval); if (loading) loading.style.display = 'none'; if (prompt) prompt.style.display = 'none'; if (btn) btn.disabled = false; const results = document.getElementById('aiResults'); results.innerHTML = `

    Błąd połączenia

    ${escapeHtml(error.message)}

    `; results.style.display = 'block'; } } function renderAIResults(data) { const results = document.getElementById('aiResults'); const summaryEl = document.getElementById('aiSummaryText'); const cacheInfo = document.getElementById('aiCacheInfo'); const actionsList = document.getElementById('aiActionsList'); summaryEl.textContent = data.summary || ''; if (data.cached && data.generated_at) { const d = new Date(data.generated_at); document.getElementById('aiCacheDate').textContent = d.toLocaleDateString('pl-PL') + ' ' + d.toLocaleTimeString('pl-PL', {hour:'2-digit', minute:'2-digit'}); cacheInfo.style.display = 'block'; } else { cacheInfo.style.display = 'none'; } actionsList.innerHTML = ''; const actions = data.actions || []; const priorityLabels = {critical: 'KRYTYCZNE', high: 'WYSOKI', medium: 'ŚREDNI', low: 'NISKI'}; actions.forEach((action, idx) => { const card = document.createElement('div'); card.className = 'ai-action-card priority-' + (action.priority || 'medium'); card.id = 'ai-action-' + idx; const impact = action.impact_score || 5; const effort = action.effort_score || 5; card.innerHTML = `
    ${priorityLabels[action.priority] || 'ŚREDNI'} ${escapeHtml(action.title || '')}

    ${escapeHtml(action.description || '')}

    Wpływ: ${impact}/10
    Wysiłek: ${effort}/10
    `; actionsList.appendChild(card); }); // Render comparison with previous analysis if available if (typeof renderAIComparison === 'function') renderAIComparison(data); results.style.display = 'block'; document.getElementById('aiActionsSection').scrollIntoView({behavior: 'smooth', block: 'start'}); // Store actions data for content generation window._aiActions = actions; } async function generateContent(actionType, idx) { const container = document.getElementById('ai-content-' + idx); if (!container) return; // If already has content, toggle visibility if (container.dataset.loaded === 'true') { container.style.display = container.style.display === 'none' ? 'block' : 'none'; return; } container.innerHTML = '
    Generowanie treści...
    '; container.style.display = 'block'; try { const response = await fetch('/api/audit/generate-content', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ company_id: companyId, action_type: actionType, context: {} }) }); const data = await response.json(); if (data.success && data.content) { const isCode = data.content.includes('{') && (data.content.includes(' ${escapeHtml(data.content)} `; } else { container.innerHTML = `
    ${simpleMarkdown(data.content)}
    `; } container.dataset.loaded = 'true'; container.scrollIntoView({behavior: 'smooth', block: 'nearest'}); } else { container.innerHTML = `
    ${escapeHtml(data.error || 'Błąd generowania')}
    `; } } catch (error) { container.innerHTML = `
    ${escapeHtml(error.message)}
    `; } } function copyContent(btn) { const code = btn.parentElement.querySelector('code') || btn.parentElement.querySelector('.ai-markdown-content'); if (!code) return; navigator.clipboard.writeText(code.textContent).then(() => { const orig = btn.textContent; btn.textContent = 'Skopiowano!'; setTimeout(() => { btn.textContent = orig; }, 2000); }); } function markAction(idx, status) { const card = document.getElementById('ai-action-' + idx); if (!card) return; if (status === 'implemented') { card.classList.add('implemented'); } else if (status === 'dismissed') { card.classList.add('dismissed'); } // Fire and forget status update to backend const actions = window._aiActions || []; if (actions[idx] && actions[idx].id) { fetch('/api/audit/actions/' + actions[idx].id + '/status', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ status: status }) }).catch(() => {}); } } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } {% endblock %}