🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Struktura Dokumentu HTML5 i Kluczowe Tagi Treści — Kompletny Szablon Razor Page

Kąt widzenia: co musi znaleźć się w każdym pliku HTML i dlaczego — od DOCTYPE po tagi treści — z gotowym szablonem _Layout.cshtml dla ASP.NET Core
Przykład przewodni: kompletna struktura strony produktu w sklepie


DOCTYPE — deklaracja na początku każdego pliku HTML

<!DOCTYPE html>

Ta jedna linia musi być pierwszą linią każdego pliku HTML — przed <html>, przed komentarzami, przed wszystkim.

Mówi przeglądarce: “to jest dokument HTML5, interpretuj go zgodnie ze standardem HTML5”. Bez DOCTYPE przeglądarka przełącza się w Quirks Mode — tryb kompatybilności z przeglądarkami z lat 90., który renderuje strony inaczej niż standard. Efekt: układ CSS wygląda inaczej w różnych przeglądarkach i trudno debugować.

Starsze standardy miały długie DOCTYPE:

<!-- HTML 4.01 Strict — archaiczne -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

HTML5 upraszcza to do <!DOCTYPE html>. Tylko tyle. Nie pamiętaj starszych form — HTML5 to standard od 2014 roku.


Element <html> — korzeń dokumentu

<!DOCTYPE html>
<html lang="pl">
...
</html>

Atrybut lang jest obowiązkowy w dobrej praktyce:

  • Czytniki ekranu dobierają wymowę i akcent na podstawie lang
  • Przeglądarki wyświetlają poprawny słownik dla spellcheck
  • Wyszukiwarki lepiej indeksują treść

Wartości: lang="pl" (polski), lang="en" (angielski), lang="de" (niemiecki). Pełna lista: BCP 47 language tags.

W Razor Layout (_Layout.cshtml) zawsze ustaw lang:

<html lang="@CultureInfo.CurrentCulture.TwoLetterISOLanguageName">

Sekcja <head> — metadane dokumentu

<head> zawiera informacje dla przeglądarki i robotów — nic w <head> nie jest widoczne bezpośrednio użytkownikowi (z wyjątkiem tytułu na karcie).

<meta charset="UTF-8"> — kodowanie znaków

<meta charset="UTF-8">

Pierwsza rzecz w <head> — przed jakimkolwiek tekstem. Mówi przeglądarce, jak interpretować bajty pliku. UTF-8 obsługuje polskie znaki (ą, ę, ó, ś, ź, ż, ć, ń, ł), emoji i praktycznie wszystkie języki świata.

Bez tego — polskie znaki mogą wyświetlać się jako Ä™, ó, Ĺ‚ itp. (błąd kodowania).

<meta name="viewport"> — responsywność na urządzeniach mobilnych

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Bez tego tagu przeglądarka mobilna zakłada, że strona ma szerokość 980px (jak desktop) i skaluje ją w dół — tekst staje się mikroskopijny. Z tym tagiem — strona ma szerokość urządzenia i jest czytelna.

width=device-width — szerokość viewportu = szerokość ekranu urządzenia
initial-scale=1.0 — brak domyślnego powiększenia

To jest warunek konieczny, żeby CSS media queries działały prawidłowo.

<title> — tytuł strony

<title>Klawiatura TKL Cherry MX Red — Sklep</title>

Wyświetla się w:

  • Karcie przeglądarki
  • Wynikach wyszukiwania Google (niebieski link)
  • Zakładkach (Bookmarks)
  • Udostępnieniach w mediach społecznościowych

Dobry tytuł: Temat strony — Nazwa witryny. Dla .NET:

<!-- Razor _Layout.cshtml -->
<title>@ViewData["Title"] — Sklep</title>

<!-- Razor Page lub View -->
@{
    ViewData["Title"] = "Klawiatura TKL Cherry MX Red";
}

<meta name="description"> — opis dla wyszukiwarek

<meta name="description" content="Klawiatura mechaniczna TKL z przełącznikami Cherry MX Red. Podświetlenie RGB, anti-ghosting. Cena: 299 zł. Dostawa w 24h.">

Wyświetla się jako szary opis pod tytułem w wynikach Google. Max ~160 znaków. Nie wpływa bezpośrednio na pozycję w Google, ale wpływa na klikalność (CTR).

<link> — zewnętrzne zasoby

<!-- Arkusz stylów CSS -->
<link rel="stylesheet" href="/css/style.css">

<!-- Favicon — ikona w karcie przeglądarki -->
<link rel="icon" type="image/png" href="/img/favicon.png">
<link rel="apple-touch-icon" href="/img/apple-touch-icon.png">

<!-- Preload — pobierz zasób z wyprzedzeniem -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>

<!-- Canonical — mówi Google, który URL jest "oficjalny" przy duplikatach -->
<link rel="canonical" href="https://sklep.pl/products/keyboards/tkl-mx-red">

Open Graph — podglądy w mediach społecznościowych

