# HTML5 Boilerplate Administrator & Developer Guide

This guide is the source-based reference for build engineers integrating HTML5 Boilerplate (h5bp) into a project, devops handling its server-side concerns, and contributors maintaining the boilerplate itself. It documents the layered structure of the template, the exact build pipeline (npm + gulp + webpack), every dotfile and configuration knob that ships in the upstream repo, and the explicit boundaries between this repo and its companion repositories.

HTML5 Boilerplate is a **template, not a runtime**. The published artifact (`dist/`) is plain static HTML, CSS, JavaScript, and image files that any web server can serve. The Node.js dependency exists only at *build time* — to run webpack, gulp, and the upstream test suite. Treat that distinction as the foundation for every operational decision in this guide.

> [!NOTE]
> This is a release-targeting reference for **v9.0.1** of `h5bp/html5-boilerplate` (commit `31357fd`, source date 2026-04-22). Behaviour described here may differ in earlier or later versions — always cross-check `package.json` `version` and the `CHANGELOG.md` of the version you're deploying.

---

## 1. Architecture Overview

HTML5 Boilerplate is structured in five clearly separated layers. Each layer has a different audience, a different change cadence, and a different deployment path.

### Layer cake

```
┌───────────────────────────────────────────────────────────────────┐
│  L5 — Maintainer tooling   (root)                                 │
│       gulpfile.mjs, eslint.config.mjs, .cspell.json,              │
│       .prettierrc.json, .prettierignore, package.json,            │
│       package-lock.json, test/, CHANGELOG.md, .npmrc              │
│       Audience: upstream contributors only.                       │
├───────────────────────────────────────────────────────────────────┤
│  L4 — Build pipeline       (src/webpack.*.js)                     │
│       webpack.common.js, webpack.config.dev.js,                   │
│       webpack.config.prod.js, src/package.json                    │
│       Audience: site authors building dist/.                      │
├───────────────────────────────────────────────────────────────────┤
│  L3 — Static deliverables  (src/*)                                │
│       src/index.html, src/404.html, src/robots.txt,               │
│       src/site.webmanifest, src/favicon.ico,                      │
│       src/icon.png, src/icon.svg                                  │
│       Audience: end users of the deployed website.                │
├───────────────────────────────────────────────────────────────────┤
│  L2 — Application slot     (src/js, src/img, src/css)             │
│       src/js/app.js (empty), src/js/vendor/, src/img/             │
│       Audience: your application code goes here.                  │
├───────────────────────────────────────────────────────────────────┤
│  L1 — Cross-cutting hygiene (dotfiles)                            │
│       .editorconfig, .gitattributes, .gitignore,                  │
│       src/.editorconfig, src/.gitattributes, src/.gitignore       │
│       Audience: every contributor, every editor.                  │
└───────────────────────────────────────────────────────────────────┘
```

The repo intentionally separates **L5 maintainer tooling** (which lives at the repo root) from **L2-L4 deliverables** (which live under `src/`). When a downstream user runs `npx create-html5-boilerplate my-site`, the published npm package contains only L1-L4 — the contents of `dist/` from a fresh build. The maintainer tooling never reaches the consumer.

### Component inventory

| Component | Path | Role |
|---|---|---|
| Root `package.json` | `package.json` | Maintainer dev-deps + gulp-driven build pipeline (`build`, `test`, `prettier`) |
| Per-site `package.json` | `src/package.json` | Webpack scripts (`start`, `build`) shipped with every scaffolded project |
| Common webpack config | `src/webpack.common.js` | Shared `entry`/`output` (the only shared config) |
| Dev webpack config | `src/webpack.config.dev.js` | Adds `webpack-dev-server`, hot reload, inline source maps |
| Prod webpack config | `src/webpack.config.prod.js` | Adds `HtmlWebpackPlugin` + `CopyPlugin` for static assets |
| HTML skeleton | `src/index.html` | Default page with viewport, OG tags, manifest, icon links |
| 404 page | `src/404.html` | Self-contained, inline-styled error page |
| Web manifest | `src/site.webmanifest` | PWA install metadata |
| Crawler policy | `src/robots.txt` | Default allow-all |
| Icon set | `src/favicon.ico`, `src/icon.svg`, `src/icon.png` | Browser tabs, home screens, OS install |
| App entry point | `src/js/app.js` | Empty file; user-supplied JavaScript |
| Image bucket | `src/img/.gitkeep` | Empty folder for project images |
| ESLint flat config | `eslint.config.mjs` | JS lint rules (root + tests + src/js) |
| Spellcheck config | `.cspell.json` | Project-specific dictionary for Markdown |
| Prettier config | `.prettierrc.json` | `singleQuote: true`, `bracketSameLine: true`, YAML overrides |
| Prettier ignore | `.prettierignore` | Files prettier skips |
| Editor rules | `.editorconfig` | Cross-editor whitespace + EOL normalization |
| Git attributes | `.gitattributes` | EOL normalization (LF) for all text formats |
| Git ignore | `.gitignore` | Excludes `node_modules`, archive output |
| npm config | `.npmrc` | npm publish/install settings |
| Maintainer build | `gulpfile.mjs` | Orchestrates `archive`, `lint:js`, dist assembly |
| Test fixtures | `test/file_existence.mjs`, `test/file_content.mjs` | Mocha tests asserting `dist/` shape and byte-content |
| Docs (canonical) | `docs/*.md` | TOC, usage, html, css, js, misc, faq, extend, about-this-repo |

### Technology stack

| Layer | Tech | Version (locked) |
|---|---|---|
| Runtime (deliverable) | Static HTML/CSS/JS | n/a — pure web standards |
| Build orchestrator | Node.js | `>=20` (Volta-pinned to `20.19.1`) |
| Per-site bundler | webpack | `^5.105.3` |
| Per-site dev server | webpack-dev-server | `^5.2.3` |
| Per-site config merge | webpack-merge | `^6.0.1` |
| Per-site HTML plugin | html-webpack-plugin | `^5.6.6` |
| Per-site copy plugin | copy-webpack-plugin | `^13.0.1` |
| Per-site CLI | webpack-cli | `^6.0.1` |
| Maintainer build | gulp | `^5.0.1` |
| Maintainer linter | eslint + gulp-eslint-new | `^9.35.0`, `^2.6.0` |
| Maintainer prettier | prettier | `3.6.2` (pinned, no caret) |
| Maintainer test runner | mocha | `^11.7.5` |
| Maintainer archiver | archiver | `^7.0.1` |
| CSS source | `h5bp/main.css` | `3.0.0` |
| Autoprefixer (build) | gulp-autoprefixer | `^9.0.0` |

> [!NOTE]
> The **only npm overrides** declared in the upstream repo are `diff: ^8.0.3` and `serialize-javascript: ^7.0.5` — both are transitive-dependency security pins. Don't remove them without checking the issue tracker for the supply-chain context that motivated each pin.

### Data flow — production build

```
        [src/index.html]──────┐
        [src/404.html]────┐    │
        [src/css/]────┐   │    │
        [src/img/]────┤   │    │
        [src/js/vendor/]──┤    │
        [src/icon.svg]────┤    │
        [src/icon.png]────┤    │
        [src/favicon.ico]─┤    │
        [src/robots.txt]──┤    │
        [src/site.webmanifest]┤
                          │    │
                          ▼    ▼
              ┌──────────────────────────┐
              │ webpack.config.prod.js   │
              │ ├── CopyPlugin patterns  │
              │ └── HtmlWebpackPlugin    │
              │     (templates index)    │
              └──────────────┬───────────┘
                             │
                  [src/js/app.js]
                             │
                             ▼
                  ┌────────────────┐
                  │  webpack 5     │
                  │  bundle + min  │
                  └────────┬───────┘
                           │
                           ▼
                       [dist/]
                  (the deliverable)
```

