Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

A multi-page site

Let's put it all together: the Morning Café site with three pages — home, menu and contacts. The pages share a layout, a header and a footer, and the build script finds all the pages by itself and writes the HTML files.

Project structure

The pages move to the src/pages/ folder, while the shared parts stay in 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

The file src/mixins.pug is the same as in the lesson on includes: the drink(name, price) mixin.

A script for all pages

Replace 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: "Espresso", price: 2 },
    { name: "Cappuccino", price: 3 },
    { name: "Hot chocolate", 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(`Done: dist/${page}.html`);
}

The loop takes every file from src/pages/ and passes the shared data to the template, plus a page variable — the page name without the extension. A new page appears on the site as soon as you create a .pug file for it.

Create src/includes/header.pug. The link to the current page gets the active class:

header
  a.logo(href="index.html") Morning Café
  nav
    each title, slug in { index: "Home", menu: "Menu", contacts: "Contacts" }
      a(href=slug + ".html" class=page === slug ? "active" : undefined)= title

The footer is src/includes/footer.pug:

footer
  p © #{year} Morning Café · #{phone}

The layout src/layout.pug includes both parts. Paths in include are counted from the layout itself:

doctype html
html(lang="en")
  head
    meta(charset="utf-8")
    block title
      title Morning Café
    link(rel="stylesheet" href="style.css")
  body
    include includes/header.pug
    main
      block content
    include includes/footer.pug

The pages

Home is src/pages/index.pug:

extends ../layout.pug
 
block content
  h1 Fresh coffee every day
  p Come for breakfast from 8:00.

Contacts is src/pages/contacts.pug:

extends ../layout.pug
 
block title
  title Contacts — Morning Café
 
block content
  h1 Contacts
  p Call us: #{phone}

The menu is src/pages/menu.pug. Mixins are included with an include line next to extends:

extends ../layout.pug
include ../mixins.pug
 
block title
  title Menu — Morning Café
 
block content
  h1 Menu
  each item in menu
    +drink(item.name, item.price)

The result

Run npm run build: the terminal prints three "Done" lines, and dist/ gets three pages. This is dist/menu.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Menu — Morning Café</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <header><a class="logo" href="index.html">Morning Café</a>
      <nav><a href="index.html">Home</a><a class="active" href="menu.html">Menu</a><a href="contacts.html">Contacts</a>
      </nav>
    </header>
    <main>
      <h1>Menu</h1>
      <div class="card">
        <h3>Espresso</h3>
        <p class="price">$2</p>
      </div>
      <div class="card">
        <h3>Cappuccino</h3>
        <p class="price">$3</p>
      </div>
      <div class="card">
        <h3>Hot chocolate</h3>
        <p class="price">$3</p>
      </div>
    </main>
    <footer>
      <p>© 2026 Morning Café · +1 555 0100</p>
    </footer>
  </body>
</html>

The "Menu" link has the active class, and the prices come from the data in build.js. Put style.css into dist/ and open dist/index.html — the header links take you between the pages.

Common mistake

Putting something other than pages into src/pages/, such as mixins or the header. The script tries to build such a file as a page of its own and creates a stray HTML file. Keep only pages in src/pages/, and the shared parts in src/ and src/includes/.

Thanks for reading!

Donate to the site — we work hard for you.