<meta property="og:title" content="Klawiatura TKL Cherry MX Red — Sklep">
<meta property="og:description" content="Kompaktowa klawiatura mechaniczna. Cena: 299 zł.">
<meta property="og:image" content="https://sklep.pl/img/kb-tkl-og.jpg">
<meta property="og:url" content="https://sklep.pl/products/keyboards/tkl-mx-red">
<meta property="og:type" content="product">

Te tagi decydują o tym, jak strona wygląda po udostępnieniu na Facebook, Twitter, LinkedIn, Slack. Bez nich — social media pokażą losowe zdjęcie i brak opisu.

<script> — JavaScript

<!-- ❌ W <head> bez atrybutów — blokuje renderowanie -->
<script src="/js/app.js"></script>

<!-- ✅ defer — pobiera równolegle, wykonuje po załadowaniu DOM -->
<script src="/js/app.js" defer></script>

<!-- ✅ async — pobiera równolegle, wykonuje natychmiast po pobraniu -->
<script src="/js/analytics.js" async></script>

<!-- ✅ Lub na końcu <body> — stara metoda, defer jest lepsza -->

defer — skrypt czeka na pełny DOM, wykonuje się w kolejności. Właściwy wybór dla większości skryptów.
async — skrypt wykonuje się jak najszybciej, może przerywać parsowanie HTML. Dla niezależnych skryptów (analytics, reklamy).


Kluczowe tagi treści w <body>

Nagłówki <h1><h6>

<h1>Klawiatura TKL Cherry MX Red</h1>
<h2>Specyfikacja techniczna</h2>
<h3>Przełączniki</h3>
<h3>Podświetlenie</h3>
<h2>Opinie klientów</h2>
<h3>Ocena 5/5 — Jan K.</h3>

Jeden <h1> na stronę. <h2><h6> mogą się powtarzać. Hierarchia musi być zachowana.

Akapit <p>

<p>
    Klawiatura mechaniczna TKL z przełącznikami Cherry MX Red — 
    idealna do pracy i gier. Kompaktowy układ bez bloku numerycznego 
    zostawia więcej miejsca na biurku dla myszy.
</p>

Przeglądarki dodają margines powyżej i poniżej <p> — to jest domyślny styl, można go nadpisać CSS.

Linki <a>

<!-- Link zewnętrzny -->
<a href="https://www.cherry.de/pl" 
   rel="noopener noreferrer" 
   target="_blank">
    Producent Cherry
</a>

<!-- Link wewnętrzny -->
<a href="/products/keyboards">Wróć do klawiatur</a>

<!-- Link do emaila -->
<a href="mailto:kontakt@sklep.pl">Napisz do nas</a>

<!-- Link do telefonu -->
<a href="tel:+48123456789">+48 123 456 789</a>

<!-- Link do sekcji na tej samej stronie (anchor) -->
<a href="#reviews">Przejdź do opinii</a>
<section id="reviews">...</section>

rel="noopener noreferrer" na linkach target="_blank" — blokuje dostęp otwartej strony do window.opener. To jest wymóg bezpieczeństwa — bez tego otwarta strona może przekierować oryginalną zakładkę.

Obrazy <img>

<img 
    src="/img/keyboard-tkl.jpg"
    alt="Klawiatura TKL Cherry MX Red — widok z góry na białym tle"
    width="800"
    height="600"
    loading="lazy"
>
  • altobowiązkowy. Opisuje obraz dla niewidomych i gdy obraz się nie ładuje. Dla obrazów dekoracyjnych: alt="" (pusty, ale obecny).
  • width i height — zapobiega Layout Shift (CLS) — przeglądarka rezerwuje miejsce przed załadowaniem obrazu
  • loading="lazy" — ładuj tylko gdy obraz wchodzi w viewport (poniżej fold). Dla pierwszego obrazu na stronie (above fold) — pomiń lazy.

Formatowanie tekstu

<!-- Semantyczne — mają znaczenie -->
<strong>Ważna informacja</strong>   <!-- duże znaczenie, domyślnie bold -->
<em>Klawiatura</em>                 <!-- nacisk/kursywa -->
<mark>Promocja -20%</mark>          <!-- zaznaczenie/highlight -->
<del>349 zł</del>                   <!-- przekreślony, stara cena -->
<ins>279 zł</ins>                   <!-- wstawiony tekst, nowa cena -->
<code>Console.WriteLine()</code>    <!-- fragment kodu -->
<abbr title="Ten-Key-Less">TKL</abbr>  <!-- skrót z rozwinięciem -->
<small>*Oferta ważna do 31.03.2026</small>  <!-- drobny druk -->
<sup>2</sup>                        <!-- indeks górny (m²) -->
<sub>2</sub>                        <!-- indeks dolny (H₂O) -->

<!-- Prezentacyjne — tylko wizualne, unikaj -->
<b>pogrubiony</b>    <!-- użyj strong zamiast -->
<i>kursywa</i>       <!-- użyj em zamiast, chyba że to nazwa techniczna -->

<strong> vs <b>: oba domyślnie bold, ale <strong> znaczy “ważne” (czytnik ekranu może to zaakcentować), <b> znaczy tylko “pogrubione wizualnie”.

