Многостраничный сайт
Соберём всё вместе: сайт кофейни «Утро» из трёх страниц — главная, меню и контакты. У страниц общий макет, шапка и подвал, а скрипт сборки сам находит все страницы и записывает HTML-файлы.
Структура проекта
Страницы переезжают в папку src/pages/, а общие части остаются в src/:
cafe/
├── build.js
├── dist/ ← index.html, menu.html, contacts.html
└── src/
├── layout.pug
├── mixins.pug
├── includes/
│ ├── header.pug
│ └── footer.pug
└── pages/
├── index.pug
├── menu.pug
└── contacts.pugФайл src/mixins.pug — тот же, что в уроке о подключении файлов: миксин drink(name, price).
Скрипт для всех страниц
Замените build.js:
// build.js
import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
import pug from "pug";
const data = {
year: new Date().getFullYear(),
phone: "+1 555 0100",
menu: [
{ name: "Эспрессо", price: 2 },
{ name: "Капучино", price: 3 },
{ name: "Какао", price: 3 },
],
};
mkdirSync("dist", { recursive: true });
for (const file of readdirSync("src/pages")) {
const page = file.replace(".pug", "");
const html = pug.renderFile(`src/pages/${file}`, { pretty: true, page, ...data });
writeFileSync(`dist/${page}.html`, html);
console.log(`Готово: dist/${page}.html`);
}Цикл берёт каждый файл из src/pages/ и передаёт в шаблон общие данные и переменную page — имя страницы без расширения. Новая страница появится на сайте, как только вы создадите для неё файл .pug.
Макет, шапка и подвал
Создайте src/includes/header.pug. Ссылка на текущую страницу получает класс active:
header
a.logo(href="index.html") Кофейня «Утро»
nav
each title, slug in { index: "Главная", menu: "Меню", contacts: "Контакты" }
a(href=slug + ".html" class=page === slug ? "active" : undefined)= titleПодвал — src/includes/footer.pug:
footer
p © #{year} Кофейня «Утро» · #{phone}Макет src/layout.pug подключает обе части. Пути в include отсчитываются от самого макета:
doctype html
html(lang="ru")
head
meta(charset="utf-8")
block title
title Кофейня «Утро»
link(rel="stylesheet" href="style.css")
body
include includes/header.pug
main
block content
include includes/footer.pugСтраницы
Главная — src/pages/index.pug:
extends ../layout.pug
block content
h1 Свежий кофе каждый день
p Заходите на завтрак с 8:00.Контакты — src/pages/contacts.pug:
extends ../layout.pug
block title
title Контакты — Кофейня «Утро»
block content
h1 Контакты
p Звоните: #{phone}Меню — src/pages/menu.pug. Миксины подключаются строкой include рядом с extends:
extends ../layout.pug
include ../mixins.pug
block title
title Меню — Кофейня «Утро»
block content
h1 Меню
each item in menu
+drink(item.name, item.price)Результат
Запустите npm run build — в терминале появятся три строки «Готово», а в dist/ три страницы. Так выглядит dist/menu.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Меню — Кофейня «Утро»</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header><a class="logo" href="index.html">Кофейня «Утро»</a>
<nav><a href="index.html">Главная</a><a class="active" href="menu.html">Меню</a><a href="contacts.html">Контакты</a>
</nav>
</header>
<main>
<h1>Меню</h1>
<div class="card">
<h3>Эспрессо</h3>
<p class="price">$2</p>
</div>
<div class="card">
<h3>Капучино</h3>
<p class="price">$3</p>
</div>
<div class="card">
<h3>Какао</h3>
<p class="price">$3</p>
</div>
</main>
<footer>
<p>© 2026 Кофейня «Утро» · +1 555 0100</p>
</footer>
</body>
</html>У ссылки «Меню» класс active, а цены взяты из данных в build.js. Положите style.css в dist/ и откройте dist/index.html — по ссылкам в шапке можно переходить между страницами.
Частая ошибка
Положить в src/pages/ что-то кроме страниц, например миксины или шапку. Скрипт попытается собрать такой файл как отдельную страницу и создаст лишний HTML. В src/pages/ держите только страницы, а общие части — в src/ и src/includes/.