Е

Техническое руководство проекта secserpr.ru

Как собирался сайт — по шагам, с кодом и пояснениями
← На сайт
Руководство › Часть I. Фундамент › 03. Partials

03. Partials — кирпичи сайта

Partial — от английского partial, «частичный». Это файл, который содержит не целую страницу, а только её фрагмент: шапку, меню, футер. Собираются такие фрагменты в готовые страницы через SSI, который мы настроили в предыдущем разделе.

Идея проста: то, что повторяется на всех страницах, пишем один раз. И подключаем в нужное место через одну строку.

Зачем нужны partials

Без partials каждая страница сайта содержала бы полный код шапки, меню и футера. У нас восемь страниц — значит, восемь копий шапки, восемь копий меню, восемь копий футера. Поменяли телефон в футере — правим в восьми файлах и молимся, чтобы нигде не забыть.

С partials всё иначе: телефон правится в одном файле. На всех восьми страницах он обновится автоматически.

Без partials / С partials
БЕЗ PARTIALS                    С PARTIALS

index.html  - шапка - меню      index.html ----+
services.html - шапка - меню    services.html -+
about.html  - шапка - меню      about.html ----+
...                             ...            |
                                 Каждая         |
Каждая страница содержит        страница       |
8 копий одного и того же        ссылается на   |
кода шапки                      один файл      |
                                               v
                                  partials/
                                    header.html
                                    nav.html
                                    footer.html
                                    head-links.html

8 правок при изменении          1 правка

Какие partials есть в проекте

Разберём каждый по порядку. Построчно, с пояснением каждого символа и отступа.

3.1. partials/head-links.html

Этот файл подключается внутри head каждой страницы. Он содержит всё то, что нужно всем страницам одинаково: три фавикона, шесть og-тегов, два twitter-тега и два подключения стилей. Уникальные title, meta description и og:title остаются в самих страницах — они у каждой свои.

partials/head-links.html — полностью
<!-- ============================================================
     ФАЙЛ: partials/head-links.html
     АВТОР: Ефимов Андрей Михайлович
     НАЗНАЧЕНИЕ: Повторяющиеся ссылки для head — фавиконы,
                 og-теги, twitter-теги, стили.
     ПОДКЛЮЧАЕТСЯ: внутри head каждой страницы через SSI-директиву
                   include virtual на этот файл.
     ЗАВИСИТ ОТ: файлов favicon.svg, favicon-32.png,
                 apple-touch-icon.png, style.css, scroll-top.css
                 в корне.
     ОТ НЕГО ЗАВИСЯТ: все страницы сайта.
     ВАЖНО №1: Google Fonts намеренно исключены — системный стек
               шрифтов устраняет «дёргание» при загрузке.
     ВАЖНО №2: НЕ размещать здесь SSI-директиву include на самого
               себя — это вызывает зацикливание страницы.
     ВАЖНО №3: favicon.svg идёт ПЕРВЫМ и единственным тегом
               с rel="icon" type="image/svg+xml". Дублирование
               SVG-тегов сбивает браузер с толку.
     ============================================================ -->

<!-- Фавикон: основной SVG (современные браузеры) -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />

<!-- Фавикон: PNG 32×32 (браузеры без поддержки SVG) -->
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />

<!-- Apple Touch Icon: иконка на домашнем экране iOS -->
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />

<!-- Theme color: окрашивает адресную строку мобильного браузера -->
<meta name="theme-color" content="#33ff33" />

<!-- Open Graph: общие теги для превью в соцсетях -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Ефимов Андрей Михайлович" />
<meta property="og:image" content="https://secserpr.ru/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:locale" content="ru_RU" />

<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://secserpr.ru/og-image.png" />

<!-- Стили -->
<link rel="stylesheet" href="/style.css" />
<link rel="stylesheet" href="/scroll-top.css" />

Блок-паспорт в комментарии

Первые строки — комментарий с паспортом файла. Он не отображается на странице, но его читает тот, кто откроет исходник. Именно здесь содержится ответ на вопрос «а зачем этот файл нужен».

Строка ПОДКЛЮЧАЕТСЯ: внутри head каждой страницы через SSI — ключевая. Она говорит: этот файл попадает не как обычный HTML, а через серверное включение.

Важно: три предупреждения в паспорте