The production build is the single source of truth for what ends up at the deployed origin. Nothing magical happens to `dist/` post-build — whatever webpack writes is exactly what your users will fetch.

### Data flow — maintainer release

```
      contributor PR
           │
           ▼
   gulp lint:js ──── eslint flat config (eslint.config.mjs)
           │
           ▼
   gulp archive ──── reads h5bp-configs.directories from package.json
           │             ├── archive  (output dir)
           ▼             ├── dist     (build output)
   mocha tests ─────┐    ├── src      (source)
                    │    └── test     (test files)
                    ▼
        test/file_existence.mjs
        test/file_content.mjs
                    │
                    ▼
   git tag + npm publish (per CHANGELOG.md)
                    │
                    ▼
        npm registry: html5-boilerplate@9.0.1
        github.com/h5bp/html5-boilerplate-template (mirrored)
```

The maintainer pipeline is gulp-driven and runs only in CI / on a maintainer's machine. End users never touch it.

> [!IMPORTANT]
> The split between root `package.json` and `src/package.json` is deliberate. Root tools (gulp, mocha, prettier) belong to the maintainer flow; `src/package.json`'s tools (webpack et al.) are what every downstream user installs. **Do not consolidate them** — that would force every consumer of the template to install the entire maintainer toolchain.

---

## 2. System Requirements

The hosting target for a built site has essentially zero requirements; the build environment requires modern Node.js and a Unix-style shell.

### Runtime requirements (deployed site)

| Requirement | Status | Notes |
|---|---|---|
| Web server | Any | Apache, Nginx, IIS, Lighttpd, GAE, Netlify, Vercel, GitHub Pages, S3, etc. |
| Server-side runtime | Not required | The deliverable is purely static |
| TLS / HTTPS | Strongly recommended | Required for PWA installability and `theme-color` honored by some browsers |
| HTTP/2 or HTTP/3 | Optional but recommended | The template assumes one-bundle-per-asset; HTTP/2 multiplexing makes this efficient |

### Build / development requirements

| Requirement | Minimum | Locked / Pinned | Notes |
|---|---|---|---|
| Node.js | `>=20` | `20.19.1` (via Volta `volta.node` field in root `package.json`) | Enforced by `engines.node` |
| npm | Bundled with Node 20 | Lockfile uses `lockfileVersion: 3` | yarn / pnpm should work but are untested upstream |
| Operating system | Any | n/a | macOS, Linux, Windows (with `.gitattributes` LF normalization) |
| Disk | ~200 MB | n/a | `node_modules` dominates; `dist/` is <50 KB |

> [!NOTE]
> Node `>=20` is a hard floor — install will fail with `EBADENGINE` on Node 18. The CI matrix [previously ran Node 22/24](https://github.com/h5bp/html5-boilerplate/issues/3413); migrating CI off the deprecated runner versions is tracked in that closed issue. Maintainers should keep an eye on Node LTS schedules and bump the floor on major releases.

### Browser support matrix

| Target | Status |
|---|---|
| Browserslist `defaults` | Supported (autoprefixer reads this list) |
| Latest stable Chrome / Edge / Firefox / Safari | Supported |
| Internet Explorer | **Not supported** — dropped in v8.0.0 |
| Modernizr-style feature detection | **Not bundled** — removed in v9.0.0 |
| Mobile browsers (iOS Safari, Android Chrome) | Supported |

> [!TIP]
> The `defaults` Browserslist query at the time of this writing covers >0.5% global usage in the last two versions, plus Firefox ESR — not dead. If you need broader compatibility, set a custom `browserslist` field in your project's `package.json`. Autoprefixer reads it and adapts vendor prefix output accordingly.

---

## 3. Configuration Reference

The HTML5 Boilerplate template has no central configuration file. Configuration is split across (a) project package metadata, (b) webpack files, (c) dotfiles, and (d) the per-page meta tags inside the HTML skeleton. This section enumerates **every** knob that ships in the upstream repo.

### `package.json` (root) — maintainer surface

The root `package.json` declares the upstream-only build pipeline and is **not** shipped to downstream users. Fields documented:

| Field | Value (v9.0.1) | Purpose |
|---|---|---|
| `name` | `html5-boilerplate` | npm package name |
| `version` | `9.0.1` | Source of truth for releases |
| `description` | `A professional front-end template for building fast, robust, and adaptable web apps or sites.` | npm metadata |
| `keywords` | `["h5bp", "template", "front-end"]` | npm search |
| `homepage` | `https://html5boilerplate.com/` | Project site |
| `bugs.url` | `https://github.com/h5bp/html5-boilerplate/issues` | Issue tracker |
| `repository.url` | `https://github.com/h5bp/html5-boilerplate.git` | Git origin |
| `license` | `MIT` | Permissive, no attribution required |
| `files` | `["CHANGELOG.md", "LICENSE.txt", "package.json", "dist/", "README.md"]` | Whitelist of files included in the published npm tarball |
| `engines.node` | `>=20` | Runtime floor |
| `volta.node` | `20.19.1` | Per-tool Node pin for [Volta](https://volta.sh/) users |
| `h5bp-configs.directories` | `{ archive, dist, src, test }` | Custom config consumed by gulp |
| `overrides.diff` | `^8.0.3` | Transitive-dep security pin |
| `overrides.serialize-javascript` | `^7.0.5` | Transitive-dep security pin |

#### `package.json` scripts (root)

| Script | Command | Purpose |
|---|---|---|
| `build` | `gulp build` | Runs the gulp `build` pipeline; produces `dist/` from `src/` (used by maintainers when assembling a release tarball) |
| `prettier` | `prettier --write ./**/*.{js,json,md,mjs,yml}` | Formats every JavaScript / JSON / Markdown / MJS / YAML file in place using `.prettierrc.json` rules |
| `test` | `gulp archive && mocha --reporter spec --timeout 5000` | Builds an archive, then runs Mocha tests with the `spec` reporter and a 5 s per-test timeout |

#### `package.json` devDependencies (root) — selected

| Package | Version | Why |
|---|---|---|
| `@eslint/js` | `^9.35.0` | ESLint flat-config base rules |
| `archiver` | `^7.0.1` | Used by the gulp `archive` task to build release tarballs |
| `del` | `^8.0.1` | Cleanup utility for gulp tasks |
| `eslint` | `^9.35.0` | Flat-config linter |
| `eslint-plugin-mocha` | `^11.1.0` | Mocha-aware lint rules |
| `glob` | `^13.0.6` | File-pattern resolution in gulp tasks |
| `globals` | `^16.4.0` | Predefined global declarations for ESLint |
| `gulp` | `^5.0.1` | Maintainer build orchestrator |
| `gulp-autoprefixer` | `^9.0.0` | Adds vendor prefixes to CSS at build |
| `gulp-eslint-new` | `^2.6.0` | Streams ESLint into a gulp pipeline |
| `gulp-header` | `^2.0.9` | Prepends a banner (used on `style.css`) |
| `gulp-rename` | `^2.1.0` | Renames files mid-stream |
| `main.css` | `3.0.0` | Source of `dist/css/style.css`; depended on as a versioned npm package |
| `mocha` | `^11.7.5` | Test runner |
| `prettier` | `3.6.2` | Pinned (no caret); the version drift here is intentionally controlled |

### `src/package.json` — downstream surface

The `src/package.json` is the **per-site** manifest that ships with every scaffolded project (the contents of `dist/`). It is intentionally minimal:

| Field | Value | Purpose |
|---|---|---|
| `name` | `" "` (single space) | Placeholder; user fills in their own |
| `version` | `0.0.1` | Placeholder |
| `description` | `""` | Placeholder |
| `private` | `true` | Prevents accidental npm publish |
| `keywords` | `[""]` | Placeholder |
| `license` | `""` | Placeholder |
| `author` | `""` | Placeholder |

#### `src/package.json` scripts

| Script | Command | What it does |
|---|---|---|
| `test` | `echo "Error: no test specified" && exit 1` | Default placeholder; user replaces with their own test command |
| `start` | `webpack serve --open --config webpack.config.dev.js` | Starts webpack-dev-server with auto-open, hot reload, live reload, and inline source maps |
| `build` | `webpack --config webpack.config.prod.js` | Produces `dist/` with HTML templating + static asset copy |

#### `src/package.json` devDependencies

| Package | Version | Purpose |
|---|---|---|
| `copy-webpack-plugin` | `^13.0.1` | Copies static assets (icons, manifest, robots) into `dist/` |
| `html-webpack-plugin` | `^5.6.6` | Templates `index.html` and injects bundled `<script>` tags |
| `webpack` | `^5.105.3` | The bundler |
| `webpack-cli` | `^6.0.1` | CLI wrapper |
| `webpack-dev-server` | `^5.2.3` | Local dev server with HMR |
| `webpack-merge` | `^6.0.1` | Composes common + mode-specific configs |

### Webpack — `src/webpack.common.js`

Shared between dev and prod. Verbatim:

```javascript
const path = require('path');

module.exports = {
  entry: {
    app: './js/app.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    clean: true,
    filename: './js/app.js',
  },
};
```

**Knobs:**

- `entry.app` — the application entry point. Change to `./js/app.ts` if integrating TypeScript.
- `output.path` — `dist/` directory inside the project root. Don't change without also updating the deploy pipeline.
- `output.clean: true` — purges stale files in `dist/` before each build. Prevents leftover assets from previous builds from being deployed.
- `output.filename` — bundle path inside `dist/`. The leading `./` is intentional and resolves correctly under webpack 5.

### Webpack — `src/webpack.config.dev.js`

Dev-only overrides. Verbatim:

```javascript
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    liveReload: true,
    hot: true,
    open: true,
    static: ['./'],
  },
});
```

| Key | Value | Effect |
|---|---|---|
| `mode` | `development` | webpack development mode (no minification) |
| `devtool` | `inline-source-map` | Source maps embedded in the bundle for in-browser DevTools debugging |
| `devServer.liveReload` | `true` | Reload the page when dev server detects a file change |
| `devServer.hot` | `true` | Enable hot module replacement (more granular than liveReload) |
| `devServer.open` | `true` | Open the default browser when the server starts |
| `devServer.static` | `['./']` | Serve static files from the project root |

### Webpack — `src/webpack.config.prod.js`

Production-only overrides. Verbatim:

```javascript
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html',
    }),
    new CopyPlugin({
      patterns: [
        { from: 'img', to: 'img' },
        { from: 'css', to: 'css' },
        { from: 'js/vendor', to: 'js/vendor' },
        { from: 'icon.svg', to: 'icon.svg' },
        { from: 'favicon.ico', to: 'favicon.ico' },
        { from: 'robots.txt', to: 'robots.txt' },
        { from: 'icon.png', to: 'icon.png' },
        { from: '404.html', to: '404.html' },
        { from: 'site.webmanifest', to: 'site.webmanifest' },
      ],
    }),
  ],
});
```

The `CopyPlugin.patterns` array is the **single source of truth** for "what assets end up in `dist/`." If you add a static file to your project (e.g., a sitemap, an additional manifest, a `humans.txt`, a `.well-known/` folder), it will not be copied unless you add a corresponding pattern.

> [!IMPORTANT]
> Forgetting to add a `CopyPlugin` entry is the most common downstream deploy bug. There is no warning at build time — the missing file is simply absent from `dist/`. Add a build-completion check to your CI that asserts every expected asset is present.

### `.prettierrc.json` — formatter rules

Verbatim:

```json
{
  "bracketSameLine": true,
  "embeddedLanguageFormatting": "off",
  "singleQuote": true,
  "overrides": [
    {
      "files": "**/*.yml",
      "options": {
        "singleQuote": false
      }
    }
  ]
}
```

| Key | Value | Effect |
|---|---|---|
| `bracketSameLine` | `true` | Closing `>` of multi-line elements stays on the line of the last attribute |
| `embeddedLanguageFormatting` | `"off"` | Do not reformat code inside Markdown / template-literal blocks |
| `singleQuote` | `true` | Use `'` instead of `"` in JS/JSON-like outputs |
| YAML override | `singleQuote: false` | Use double quotes in `*.yml` (YAML idiomatic) |

### `.cspell.json` — spell-check dictionary

Project-specific words and config. Verbatim:

```json
{
  "version": "0.2",
  "words": [
    "codeql",
    "devdeps",
    "FOUC",
    "lighttpd",
    "mkdir",
    "modernizr",
    "robotstxt",
    "vuejs",
    "cpina"
  ],
  "language": "en,en-GB,en-US",
  "allowCompoundWords": true,
  "files": ["**/*.md"],
  "ignoreRegExpList": ["\\_\\w+", "\\#\\w+"],
  "ignorePaths": [".cspell.json"],
  "useGitignore": true
}
```

CSpell runs against Markdown files only (`files: ["**/*.md"]`). Add new technical terms to `words` to suppress false positives in docs. The `\\_\\w+` and `\\#\\w+` regexes ignore underscored identifiers and hashtag-style references inside docs.

### Dotfiles

| File | Format | What it controls |
|---|---|---|
| `.editorconfig` | EditorConfig | Whitespace, EOL, charset across editors (universal) |
| `src/.editorconfig` | EditorConfig | Same rules, scoped to the per-site template |
| `.gitattributes` | Git attributes | Forces LF line endings on text formats; ensures `dist/` byte-content tests pass on Windows |
| `src/.gitattributes` | Git attributes | Same, scoped to the per-site template |
| `.gitignore` | Git ignore | Excludes `node_modules`, archive output, lockfile copies in `dist/` |
| `src/.gitignore` | Git ignore | Same, scoped to the per-site template |
| `.prettierignore` | Prettier ignore | Files prettier should not format |
| `.npmrc` | npm config | npm publish/install settings |

### Bundled server configurations

**None.** The upstream `h5bp/html5-boilerplate` repository at v9.0.1 ships **no** Apache (`.htaccess`), Nginx (`nginx.conf`), IIS (`web.config`), or Lighttpd configuration. Server config was extracted from the core template in the v9.0.0 release line and lives in dedicated repos:

| Server | Companion repo |
|---|---|
| Apache | [`h5bp/server-configs-apache`](https://github.com/h5bp/server-configs-apache) |
| Nginx | [`h5bp/server-configs-nginx`](https://github.com/h5bp/server-configs-nginx) |
| IIS | [`h5bp/server-configs-iis`](https://github.com/h5bp/server-configs-iis) |
| Lighttpd | [`h5bp/server-configs-lighttpd`](https://github.com/h5bp/server-configs-lighttpd) |
| Node.js | [`h5bp/server-configs-node`](https://github.com/h5bp/server-configs-node) |
| Google App Engine | [`h5bp/server-configs-gae`](https://github.com/h5bp/server-configs-gae) |

This separation is intentional. Server configs evolve on a different cadence (security headers in particular) and have hard dependencies on server version, module availability, and operator preferences. Bundling them into the core template would tie release cycles together inappropriately.

> [!IMPORTANT]
> Any prior documentation, blog post, or AI-generated guide that references "the bundled `.htaccess`" inside `h5bp/html5-boilerplate` is referring to a **pre-9.0.0** layout. For v9.0.1 and newer, the configs live exclusively in the companion repos.

---

## 4. Deployment & Scaling

Because the deliverable is purely static, "deployment" is essentially "upload `dist/` somewhere." Scaling is whatever your CDN or origin server supports. This section covers the canonical hosting paths and the practical config snippets that go with each.

### The deployable artifact

After `npm run build`, `dist/` contains exactly:

```text
dist/
├── 404.html
├── css/style.css
├── favicon.ico
├── icon.png
├── icon.svg
├── img/                # only if you placed images in src/img/
├── index.html          # templated by HtmlWebpackPlugin; bundled <script> injected
├── js/
│   ├── app.js          # bundled application
│   └── vendor/         # only if you placed files in src/js/vendor/
├── package.json        # the per-site package.json (low-value to publish; safe to deploy-ignore)
├── robots.txt
├── site.webmanifest
├── webpack.common.js   # safe to deploy-ignore
├── webpack.config.dev.js
├── webpack.config.prod.js
├── .editorconfig       # safe to deploy-ignore
├── .gitattributes      # safe to deploy-ignore
└── .gitignore          # safe to deploy-ignore
```

For production, you typically want to deploy only the runtime assets (`*.html`, `css/`, `js/`, `img/`, icons, `robots.txt`, `site.webmanifest`) — not `package.json`, dotfiles, or the webpack config files. Most platform-managed hosts ignore those automatically.

### GitHub Pages

The canonical deploy uses GitHub Actions. Key operational points:

- Set **Pages → Source** to **GitHub Actions** (not "Deploy from a branch") in repository settings.
- Use `actions/setup-node@v4` with `node-version: 20` and `cache: npm` to keep CI fast.
- Run `npm ci` (not `npm install`) for reproducible builds.
- Upload `dist/` via `actions/upload-pages-artifact@v3`, then deploy via `actions/deploy-pages@v4`.
- If your repo is hosted at `https://USERNAME.github.io/REPO/` (i.e., not a custom domain), use **relative paths** in `index.html` instead of absolute paths starting with `/`. A leading `/` resolves to the GitHub-Pages root, not your repo's subpath.

### Netlify

| Setting | Value |
|---|---|
| Build command | `npm run build` |
| Publish directory | `dist` |
| Node version | `20` (set via `.nvmrc` or `NODE_VERSION` in `netlify.toml`) |

Netlify auto-detects Node from `engines.node`, but the floor is `>=20`. Pin explicitly in `netlify.toml` to avoid drift:

```toml
[build]
  command = "npm run build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "20"
```

### Vercel

Same shape:

| Setting | Value |
|---|---|
| Build Command | `npm run build` |
| Output Directory | `dist` |

Vercel sometimes defaults to `public/` as the output directory; override explicitly. Set `engines.node` in your project's `package.json` to `20.x` so Vercel resolves to a Node 20 runtime.

### CDN / cache strategy

The template intentionally **does not ship cache headers** (because it does not ship any server config). The recommended strategy when wiring up your hosting:

| Asset class | `Cache-Control` recommendation | Why |
|---|---|---|
| HTML (`*.html`) | `max-age=0, must-revalidate` | Always serve the latest entry point; never stale |
| Hashed bundles (`js/app.[hash].js`) | `max-age=31536000, immutable` | Hash in filename means content-addressed; safe to cache forever |
| Unhashed bundles (`js/app.js` — current default) | `max-age=300, must-revalidate` | Modest cache because filename does not change between builds |
| Static images, icons | `max-age=2592000` (30 days) | Rarely change; cheap to recache |
| `robots.txt`, `site.webmanifest` | `max-age=86400` (1 day) | Stable but not immutable |

Pull the actual snippets for your server platform from the companion `h5bp/server-configs-*` repos. Apache (`.htaccess` `<FilesMatch>` directives), Nginx (`location` blocks with `add_header`), and IIS (`web.config` `<staticContent>`) all work; your platform's docs will tell you which approach is canonical.

> [!TIP]
> Webpack 5 supports content-hashed filenames out of the box. Consider switching `output.filename` from `'./js/app.js'` to `'./js/[name].[contenthash].js'` for production builds — combined with `HtmlWebpackPlugin` (which auto-rewrites the script tag), this enables the `immutable` caching tier above with no manual filename juggling.

### Scaling concerns

The static deliverable scales horizontally to whatever your CDN supports. There are no shared sessions, no database — concurrent requests do not contend with each other. Operational concerns are CDN-level:

- **Origin shielding** — direct your CDN to a single origin tier (S3 bucket, GCS bucket, or your hosting platform's origin) and let the edge cache absorb load.
- **Geographic distribution** — most CDNs (Cloudflare, Fastly, CloudFront, Vercel Edge Network) replicate static assets globally with no extra config.
- **DDoS** — pure-static sites are inherently resistant to application-layer DDoS because there's no compute to overwhelm. Bandwidth-layer DDoS is a CDN concern; rely on your CDN's defaults.

---

## 5. Operations

### Daily build operations

The maintainer flow is gulp-driven; the per-site flow is webpack-driven. They run in different working directories:

| Flow | Working dir | Command |
|---|---|---|
| Maintainer (release / lint / test) | repo root | `npm run build`, `npm test`, `npm run prettier` |
| Site author (dev / build) | `src/` (or scaffolded project root) | `npm run start`, `npm run build` |

### Refreshing meta tags & icons for a new project

The "configuration ritual" you run once at project start, in this order:

- [ ] Replace `src/favicon.ico` with your 16×16 / 32×32 / 48×48 multi-resolution `.ico`.
- [ ] Replace `src/icon.svg` with your scalable icon (preferred for modern browsers).
- [ ] Replace `src/icon.png` with at least a 192×192 PNG. Add a 512×512 entry to `site.webmanifest` for PWA installability.
- [ ] Edit `src/site.webmanifest` to fill in `name`, `short_name`, additional `icons` entries, `theme_color`, `background_color`.
- [ ] Edit `src/index.html`: set `<html lang="...">`, `<title>`, `<meta name="description">`, all `og:*` tags, and update `<meta name="theme-color" content="...">` to match `site.webmanifest`.
- [ ] Edit `src/robots.txt`: add `Sitemap:` directive, `Disallow:` rules for staging/private paths.
- [ ] Optional — add `.well-known/security.txt` to `src/`, then add a `CopyPlugin` pattern for `.well-known` in `webpack.config.prod.js`.
- [ ] Optional — add `humans.txt` to `src/`, plus a `CopyPlugin` pattern.

> [!NOTE]
> Because each new static asset requires a `CopyPlugin` pattern in `webpack.config.prod.js`, the per-project setup overhead grows linearly with the number of files. For a complex deployment with sitemaps, multiple manifests, well-known files, and verification tokens, consider replacing the explicit pattern list with a glob: `{ from: '*', to: '', globOptions: { ignore: ['**/*.html', '**/*.js', '**/*.css'] } }`.

### Updating dependencies safely

The template's dependency tree is shallow but not trivial. Standard hygiene:

```bash
npm outdated
```

Run on both root and `src/package.json` (treat them as separate manifests). Major version bumps to webpack, eslint, gulp, or mocha frequently require config-format changes (`eslint` flat config landed in v9; `gulp` 5 introduced new task signatures; `webpack` 5 changed asset-module handling).

For minor / patch bumps:

```bash
npm update
npm test
```

For major bumps:

1. Read the upstream changelog of the package being bumped.
2. Bump in a feature branch.
3. Run `npm test` (mocha + gulp `archive`).
4. Manually scaffold a fresh project from the new version and verify `npm run start` and `npm run build` both succeed.
5. Compare `dist/` byte-for-byte against the previous release; investigate any unexpected changes.

> [!TIP]
> The `overrides` block in root `package.json` (currently `diff: ^8.0.3`, `serialize-javascript: ^7.0.5`) is the right tool for forcing a transitive dependency to a security-patched version without waiting for the parent package to release. Add new entries here when CVEs land in the dep tree.

### Re-running tests

```bash
npm test
```

```text
> gulp archive && mocha --reporter spec --timeout 5000

  file_existence
    OK dist/index.html exists
    OK dist/404.html exists
    OK dist/css/style.css exists
    OK dist/js/app.js exists
    OK dist/site.webmanifest exists
    ...

  file_content
    OK dist/css/style.css contains the expected banner
    ...
```

`gulp archive` is the prerequisite — without it, `dist/` doesn't exist and the existence tests all fail.

### Releasing a new version (maintainer playbook)

- [ ] Bump `version` in root `package.json`.
- [ ] Update `CHANGELOG.md` with the new release section.
- [ ] Run `npm test` to confirm `dist/` is correct.
- [ ] `git tag v9.0.X && git push --tags`
- [ ] `npm publish` (publishes `dist/` per the `files` whitelist in `package.json`).
- [ ] Confirm the GitHub Actions workflow that mirrors `main` to `h5bp/html5-boilerplate-template` ran cleanly (per [#3412](https://github.com/h5bp/html5-boilerplate/issues/3412)). If it failed, fix manually before the release is "done."
- [ ] Draft a GitHub Release with the changelog snippet attached.
- [ ] Refresh the [html5boilerplate.com](https://html5boilerplate.com/) site (separately maintained).

> [!NOTE]
> The npm-publishing flow itself is being modernized (see [#3411](https://github.com/h5bp/html5-boilerplate/issues/3411)). The template-mirror workflow has known issues tracked in [#3412](https://github.com/h5bp/html5-boilerplate/issues/3412) — confirm both before tagging a release.

### Monitoring a deployed site

For a static site, monitoring is mostly synthetic: external HTTP checks + CDN access logs. Recommended baseline:

- Synthetic uptime monitor (e.g., UptimeRobot, Pingdom, or your CDN's built-in) hitting `/` and `/site.webmanifest` every 5 min.
- CDN log streaming to your log aggregator for status-code distributions and unusual 4xx/5xx rates.
- Lighthouse CI (or a similar tool) running on every deploy, asserting Performance ≥ 90 and Accessibility ≥ 95.

There is no application-level health endpoint to monitor — there's no application.

---

## 6. Performance

The performance posture is "ship the smallest correct payload, cache aggressively, then get out of the way." The template gives you a near-zero baseline; everything you add is yours to budget.

### Bundle size baseline

| Asset | Size (bare template) | Notes |
|---|---|---|
| `index.html` | ~656 bytes (after webpack templating) | Below TCP-window slow-start; arrives in single round-trip |
| `js/app.js` | ~26 bytes (minimized, empty entry) | Webpack overhead is essentially zero |
| `css/style.css` | Sub-kilobyte | Sourced from `h5bp/main.css` v3.0.0; no Normalize.css |
| `404.html` | ~1.74 KB | Inline-styled, no external deps |
| `site.webmanifest` | <500 bytes | Minimal stub |
| `robots.txt` | <100 bytes | Allow-everything |
| Icons | 192×192 PNG + SVG + ICO | Replace with optimized versions |

A bare-template build is **measured in single-digit kilobytes** before you add any application code. The HTTP overhead per request dominates on first paint; subsequent loads benefit from the cache strategy in §4.

### `humans.txt` and `robots.txt`

The template ships `robots.txt` (allow-all) but **does not ship `humans.txt`**. If you want one, drop a `humans.txt` file into `src/` and add a `CopyPlugin` pattern in `webpack.config.prod.js`:

```javascript
{ from: 'humans.txt', to: 'humans.txt' },
```

Reference it from your HTML `<head>`:

```html
<link rel="author" href="humans.txt">
```

The robots.txt baseline is intentionally permissive — change it only when you have specific paths to gate.

### Cache-busting strategies

The default `output.filename: './js/app.js'` produces an unhashed bundle. For production sites, switch to content-hashed names so you can serve them with `Cache-Control: max-age=31536000, immutable` and never worry about staleness:

```javascript
output: {
  path: path.resolve(__dirname, 'dist'),
  clean: true,
  filename: './js/[name].[contenthash].js',
}
```

`HtmlWebpackPlugin` automatically rewrites the `<script>` tag in the templated `index.html` to point at the hashed filename, so no manual HTML editing is needed. The hash changes only when the bundle's content changes — perfect for the immutable cache tier.

### Compression at the edge

The template assumes your CDN or origin handles gzip / brotli compression. For text assets (HTML, CSS, JS, JSON, SVG, manifest) this is non-negotiable — dropping it triples your transfer size. The companion `server-configs-*` repos all ship correct compression directives for their respective servers.

### Image optimization

The shipped icon set is unoptimized reference art. For production, run your icons through:

- `oxipng` or `optipng` for PNG (`icon.png`, `favicon.ico` if PNG-based).
- `svgo` for `icon.svg`.
- A favicon generator that emits multi-resolution `.ico` from a master SVG.

The template's image bucket (`src/img/`) is where you place your project's images; the build copies them verbatim. webpack does **not** auto-optimize them — wire that into your build separately if needed (e.g., via `image-webpack-loader`).

### Performance regression detection

For an upstream contributor / serious downstream maintainer:

- Run [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) on every PR via [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci).
- Track `dist/` total size on every release; alert on >5% regression.
- Track first-paint and first-contentful-paint as part of your synthetic monitoring (see §5).

---

## 7. Security

The template ships **no security headers** of its own — that responsibility lives in the companion `h5bp/server-configs-*` repos. This section enumerates the hardened defaults those repos provide, the headers you should set, and the supply-chain considerations specific to maintaining or consuming this template.

### Server-side headers (from companion `server-configs-*` repos)

The companion repos for Apache, Nginx, IIS, Lighttpd, Node.js, and GAE ship a hardened set of HTTP response headers. These are **not** built into the core template and **are not** present in `dist/` — they must be applied at the web-server / CDN layer.

| Header | Purpose | Recommended value (from h5bp configs) |
|---|---|---|
| `Strict-Transport-Security` (HSTS) | Forces HTTPS for the configured `max-age` | `max-age=31536000; includeSubDomains` (only after HTTPS is fully working — see warning below) |
| `Content-Security-Policy` (CSP) | Restricts which origins can load scripts / styles / images | Project-specific; start with `default-src 'self'` and broaden as needed |
| `X-Content-Type-Options` | Disables content-type sniffing | `nosniff` |
| `X-Frame-Options` | Clickjacking protection | `DENY` (or `SAMEORIGIN` if you embed yourself) |
| `Referrer-Policy` | Controls `Referer` header on outbound links | `strict-origin-when-cross-origin` |
| `Permissions-Policy` (formerly Feature-Policy) | Disables browser features by default (camera, geolocation, etc.) | Project-specific; deny-list features your site doesn't use |
| `Cross-Origin-Opener-Policy` | Isolates browsing contexts | `same-origin` |
| `Cross-Origin-Embedder-Policy` | Requires CORP headers on embedded resources | `require-corp` (only if your embeds support it) |
| `Cross-Origin-Resource-Policy` | Limits which other origins can embed your resource | `same-origin` |

> [!WARNING]
> **HSTS is sticky.** Once a browser caches a `Strict-Transport-Security` header from your origin, it will refuse plain-HTTP connections for the full `max-age` duration — even if you later disable HTTPS. **Do not** roll out HSTS until your TLS setup is end-to-end working: valid certs on every subdomain you `includeSubDomains` over, no mixed content, no plain-HTTP fallback you depend on. Start with a short `max-age` (e.g., 300 seconds) and ramp up over weeks.

> [!CAUTION]
> Do not blindly copy a Content-Security-Policy from a sample config. CSP is the most powerful and most error-prone header in the set — a too-strict policy breaks your site silently in some browsers; a too-loose one provides no protection. Always test in **report-only** mode (`Content-Security-Policy-Report-Only: ...`) for at least a week before enforcing, with a `report-uri` or `report-to` directive aimed at a log aggregator.

### Stance on inline scripts & inline styles

The shipped `index.html` does not inline any scripts. The shipped `404.html` **does** inline its CSS (intentionally, for the "page must be ≥512 bytes to bypass IE's friendly error pages" historical reason). The 404 page is a single static document with no user-supplied content, so its inlined CSS is safe under any reasonable CSP — but you'll need a CSP allowance (either `'unsafe-inline'` or a hash) if you set strict `style-src`.

For your own application code, **prefer external scripts and stylesheets** over inline. They:

- Are subject to cache rules separately from HTML.
- Don't require `'unsafe-inline'` in your CSP.
- Get bundled and minified by webpack.

If you must inline a snippet (e.g., a critical-CSS hero block, a third-party analytics tag), generate a SHA-256 hash and add it to your CSP's `script-src` or `style-src` rather than relaxing to `'unsafe-inline'`.

### Files you should NOT remove

| File | Why |
|---|---|
| `.editorconfig` | Prevents accidental whitespace / EOL drift; without it, diff review across editors gets noisy |
| `.gitattributes` | Forces LF line endings; if removed, Windows contributors will introduce CRLF that breaks the byte-content tests |
| `.gitignore` | Prevents `node_modules/` and stray lockfile copies from being committed; without it, accidental commits of `.env`-style files become much more likely |
| `.cspell.json` | Prevents typo-driven false positives in security-relevant docs; not strictly security but operationally useful |

The dotfiles look removable because they're "just config" — but each one is enforcing an invariant the project's CI relies on. Treat them as part of the CI surface, not as decoration.

### Supply chain — npm dependencies

| Concern | Mitigation in this repo |
|---|---|
| Transitive vulnerabilities | `overrides` block pins `diff: ^8.0.3`, `serialize-javascript: ^7.0.5` |
| Lockfile integrity | `package-lock.json` (lockfileVersion 3) committed; `npm ci` in CI |
| Audit cadence | Tracked in [#3415](https://github.com/h5bp/html5-boilerplate/issues/3415) (closed) |
| Publishing modernization | Tracked in [#3411](https://github.com/h5bp/html5-boilerplate/issues/3411) (open) |

When integrating the template into a downstream project:

1. Run `npm audit` after every `npm install`. The maintainer's lockfile resolves to known-good versions, but your additional dependencies might pull in vulnerable transitives.
2. Consider [Socket](https://socket.dev), [Snyk](https://snyk.io/), or [Dependabot](https://github.com/dependabot) for automated alerts.
3. Pin direct dependencies in your project to `^X.Y.Z` and run `npm update` weekly; bump majors deliberately.

> [!WARNING]
> The default `npm install` command will execute `postinstall` scripts in any package in your dependency tree. This is the standard supply-chain attack vector. For high-security deployments, set `npm config set ignore-scripts true` and review postinstall scripts manually before running them. Note that disabling `ignore-scripts` repo-wide may break legitimate native-module builds — apply it as part of CI policy rather than a developer machine default.

### `.npmrc` audit

The repo includes an `.npmrc`. Whatever it sets (registry URL, scope mappings, auth tokens) ships with every clone — review before merging upstream changes to it. Auth tokens MUST NOT be committed; they belong in CI secrets.

### License

[MIT](https://github.com/h5bp/html5-boilerplate/blob/main/LICENSE.txt). Permissive; allows commercial use, modification, and redistribution without attribution. From a security-supply-chain perspective, MIT carries no copyleft obligations — your downstream consumers do not inherit any obligation when they use the template.

### Reporting vulnerabilities

For vulnerabilities **in the template itself**: open a private [Security advisory](https://github.com/h5bp/html5-boilerplate/security/advisories) on GitHub. Do not file a public issue.

For vulnerabilities **in your downstream site**: that's your project's responsibility — declare a `.well-known/security.txt` for inbound reports, run a coordinated disclosure process, and patch promptly.

---

## 8. Developer Internals

This section is for upstream contributors to `h5bp/html5-boilerplate` and downstream maintainers who want to understand the project at a depth beyond "use it as scaffolded."

### Repository structure (annotated)

```text
html5-boilerplate/
├── .github/                  # GitHub Actions, issue templates, CODEOWNERS
├── docs/                     # Canonical user-facing docs
│   ├── TOC.md
│   ├── about-this-repo.md
│   ├── css.md
│   ├── extend.md
│   ├── faq.md
│   ├── html.md
│   ├── js.md
│   ├── misc.md
│   └── usage.md
├── src/                      # The deliverable — what ships to downstream users
│   ├── img/.gitkeep
│   ├── js/app.js             # Empty placeholder; user fills in
│   ├── 404.html
│   ├── favicon.ico
│   ├── icon.png
│   ├── icon.svg
│   ├── index.html
│   ├── package.json          # Per-site manifest with webpack scripts
│   ├── robots.txt
│   ├── site.webmanifest
│   ├── webpack.common.js
│   ├── webpack.config.dev.js
│   ├── webpack.config.prod.js
│   ├── .editorconfig
│   ├── .gitattributes
│   └── .gitignore
├── test/                     # Mocha tests
│   ├── file_content.mjs      # Asserts byte-content of dist/css/style.css banner
│   └── file_existence.mjs    # Asserts every expected dist/* file is present
├── .cspell.json              # Spell-check config
├── .editorconfig             # Cross-editor whitespace rules
├── .gitattributes            # Git EOL normalization
├── .gitignore                # node_modules, archive, etc.
├── .npmrc                    # npm publish/install config
├── .prettierignore           # Prettier ignore list
├── .prettierrc.json          # Prettier rules
├── CHANGELOG.md              # Release history
├── eslint.config.mjs         # ESLint flat config
├── gulpfile.mjs              # Maintainer build orchestrator
├── LICENSE.txt               # MIT
├── package.json              # Maintainer manifest (gulp, mocha, eslint, prettier)
├── package-lock.json         # Locked dep tree (lockfileVersion 3)
└── README.md                 # Project intro
```

### Key invariants

The project enforces several implicit invariants. Breaking them breaks downstream consumers; the test suite catches some but not all.

| Invariant | Enforced where | Why |
|---|---|---|
| `dist/` byte-for-byte stable | `test/file_content.mjs` | Downstream users diff against previous releases; unexpected changes signal regressions |
| Every `src/` static asset has a `CopyPlugin` pattern | Manual review | Without one, the asset is missing from `dist/` |
| `engines.node >= 20` | `package.json` | Prevents downstream users from shipping with deprecated Node versions |
| Single space placeholder for `src/package.json` `name` | `src/package.json` | Avoid registering `html5-boilerplate` (the per-site manifest's name) by accident |
| `gulpfile.mjs` reads `h5bp-configs.directories` | `package.json` + `gulpfile.mjs` | Allows downstream forks to relocate dirs without editing the gulpfile |
| LF line endings everywhere | `.gitattributes` | Tests assert exact byte content; CRLF on Windows breaks them |
| No runtime npm dependencies | `package.json` `dependencies` is absent | `dist/` is purely static; no runtime to install |

### Module reference

#### `src/webpack.common.js`

The shared webpack config. The **only** dependency from `node_modules` is `path` (Node built-in). It exports a config object with `entry` (`{ app: './js/app.js' }`) and `output` (`{ path: dist/, clean: true, filename: './js/app.js' }`). Both dev and prod configs `merge` against it.

The `output.clean: true` setting is what guarantees no stale assets survive across builds — webpack 5 deletes the contents of `dist/` before each emit.

#### `src/webpack.config.dev.js`

Adds `mode: 'development'`, `devtool: 'inline-source-map'`, and a `devServer` block with hot reload, live reload, auto-open, and `static: ['./']`. This file imports `webpack-merge` and the common config; its only `node_modules` dependency is `webpack-merge`.

If you want to change the dev server port, add `port: <N>` inside the `devServer` block. If you want to disable auto-open (helpful in CI / containerized dev), set `open: false`.

#### `src/webpack.config.prod.js`

Adds `mode: 'production'` and registers two plugins:

1. `HtmlWebpackPlugin({ template: './index.html' })` — reads the template, injects the bundled `<script>` tag, and emits the result to `dist/index.html`.
2. `CopyPlugin({ patterns: [...] })` — copies static assets from project root to `dist/`. The pattern list is the canonical inventory of "what is in `dist/`."

The 9 patterns currently listed:

```text
img/        →  dist/img/
css/        →  dist/css/
js/vendor/  →  dist/js/vendor/
icon.svg    →  dist/icon.svg
favicon.ico →  dist/favicon.ico
robots.txt  →  dist/robots.txt
icon.png    →  dist/icon.png
404.html    →  dist/404.html
site.webmanifest → dist/site.webmanifest
```

Modifying this list is the single most common edit downstream users make to the webpack config.

#### `gulpfile.mjs` (maintainer-only)

Orchestrates lint, archive, and the release dist. The `archive` task reads the `h5bp-configs.directories` block from root `package.json` to know where source / output / archive live, builds `dist/` from `src/`, autoprefixes CSS via `gulp-autoprefixer`, prepends a version banner via `gulp-header`, and packages the result into `archive/` for release.

Downstream users **never** invoke gulp — it's an upstream-only concern. The `dist/` you get from `npm run build` (per-site, via webpack) and the `dist/` the maintainer assembles via `gulp archive` are slightly different: the latter includes only the static deliverables (no `package.json`, no webpack configs); the former includes everything the per-site `webpack.config.prod.js` is told to copy.

### Code patterns and conventions

| Convention | Where applied | Tooling |
|---|---|---|
| Single quotes in JS | `*.js`, `*.mjs` | Prettier (`.prettierrc.json`), ESLint |
| Double quotes in YAML | `*.yml` | Prettier override |
| 2-space indent | All formats | `.editorconfig` |
| LF line endings | All text | `.gitattributes` |
| ESM imports | `gulpfile.mjs`, `eslint.config.mjs`, `test/*.mjs` | Native Node ESM |
| CommonJS in webpack configs | `src/webpack.*.js` | Compatible with webpack 5's loader |
| Semicolons required | All JS | ESLint |
| No unused vars | All JS | ESLint |

### Testing strategy

The upstream test suite is intentionally minimal. Two Mocha files:

- `test/file_existence.mjs` — verifies that every expected file in `dist/` exists after `gulp archive`.
- `test/file_content.mjs` — verifies the byte content of `dist/css/style.css` matches the expected banner (catches whitespace-drift regressions).

Run with `npm test`. The test runner has a 5-second per-test timeout (`--timeout 5000` in the test script) — generous because file-existence checks are cheap.

There are **no** end-to-end browser tests, **no** linting tests as part of `npm test`, and **no** snapshot tests of the HTML output. Linting runs separately via `gulp lint:js` (or directly via `eslint`).

> [!NOTE]
> The minimal test surface reflects the project's nature: it's a template, not an application. Tests guard the deliverable's shape; they don't guard "does this site work in a browser." Downstream users add their own tests against their own application code.

### Potential improvements & open work

These come straight from the upstream issue tracker; cite by number when you contribute:

- [#3411](https://github.com/h5bp/html5-boilerplate/issues/3411) (open) — Modernize npm publishing & auth (token management, OIDC trusted publishers).
- [#3412](https://github.com/h5bp/html5-boilerplate/issues/3412) (open) — Migrate to Repository Rulesets to fix the template-push automation.
- [#3373](https://github.com/h5bp/html5-boilerplate/issues/3373) (open) — Remove Twitter Cards docs (Twitter/X falls back to Open Graph now).
- [#3228](https://github.com/h5bp/html5-boilerplate/issues/3228) (open) — Release 9.0.2 (post-9.0.1 patch tracking).
- [#3038](https://github.com/h5bp/html5-boilerplate/issues/3038) (open) — Enhance accessibility documentation for inclusive web development.
- [#3018](https://github.com/h5bp/html5-boilerplate/issues/3018) (open) — Expand `theme-color` meta tag docs in `extend.md`.
- [#3031](https://github.com/h5bp/html5-boilerplate/issues/3031) (open) — Document `color-scheme` meta attribute.
- [#3229](https://github.com/h5bp/html5-boilerplate/issues/3229) (open) — Document TypeScript compilation.
- [#3413](https://github.com/h5bp/html5-boilerplate/issues/3413) (closed) — Migrate GitHub Actions to Node 22/24 to avoid runner deprecation.
- [#3415](https://github.com/h5bp/html5-boilerplate/issues/3415) (closed) — Audit Package Lock health & supply-chain tooling.

### Contributing workflow

1. Fork `h5bp/html5-boilerplate` and clone your fork.
2. `npm install` at the root (installs maintainer toolchain).
3. Create a topic branch: `git checkout -b fix/short-description`.
4. Make your change. If it touches `src/`, run a fresh `cd src && npm install && npm run start` to verify the per-site flow still works.
5. Run `npm run prettier` to auto-format any files you touched.
6. Run `gulp lint:js` to catch ESLint errors.
7. Run `npm test` to confirm `dist/` shape and content tests pass.
8. Push your branch and open a pull request against `main`. Reference any related issue in the PR description.
9. Address review feedback. The project uses a single-`main`-branch model; no `develop` branch.

> [!IMPORTANT]
> Run `npm test` on Windows if your change touches anything line-ending-sensitive. The test suite was hardened against Windows in the v9.0.1 release line, but new edits can re-introduce CRLF drift if they bypass `.gitattributes`.

### Where contributions get blocked

The template's surface area is small, which means most "improvement" PRs end up wanting to expand scope (add a CSS reset, ship a service worker, bundle a framework). The maintainers have historically pushed back hard on scope creep — the template is deliberately minimal and the v9.0.0 release line is a continuation of that philosophy (drop Modernizr, drop Normalize, drop Google Analytics, drop `.htaccess` from the core repo). Read `docs/about-this-repo.md` and the `CHANGELOG.md` for the philosophy before proposing additions.

---

## 9. Integration Points

This section covers how HTML5 Boilerplate plays with other systems — build tools, frameworks, prior versions of itself, and the broader h5bp ecosystem.

### Build tool integration

The template ships webpack 5, but nothing in the `src/` deliverable depends on webpack specifically. Migrating to a different bundler is a matter of pointing the new bundler at `src/index.html`, `src/js/app.js`, and the static assets enumerated in `webpack.config.prod.js`'s `CopyPlugin`.

#### Migrating to Vite

```bash
npm uninstall webpack webpack-cli webpack-dev-server webpack-merge \
              html-webpack-plugin copy-webpack-plugin
npm install --save-dev vite
```

Replace `src/package.json` scripts:

```json
{
  "scripts": {
    "start": "vite",
    "build": "vite build"
  }
}
```

Vite uses `index.html` at the project root as its entry by convention. The static assets need a `public/` folder convention (Vite copies `public/*` to `dist/` automatically) — move `404.html`, `favicon.ico`, `icon.png`, `icon.svg`, `robots.txt`, and `site.webmanifest` into `public/`.

#### Migrating to Parcel

```bash
npm uninstall webpack webpack-cli webpack-dev-server webpack-merge \
              html-webpack-plugin copy-webpack-plugin
npm install --save-dev parcel
```

Parcel's zero-config approach: point at `index.html` and it figures out the rest.

```json
{
  "scripts": {
    "start": "parcel index.html",
    "build": "parcel build index.html --dist-dir dist"
  }
}
```

#### Migrating to esbuild / Rollup

Both work. esbuild needs an explicit static-asset copy step (esbuild itself only handles JS/CSS); Rollup needs a plugin (`@rollup/plugin-html` for templating, `rollup-plugin-copy` for assets). Neither replicates the dev-server experience of webpack-dev-server out of the box.

> [!TIP]
> The bundler choice is a downstream decision and should be made based on your team's preferences and the size of your application. The template intentionally ships webpack as the lowest-common-denominator default, but the maintainers do not consider it sacred — switching is supported and well-trodden.

### Framework integration

| Framework | Integration approach |
|---|---|
| **React** | `npm install react react-dom`. Replace `app.js` with `app.jsx`. Add `babel-loader` and `@babel/preset-react` in webpack. Or migrate to Vite + `@vitejs/plugin-react`. |
| **Vue** | `npm install vue`. Add `vue-loader` to webpack, configure `.vue` SFC handling. Or use the upstream `vite + create-vue` flow. |
| **Svelte** | Add `svelte-loader` to webpack, or use Vite + `@sveltejs/vite-plugin-svelte`. |
| **Solid / Preact / Lit** | Same pattern — install the framework, register a loader/plugin, point `entry` at the framework root. |

For more complex framework needs (router, state management, SSR), most teams outgrow the bundled webpack config and adopt the framework's CLI (`next`, `nuxt`, `sveltekit`, `astro`). That's expected — the boilerplate gets you to the starting line, and a framework CLI takes you the rest of the way.

### Upgrading from prior H5BP versions

| From | Key migration concerns |
|---|---|
| **v8.x → v9.0.x** | `.htaccess` removed from core; pull from `h5bp/server-configs-apache` instead. Modernizr dropped; replace with `@supports` CSS or install `modernizr` separately. Normalize.css replaced by `h5bp/main.css`. Google Analytics snippet removed; add yours manually. |
| **v7.x → v8.x** | Internet Explorer support dropped. jQuery removed from core; install separately if needed. |
| **v6.x → v7.x** | `apache_server_configs` extracted into a separate repo (now `server-configs-apache`). |
| **Pre-v6 → modern** | Treat as a fresh-scaffold; the surface has changed too much for an incremental migration. |

> [!WARNING]
> The "drop everything and re-scaffold" approach is often the fastest migration path for jumps of more than one major version. The template's deliverable is a few dozen files; copying your customizations into a freshly-scaffolded project is usually faster than auditing every changed default since the version you're on.

### Companion `h5bp/*` repos

The h5bp organization maintains a constellation of related repos:

| Repo | Purpose |
|---|---|
| [`h5bp/html5-boilerplate`](https://github.com/h5bp/html5-boilerplate) | This repo — the core template |
| [`h5bp/html5-boilerplate-template`](https://github.com/h5bp/html5-boilerplate-template) | GitHub-template-repo mirror; clickable "Use this template" |
| [`h5bp/create-html5-boilerplate`](https://github.com/h5bp/create-html5-boilerplate) | The `npx create-html5-boilerplate` scaffolder |
| [`h5bp/main.css`](https://github.com/h5bp/main.css) | Source of `dist/css/style.css` (versioned npm package, currently `3.0.0`) |
| [`h5bp/server-configs-apache`](https://github.com/h5bp/server-configs-apache) | Hardened `.htaccess` for Apache |
| [`h5bp/server-configs-nginx`](https://github.com/h5bp/server-configs-nginx) | Hardened `nginx.conf` snippets |
| [`h5bp/server-configs-iis`](https://github.com/h5bp/server-configs-iis) | Hardened `web.config` for IIS |
| [`h5bp/server-configs-lighttpd`](https://github.com/h5bp/server-configs-lighttpd) | Hardened Lighttpd configs |
| [`h5bp/server-configs-node`](https://github.com/h5bp/server-configs-node) | Node.js HTTP server templates |
| [`h5bp/server-configs-gae`](https://github.com/h5bp/server-configs-gae) | Google App Engine `app.yaml` |

Each is independently versioned and released. When you pin your downstream project to a specific h5bp release, decide whether you also want to pin a specific server-configs release — the security defaults change over time.

### CI integration

The template doesn't ship a `.github/workflows/` directory in the per-site `dist/` (it's a per-project decision), but the upstream repo's CI uses GitHub Actions. The canonical pattern for downstream projects:

```yaml
name: Build & Test

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build
      - run: npm test
```

For deploy-on-push to GitHub Pages, see the User Guide §5 recipe.

### Telemetry / opt-out integration

The template ships **no** telemetry, **no** analytics, **no** beacons. Adding them is a downstream decision:

- **Privacy-respecting**: [Plausible](https://plausible.io/), [Fathom](https://usefathom.com/), [Umami](https://umami.is/).
- **Standard-issue**: [Google Analytics 4](https://analytics.google.com/), [Matomo](https://matomo.org/), [Mixpanel](https://mixpanel.com/).
- **Self-hosted**: GoatCounter, Plausible Community Edition.

Whatever you choose, add the snippet to `src/index.html` (so it's templated into every page by `HtmlWebpackPlugin`), and review your CSP `script-src` allowance.

### Long-term project sustainability

The h5bp project is community-maintained, with primary maintenance occurring through GitHub PRs and Discussions. There is no commercial backer or paid support tier. Adoption decisions for downstream production sites should account for:

- The project's release cadence (irregular; major releases years apart).
- The maintainer pool's bandwidth (small but active).
- The narrow scope (template, not framework) limits the surface area where breaking changes can occur.

> [!NOTE]
> The "low cadence + narrow scope" combination has historically meant that templates scaffolded years ago still work today with no migration. The trade-off: new web platform features (CSS modules, View Transitions API, container queries, etc.) land in the template only when a contributor has a specific use case for them. Treat the template as a starting point, not a continuously-updated runtime — once you've scaffolded, your project is yours.

---

_Generated April 2026 from h5bp/html5-boilerplate at 31357fd. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
