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(commit31357fd, source date 2026-04-22). Behaviour described here may differ in earlier or later versions — always cross-checkpackage.jsonversionand theCHANGELOG.mdof 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.3andserialize-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.jsonandsrc/package.jsonis 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
>=20is a hard floor — install will fail withEBADENGINEon 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
defaultsBrowserslist 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 custombrowserslistfield in your project'spackage.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.tsif 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 indist/before each build. Prevents leftover assets from previous builds from being deployed.output.filename— bundle path insidedist/. 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
CopyPluginentry is the most common downstream deploy bug. There is no warning at build time — the missing file is simply absent fromdist/. 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:
| Server | Companion repo |
|---|---|
| Apache | h5bp/server-configs-apache |
| Nginx | h5bp/server-configs-nginx |
| IIS | h5bp/server-configs-iis |
| Lighttpd | h5bp/server-configs-lighttpd |
| Node.js | h5bp/server-configs-node |
| Google App Engine | 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" insideh5bp/html5-boilerplateis 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@v4withnode-version: 20andcache: npmto keep CI fast. - Run
npm ci(notnpm install) for reproducible builds. - Upload
dist/viaactions/upload-pages-artifact@v3, then deploy viaactions/deploy-pages@v4. - If your repo is hosted at
https://USERNAME.github.io/REPO/(i.e., not a custom domain), use relative paths inindex.htmlinstead 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.filenamefrom'./js/app.js'to'./js/[name].[contenthash].js'for production builds — combined withHtmlWebpackPlugin(which auto-rewrites the script tag), this enables theimmutablecaching 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.icowith your 16×16 / 32×32 / 48×48 multi-resolution.ico. - [ ] Replace
src/icon.svgwith your scalable icon (preferred for modern browsers). - [ ] Replace
src/icon.pngwith at least a 192×192 PNG. Add a 512×512 entry tosite.webmanifestfor PWA installability. - [ ] Edit
src/site.webmanifestto fill inname,short_name, additionaliconsentries,theme_color,background_color. - [ ] Edit
src/index.html: set<html lang="...">,<title>,<meta name="description">, allog:*tags, and update<meta name="theme-color" content="...">to matchsite.webmanifest. - [ ] Edit
src/robots.txt: addSitemap:directive,Disallow:rules for staging/private paths. - [ ] Optional — add
.well-known/security.txttosrc/, then add aCopyPluginpattern for.well-knowninwebpack.config.prod.js. - [ ] Optional — add
humans.txttosrc/, plus aCopyPluginpattern.
Note
Because each new static asset requires a
CopyPluginpattern inwebpack.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:
- Read the upstream changelog of the package being bumped.
- Bump in a feature branch.
- Run
npm test(mocha + gulparchive). - Manually scaffold a fresh project from the new version and verify
npm run startandnpm run buildboth succeed. - Compare
dist/byte-for-byte against the previous release; investigate any unexpected changes.
Tip
The
overridesblock in rootpackage.json(currentlydiff: ^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
versionin rootpackage.json. - [ ] Update
CHANGELOG.mdwith the new release section. - [ ] Run
npm testto confirmdist/is correct. - [ ]
git tag v9.0.X && git push --tags - [ ]
npm publish(publishesdist/per thefileswhitelist inpackage.json). - [ ] Confirm the GitHub Actions workflow that mirrors
maintoh5bp/html5-boilerplate-templateran 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.webmanifestevery 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:
oxipngoroptipngfor PNG (icon.png,favicon.icoif PNG-based).svgoforicon.svg.- A favicon generator that emits multi-resolution
.icofrom 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-Securityheader from your origin, it will refuse plain-HTTP connections for the fullmax-ageduration — 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 youincludeSubDomainsover, no mixed content, no plain-HTTP fallback you depend on. Start with a shortmax-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 areport-uriorreport-todirective 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:
- Run
npm auditafter everynpm install. The maintainer's lockfile resolves to known-good versions, but your additional dependencies might pull in vulnerable transitives. - Consider Socket, Snyk, or Dependabot for automated alerts.
- Pin direct dependencies in your project to
^X.Y.Zand runnpm updateweekly; bump majors deliberately.
Warning
The default
npm installcommand will executepostinstallscripts in any package in your dependency tree. This is the standard supply-chain attack vector. For high-security deployments, setnpm config set ignore-scripts trueand review postinstall scripts manually before running them. Note that disablingignore-scriptsrepo-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:
HtmlWebpackPlugin({ template: './index.html' })— reads the template, injects the bundled<script>tag, and emits the result todist/index.html.CopyPlugin({ patterns: [...] })— copies static assets from project root todist/. The pattern list is the canonical inventory of "what is indist/."
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 indist/exists aftergulp archive.test/file_content.mjs— verifies the byte content ofdist/css/style.cssmatches 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-colormeta tag docs inextend.md. - #3031 (open) — Document
color-schememeta 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
- Fork
h5bp/html5-boilerplateand clone your fork. npm installat the root (installs maintainer toolchain).- Create a topic branch:
git checkout -b fix/short-description. - Make your change. If it touches
src/, run a freshcd src && npm install && npm run startto verify the per-site flow still works. - Run
npm run prettierto auto-format any files you touched. - Run
gulp lint:jsto catch ESLint errors. - Run
npm testto confirmdist/shape and content tests pass. - Push your branch and open a pull request against
main. Reference any related issue in the PR description. - Address review feedback. The project uses a single-
main-branch model; nodevelopbranch.
Important
Run
npm teston 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:
- Privacy-respecting: Plausible, Fathom, Umami.
- Standard-issue: Google Analytics 4, Matomo, Mixpanel.
- 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.