Строка «Google Fonts намеренно исключены» напоминает, почему мы не подключаем внешние шрифты. Строка «НЕ размещать здесь SSI-директиву include на самого себя» — защита от классической ошибки зацикливания (разбиралась в разделе 02). Строка про «favicon.svg идёт первым и единственным тегом с rel=icon type=image/svg+xml» — напоминание: если добавить ещё один SVG-тег, браузер возьмёт последний, а первый проигнорирует. Именно эта ошибка была в старой версии файла.

Фавикон: три файла

В файле — три тега для фавикона. Не один, а три, потому что разные браузеры и устройства предпочитают разные форматы. Все три файла лежат в корне сайта.

Разбор трёх фавиконов
1. SVG-файл /favicon.svg
   Используют: современные браузеры (Chrome, Firefox, Safari 15+)
   Плюс: векторный, масштабируется без потерь
   Основной вариант для сайта 2026 года

2. PNG 32×32 /favicon-32.png
   Используют: браузеры без поддержки SVG
   Плюс: старый стандартный формат, надёжный

3. Apple Touch Icon /apple-touch-icon.png
   Используют: iOS при добавлении сайта на домашний экран
   Плюс: специфика мобильных устройств Apple

Смотрим на первый — основной:

SVG-фавикон
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />

Стандартный тег link с атрибутом rel="icon" сообщает браузеру: «это иконка сайта». Атрибут type="image/svg+xml" указывает формат — SVG. Путь /favicon.svg — абсолютный, с ведущей косой чертой: файл лежит в корне сайта.

Внутри favicon.svg — тот же самый код, что и в логотипе шапки: зелёный квадрат с чёрной буквой «Е». Единый визуальный код бренда.

Почему отказались от data-URI

В первой версии проекта фавикон был встроен прямо в атрибут href как data-URI — длинная строка вида href="data:image/svg+xml,%3Csvg...". Идея была: «один файл меньше, один HTTP-запрос меньше». Но на практике вышло иначе. Во-первых, браузер видел два тега с одинаковым rel="icon" type="image/svg+xml" — data-URI и внешний файл — и брал только последний. Первый висел зря. Во-вторых, длинный закодированный SVG парсится заново на каждой странице при каждой загрузке. Внешний файл /favicon.svg кэшируется браузером один раз — это быстрее и проще.

Theme color

Meta theme-color
<meta name="theme-color" content="#33ff33" />

Мета-тег, который окрашивает адресную строку мобильного браузера в указанный цвет. На Android Chrome адресная строка сверху подсветится зелёным #33ff33 — тем же цветом бренда.

Это не обязательный тег. Но он даёт эффект «единого целого»: сайт и адресная строка сливаются в одно визуальное пространство.

Шесть og-тегов и два twitter-тега

Следующий блок — общие теги для соцсетей. Их задача: когда кто-то кидает ссылку на страницу сайта в Telegram, WhatsApp или VK — мессенджер показывает превью с картинкой, заголовком и описанием.

Og-теги построчно
og:type          = website         тип объекта
og:site_name     = «Ефимов...»     имя сайта
og:image         = полный URL      картинка превью
og:image:width   = 1200            ширина картинки
og:image:height  = 630             высота картинки
og:locale        = ru_RU           язык

Два twitter-тега:

og:image:width и og:image:height — подсказка мессенджеру, чтобы он не грузил картинку заранее, а сразу знал пропорции. Скорость.

Почему og-теги в head-links, а не в страницах

Общие og-теги (тип, имя сайта, картинка, локаль) — одинаковые для всех страниц, поэтому в head-links.html. А og:title, og:description, og:url — уникальные, поэтому остаются в самих страницах. Разделение по «повторяемости»: общее — в partial, частное — в странице.

Строки подключения стилей

Стили
<link rel="stylesheet" href="/style.css" />
<link rel="stylesheet" href="/scroll-top.css" />

Две строки — два тега link с атрибутом rel="stylesheet" («таблица стилей»).

Обратите внимание на путь: href="/style.css" — абсолютный, с ведущей косой чертой. Он говорит браузеру: «файл лежит в корне сайта». Не «рядом с текущей страницей», а именно в корне.

Это критично для partials. Если бы мы написали href="style.css", то из папки /blog/ браузер искал бы файл по адресу /blog/style.css — и не нашёл бы. Абсолютный путь работает всегда — и с любой страницы, и из любой папки.

Пример: почему два файла CSS, а не один

style.css — основные стили. Их подключают все страницы. scroll-top.css — только стили кнопки «Наверх». Технически можно было бы объединить. Мы разделили по одной причине: если кнопка «Наверх» когда-нибудь уедет или будет переделана — править придётся только её файл, не рискуя задеть основные стили.

3.2. partials/header.html

Этот файл вставляется первым внутри header каждой страницы. Он содержит левую часть шапки — логотип и название сайта, а также кнопку-бургер для мобильных.

partials/header.html
<!-- Блок-паспорт -->

<div class="header-left">
  <svg class="icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <rect width="100" height="100" fill="#33ff33" />
    <text x="50" y="72" font-size="70" text-anchor="middle"
          fill="#000000" font-family="Arial, sans-serif"
          font-weight="bold">Е</text>
  </svg>
  <div class="header-text">
    <h1>Ефимов Андрей Михайлович</h1>
    <div class="sub">Цифровая трансформация службы безопасности</div>
  </div>
</div>

<button class="burger" aria-label="Меню">
  <span></span>
  <span></span>
  <span></span>
</button>

Левая часть: логотип и текст

div class="header-left" — контейнер, объединяющий логотип и текст. Через CSS мы говорим ему: «выстрой детей в строку, добавь между ними 20 пикселей» (правило .header-left { display: flex; gap: 20px; }). Без этой обёртки логотип и текст не знали бы, что они — единая группа.

Дальше — SVG-логотип. Он такой же, как в фавиконе, только не в data-URI, а в виде обычных тегов SVG. Смотрим на атрибуты:

Внутри SVG — два элемента:

Ниже — div class="header-text" с заголовком и подзаголовком. Заголовок — h1, потому что это главный заголовок страницы с точки зрения семантики. Каждая страница имеет один h1. Остальные заголовки — h2, h3.

Кнопка-бургер

Бургер
<button class="burger" aria-label="Меню">
  <span></span>
  <span></span>
  <span></span>
</button>

Кнопка button, а не ссылка или div. Это осознанный выбор: по клику происходит действие (открытие меню), а не переход по адресу. Для действий семантически правильен именно button.

Три пустых span — три полоски будущего бургера. Внутри — ничего, потому что рисоваться они будут через CSS (правило .burger span { display: block; width: 28px; height: 3px; background: #1a3a5c; }). Пустые элементы для декоративных полосок — обычный приём.

Атрибут aria-label="Меню" — служебный для screen-reader. Слепой пользователь услышит «Меню, кнопка», а не «пустая кнопка». Это требование доступности.

Почему header в самой странице, а не в partials

Обратите внимание: тег header открывается и закрывается в самой странице, а не в partials. Это сделано потому, что между открывающим и закрывающим тегом вставляется ещё и nav.html. Если бы header был внутри header.html, мы не смогли бы вставить nav внутрь него через SSI — тег бы закрылся раньше.

3.3. partials/nav.html

Навигация вынесена в отдельный файл. Причина проста: это самая часто изменяемая часть сайта. Добавить раздел, убрать, переставить порядок — всё делается здесь, в одном месте.

partials/nav.html
<!-- Блок-паспорт -->

<nav class="nav">
  <a href="/index.html">Главная</a>
  <a href="/services.html">Услуги</a>
  <a href="/about.html">Обо мне</a>
  <a href="/projects.html">Кейсы</a>
  <a href="/offer.html">КП</a>
  <a href="/contacts.html">Контакты</a>
</nav>

Ничего сложного с виду — шесть ссылок. Но есть два момента, на которые стоит обратить внимание.

Абсолютные пути в ссылках

Все ссылки начинаются с косой черты: /index.html, /services.html и т.д. Это уже обсуждалось, но здесь особенно важно. Если бы мы написали href="services.html", из какой-нибудь будущей вложенной страницы (например, /blog/article.html) ссылка вела бы в /blog/services.html — и не работала бы.

Ведущая косая черта означает: «считай от корня сайта». Так ссылки работают всегда, из любой точки.

Класс active НЕ прописан вручную

Обратите внимание: ни на одной ссылке нет class="active". В старой версии сайта приходилось на каждой странице вручную помечать активную ссылку. Забыл — и на странице «Услуги» подсвечена «Главная». Хаос.

У нас за это отвечает JavaScript. В script.js есть модуль, который:

  1. Берёт текущий адрес из window.location.pathname.
  2. Сравнивает его с href каждой ссылки в меню.
  3. Если совпало — добавляет этой ссылке класс active.

Благодаря этому мы больше никогда не думаем про активную ссылку. Она подсвечивается сама. Скрипт разберём в Части III.

Пример: добавление раздела «Блог»

Когда блог будет готов, добавляем в nav.html одну строку: <a href="/blog.html">Блог</a>. Всё. На всех страницах сайта появится новая ссылка в меню. Никаких правок в других файлах.

3.4. partials/footer.html

Нижний блок — самый «текстовый» из partials. Реквизиты, копирайт, ссылка на политику и код Метрики. Вставляется в конце каждой страницы, внутри контейнера.

partials/footer.html
<!-- Блок-паспорт -->

<div class="footer">
  <div class="left">
    <strong>Исполнитель:</strong><br />
    Индивидуальный предприниматель Ефимов Андрей Михайлович<br />
    ИНН 780519548222 / ОГРНИП 324784700301772<br />
    Тел.: +7 994 432 57 40<br />
    E-mail: 9944325740@mail.ru
  </div>
  <div class="right">
    <strong>Банковские реквизиты:</strong><br />
    Р/с 40802810220001003235<br />
    ...
  </div>
</div>

<div class="copyright">
  © 2026 Ефимов Андрей Михайлович. Все права защищены.<br />
  <a href="/privacy.html">Политика обработки данных</a>
</div>

Два блока: left и right

Футер построен как flex-контейнер с двумя дочерними блоками. В CSS правило .footer { display: flex; justify-content: space-between; } разводит их по краям: левый прижат влево, правый — вправо.

На мобильных (ширина ≤ 768 пикселей) медиа-запрос переключает flex-direction в column — блоки встают друг под друга, выравниваются по центру.

Тег br — перенос строки

Внутри блоков футера каждая строка заканчивается тегом br. Это не самый «семантически чистый» способ, но для реквизитов — практичный. Альтернатива — каждую строку оборачивать в p, но тогда между строками были бы большие отступы, а нам нужны плотные строчки реквизитов.

Слеш / в конце тега — «самозакрывающийся тег». В HTML5 он не обязателен, но мы ставим для единообразия и совместимости.

Блок реквизитов — единый источник правды

Те же самые реквизиты есть на странице offer.html (в разделе «Условия сотрудничества»), contacts.html и privacy.html. Если ИНН, банк или счёт изменятся — надо править в нескольких местах. В будущем это можно вынести в отдельный partial, но пока реквизиты меняются редко — оставили так.

Блок копирайта

Отдельный блок, потому что по стилю он отличается: меньше шрифт, серый цвет, тонкая линия сверху. Это «подвал подвала» — метаинформация.

Ссылка на /privacy.html — юридическое требование. По закону о персональных данных у каждой формы сбора данных должна быть доступная политика. Мы используем её как ссылку в подвале — стандартная практика.

Код Яндекс.Метрики в конце

Метрика встроена именно в footer.html, не в каждую страницу. Это значит: как только partial раскрылся на любой странице — счётчик загрузился. Одна запись — восемь страниц работают с аналитикой.

В файле используется счётчик 112503289. Этот номер встречается в коде трижды: в URL загрузки скрипта, в вызове ym() и в теге noscript. При смене аккаунта — менять во всех трёх местах.

Внимание: код Метрики — только здесь

Если случайно вставить код Метрики и в footer.html, и в саму страницу — счётчик загрузится дважды, статистика будет двойная. Одно место, одно правило.

3.5. Ловушка SSI: зацикливание через комментарий

Есть ошибка, в которую очень легко попасть. И она невидима до момента, пока не натворит бед.

SSI-директивы выполняются даже внутри HTML-комментариев. То есть вот такая строка:

Опасный пример
Пример: подключить шапку можно так:
     include virtual="/partials/header.html"
(внутри HTML-комментария)

Выглядит как безобидный комментарий-подсказка. Но сервер выполнит директиву include несмотря на то, что она внутри комментария.

И если такая строка попадёт в сам файл head-links.html, произойдёт зацикливание: сервер попытается включить head-links.html в самого себя, потом ещё раз, и так — до бесконечности. Страница раздуется до сотен килобайт мусора.

Правило проекта

В паспортах partials нельзя размещать строку include как пример. Если нужно упомянуть механизм — пишем словами: «подключается через SSI-директиву include», без самого символа #.

Именно поэтому в паспортах наших partials формулировка — без реального синтаксиса include.

Итог

Теперь у нас есть все «кирпичи». Следующая часть — разбор стилей. Как устроен style.css, почему в нём секции, и что делает каждый медиа-запрос.