Tabele

<table>
    <caption>Specyfikacja klawiatur mechanicznych</caption>
    <thead>
        <tr>
            <th scope="col">Model</th>
            <th scope="col">Przełączniki</th>
            <th scope="col">Układ</th>
            <th scope="col">Cena</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>TKL MX Red</td>
            <td>Cherry MX Red</td>
            <td>87 klawiszy</td>
            <td>299 zł</td>
        </tr>
        <tr>
            <td>Full-size MX Blue</td>
            <td>Cherry MX Blue</td>
            <td>104 klawisze</td>
            <td>349 zł</td>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <td colspan="4">Ceny brutto, dostawa od 19 zł</td>
        </tr>
    </tfoot>
</table>

scope="col" na <th> — mówi czytnikowi ekranu, że to nagłówek kolumny. <caption> — widoczny tytuł tabeli.

Tabele służą do danych tabelarycznych — nie do layoutu strony. Layout robimy przez CSS Grid i Flexbox.


Kompletny szablon _Layout.cshtml dla ASP.NET Core

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <title>@ViewData["Title"] — Sklep</title>
    <meta name="description" content="@ViewData["Description"]">
    
    <!-- Open Graph -->
    <meta property="og:title" content="@ViewData["Title"] — Sklep">
    <meta property="og:description" content="@ViewData["Description"]">
    <meta property="og:image" content="@ViewData["OgImage"] ?? "/img/default-og.jpg"">
    
    <!-- Favicon -->
    <link rel="icon" type="image/png" href="/img/favicon.png">
    
    <!-- Styles -->
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true">
    
    <!-- Render Section: strony mogą dodać własne style -->
    @await RenderSectionAsync("Styles", required: false)
</head>
<body>

    <header class="site-header">
        <a href="/" class="brand">
            <img src="/img/logo.svg" alt="Sklep — strona główna" width="120" height="40">
        </a>
        <nav aria-label="Nawigacja główna">
            <ul>
                <li><a href="/products" asp-area="" asp-controller="Products" asp-action="Index">Produkty</a></li>
                <li><a href="/brands">Marki</a></li>
                <li>
                    <a href="/cart" aria-label="Koszyk">
                        Koszyk
                        <span class="cart-count" id="cart-count">@ViewData["CartCount"]</span>
                    </a>
                </li>
            </ul>
        </nav>
    </header>

    <main>
        @if (TempData["SuccessMessage"] != null)
        {
            <div role="alert" class="alert alert-success">
                @TempData["SuccessMessage"]
            </div>
        }
        @if (TempData["ErrorMessage"] != null)
        {
            <div role="alert" class="alert alert-error">
                @TempData["ErrorMessage"]
            </div>
        }
        
        @RenderBody()
    </main>

    <footer class="site-footer">
        <nav aria-label="Linki stopki">
            <ul>
                <li><a href="/about">O firmie</a></li>
                <li><a href="/privacy">Polityka prywatności</a></li>
                <li><a href="/terms">Regulamin</a></li>
                <li><a href="/contact">Kontakt</a></li>
            </ul>
        </nav>
        <address>
            <a href="mailto:kontakt@sklep.pl">kontakt@sklep.pl</a>
        </address>
        <p><small>&copy; @DateTime.Now.Year Sklep Sp. z o.o.</small></p>
    </footer>

    <!-- Scripts na końcu body — defer by default -->
    <script src="~/js/site.js" asp-append-version="true" defer></script>
    
    @await RenderSectionAsync("Scripts", required: false)

</body>
</html>

Kilka elementów warte uwagi:

  • asp-append-version="true" — ASP.NET Core Tag Helper dodaje hash do URL pliku (cache busting): site.css?v=abc123
  • role="alert" na komunikatach — czytniki ekranu natychmiast ogłaszają jego zawartość
  • @DateTime.Now.Year w copyright — automatycznie aktualny rok
  • @RenderSectionAsync("Scripts", required: false) — strony mogą wstrzykiwać własne skrypty

Podsumowanie: co musi być w każdym pliku HTML

<!DOCTYPE html>                          <!-- 1. Deklaracja HTML5 -->
<html lang="pl">                         <!-- 2. Język strony -->
<head>
    <meta charset="UTF-8">              <!-- 3. Kodowanie — PIERWSZA linia head -->
    <meta name="viewport" 
          content="width=device-width, initial-scale=1.0">  <!-- 4. Mobilność -->
    <title>Tytuł strony</title>         <!-- 5. Tytuł -->
    <!-- link, meta, script z defer -->
</head>
<body>
    <!-- Treść strony -->
</body>
</html>

Punkty 1–5 są obowiązkowe. Bez nich strona działa, ale nie spełnia standardów HTML5 i może zachowywać się nieprzewidywalnie w różnych przeglądarkach i urządzeniach.


Następny post: Formularze HTML5 w ASP.NET Core — pola, walidacja i model binding — wszystkie typy pól formularza, walidacja po stronie klienta przez atrybuty HTML5, i jak formularz HTML łączy się z modelem C# przez model binding.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

1 comment

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →