Jesse A. Green / notebook

HTML5 Boilerplate Administrator & Developer Guide

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.

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; 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 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:

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:

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:

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:

{
  "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:

{
  "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:

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:

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:

[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:

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:

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

npm test
> 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). If it failed, fix manually before the release is "done."
  • [ ] Draft a GitHub Release with the changelog snippet attached.
  • [ ] Refresh the html5boilerplate.com site (separately maintained).

Note

The npm-publishing flow itself is being modernized (see #3411). The template-mirror workflow has known issues tracked in #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:

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

Reference it from your HTML <head>:

<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:

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 on every PR via 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 (closed)
Publishing modernization Tracked in #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, Snyk, or 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. 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 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)

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:

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 (open) — Modernize npm publishing & auth (token management, OIDC trusted publishers).
  • #3412 (open) — Migrate to Repository Rulesets to fix the template-push automation.
  • #3373 (open) — Remove Twitter Cards docs (Twitter/X falls back to Open Graph now).
  • #3228 (open) — Release 9.0.2 (post-9.0.1 patch tracking).
  • #3038 (open) — Enhance accessibility documentation for inclusive web development.
  • #3018 (open) — Expand theme-color meta tag docs in extend.md.
  • #3031 (open) — Document color-scheme meta attribute.
  • #3229 (open) — Document TypeScript compilation.
  • #3413 (closed) — Migrate GitHub Actions to Node 22/24 to avoid runner deprecation.
  • #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

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:

{
  "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

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.

{
  "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 This repo — the core template
h5bp/html5-boilerplate-template GitHub-template-repo mirror; clickable "Use this template"
h5bp/create-html5-boilerplate The npx create-html5-boilerplate scaffolder
h5bp/main.css Source of dist/css/style.css (versioned npm package, currently 3.0.0)
h5bp/server-configs-apache Hardened .htaccess for Apache
h5bp/server-configs-nginx Hardened nginx.conf snippets
h5bp/server-configs-iis Hardened web.config for IIS
h5bp/server-configs-lighttpd Hardened Lighttpd configs
h5bp/server-configs-node Node.js HTTP server templates
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:

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:

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.