Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

HTML5 Boilerplate User 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.

HTML5 Boilerplate (h5bp) is a professional front-end template for building fast, robust, and adaptable web apps or sites. It is the product of more than ten years of iterative development by 200+ contributors and ships as a small, hand-curated set of HTML, CSS scaffolding, icons, manifests, and webpack configuration that you drop into a new project as the foundation.

This guide walks you through every supported way to start a project, the contents of the template, the build commands you will run day-to-day, and the practical extension points (Open Graph, theme color, web app manifest, search, social, security.txt) that the template ships ready to wire up.

Note

HTML5 Boilerplate does not impose a development philosophy or framework. The whole point is that you are free to architect your code however you want — the template just gives you a sane, modern starting line. Current release is v9.0.1 under the MIT license, requiring Node.js >=20.


1. Quick Start

The fastest way to start a brand-new project is the create-html5-boilerplate scaffolder. It downloads the latest published template from npm, unpacks it into a folder you name, and leaves you ready to install dependencies — no global tools, no clones, no zip extraction.

npx create-html5-boilerplate my-site
cd my-site
npm install
npm run start
> webpack serve --open --config webpack.config.dev.js
[webpack-dev-server] Project is running at:
[webpack-dev-server]   http://localhost:8080/

Your default browser opens automatically to a "Hello world! This is HTML5 Boilerplate." page with hot-module replacement and live reload enabled. Edit index.html, js/app.js, or css/style.css and the browser refreshes immediately.

Tip

If npx create-html5-boilerplate is not yet available on your machine, the command auto-downloads the scaffolder package on first run. There is no install step. The package is published at create-html5-boilerplate on npm.

What you just got

After scaffolding you have a project folder containing the content of what was previously the upstream repo's src/ folder — meaning your project root is the template. You will see (among other files) index.html, 404.html, robots.txt, site.webmanifest, the icon set, js/app.js, and three webpack config files.

Try it: edit your first page

  1. Open index.html in your editor.
  2. Change <html lang=""> to <html lang="en">.
  3. Set <title>My First Site</title>.
  4. Save. The browser auto-refreshes.

What to try next

  • Section 2 — every supported install method (zip download, GitHub template repo, npm install, git clone of the upstream repo).
  • Section 3 — the meta tags, manifest, and dotfiles you need to fill in before going to production.
  • Section 4 — the webpack scripts and the layout of the shipped files.
  • Section 5 — recipe-style walkthroughs for the most common day-one tasks (replace icons, configure Open Graph, set caching headers via the companion server configs, deploy to GitHub Pages / Netlify / Vercel, run linters, build for production).

2. Installation

System requirements

Requirement Minimum Notes
Node.js >=20 Enforced via engines.node in src/package.json. CI runs Node 22 and Node 24.
Volta-pinned Node 20.19.1 Used for reproducible local builds via Volta.
Operating system Any macOS, Linux, Windows (CRLF normalization handled by .gitattributes).
Browser (for previewing) Latest stable The template targets Browserslist default.

Internet Explorer is not supported (dropped in v8.0.0). Modernizr is not bundled (removed in v9.0.0). There are no runtime dependencies for the published site — the deliverable is pure static output (dist/).

Important

You need Node.js for the development workflow (running webpack, building production bundles). You do not need Node.js to host the site in production — the build output (dist/) is plain HTML, CSS, JS, and images that any static host can serve.

Pick your install method

create-html5-boilerplate (recommended)

Scaffold a brand-new project. Fastest path. No global install required.

npx create-html5-boilerplate my-site
cd my-site
npm install
npm run start

The scaffolder fetches the latest published html5-boilerplate package from npm and unpacks dist/ into the folder you specify.

GitHub Template Repo

Use the HTML5 Boilerplate template repository on GitHub. Click Use this template → Create a new repository to seed a brand new GitHub repo from the latest main branch.

Then on your machine:

git clone https://github.com/your-username/your-new-repo.git my-site
cd my-site
npm install
npm run start

The template repo is auto-mirrored from the upstream main branch by the push-to-template.yml workflow, so it always reflects the latest release.

npm install (existing project)

Install the package into an existing project and copy the contents of node_modules/html5-boilerplate/dist/ into your project root:

npm install html5-boilerplate
yarn add html5-boilerplate

The dist/ folder is the actual deliverable — everything else in the upstream repo is build tooling for the project itself.

Download zip

Grab a tagged release zip from GitHub Releases. The zip is html5-boilerplate_v9.0.1.zip (or the current version) and contains the same files as dist/.

mkdir my-site
cd my-site
curl -LO https://github.com/h5bp/html5-boilerplate/releases/latest/download/html5-boilerplate_v9.0.1.zip
unzip html5-boilerplate_v9.0.1.zip
npm install
npm run start

On Windows PowerShell:

Expand-Archive html5-boilerplate_v9.0.1.zip -DestinationPath .

git clone (upstream)

Only useful if you intend to contribute back to the project itself. The upstream repo includes the gulpfile, test suite, and CHANGELOG that are not part of the deliverable — those are build tooling for the maintainers.

git clone https://github.com/h5bp/html5-boilerplate.git
cd html5-boilerplate
npm install
npm run build

See the Administrator & Developer Guide for the full upstream contribution workflow.

Tip

For 99% of users starting a new site, npx create-html5-boilerplate my-site is the right answer. The other methods exist for migration scenarios (drop the template into an existing app) or air-gapped/offline use (the zip).

Verify your install

After npm install you can confirm the dev server starts:

npm run start
> webpack serve --open --config webpack.config.dev.js
asset js/app.js 28 bytes [emitted] (name: app)
webpack 5.105.3 compiled successfully

Then build the production bundle:

npm run build
> webpack --config webpack.config.prod.js
asset index.html 656 bytes [emitted]
asset js/app.js 26 bytes [emitted] [minimized] (name: app)
asset 404.html 1.74 KiB [emitted] [from: 404.html] [copied]
... (icons, manifest, robots.txt, css/style.css copied)
webpack 5.105.3 compiled successfully

The dist/ folder is what you upload to your web host.

Warning

The development bundle includes inline-source-map source maps and is unminified. Always run npm run build before deploying to production — the dev bundle is much larger and exposes your source.

Install troubleshooting

  • EBADENGINE on Node 18 or older — the template requires Node 20+. Run nvm install 20 && nvm use 20, or use Volta (volta install node@20).
  • npm error code EACCES during global install — you do not need a global install. npx create-html5-boilerplate runs the scaffolder one-off without touching your global node_modules.
  • gulp: command not found — that's the upstream maintainer build tool. Downstream users use npm run start and npm run build instead. If you cloned the upstream repo by mistake, see the AdminGuide.

3. Configuration

The template's "configuration" is the set of meta tags, manifest fields, and dotfiles that you fill in once per project. There is no central config file — each piece lives in the asset that needs it.

Files you customize per project

File What you change Required for
index.html <title>, <meta name="description">, <html lang>, <meta name="theme-color">, all og:* tags Every site
site.webmanifest name, short_name, additional icon sizes, display, theme_color, background_color Installable PWAs
robots.txt Disallow: paths, optional Sitemap: directive Sites with private routes or a sitemap
favicon.ico, icon.svg, icon.png Replace with your own assets Branding
404.html Friendly error page text Production sites
package.json (your project) name, version, scripts, dev dependencies Every site

index.html — annotated

The shipped index.html is intentionally minimal:

<!doctype html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="css/style.css">
    <meta name="description" content="">

    <meta property="og:title" content="">
    <meta property="og:type" content="">
    <meta property="og:url" content="">
    <meta property="og:image" content="">
    <meta property="og:image:alt" content="">

    <link rel="icon" href="/favicon.ico" sizes="any">
    <link rel="icon" href="/icon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="icon.png">

    <link rel="manifest" href="site.webmanifest">
    <meta name="theme-color" content="#fafafa">
  </head>

  <body>
    <!-- Add your site or application content here -->
    <p>Hello world! This is HTML5 Boilerplate.</p>
    <script src="js/app.js"></script>
  </body>
</html>

The blank lang="", blank <title>, blank <meta name="description">, and blank og:* properties are intentional placeholders that you fill in for every page.

Common option combinations

Goal Tags / fields to set
Basic SEO <title>, <meta name="description">, <html lang="...">
Rich social preview All og:* fields including og:image:alt
Installable PWA site.webmanifest filled in, both 192×192 + 512×512 icons, HTTPS, valid start_url
Block crawlers from one path Add Disallow: /admin/ to robots.txt
Block all indexing (staging) Replace Disallow: with Disallow: / AND add <meta name="robots" content="noindex"> to index.html
Disable iOS phone-number auto-linking Add <meta name="format-detection" content="telephone=no">
Disable Chrome auto-translation Add <meta name="google" content="notranslate">

The shipped site.webmanifest

{
  "short_name": "",
  "name": "",
  "icons": [{
    "src": "icon.png",
    "type": "image/png",
    "sizes": "192x192"
  }],
  "start_url": "/?utm_source=homescreen",
  "background_color": "#fafafa",
  "theme_color": "#fafafa"
}

Fill in short_name and name. Add a 512×512 icon entry to satisfy PWA install criteria.

The shipped robots.txt

# https://www.robotstxt.org/

# Allow crawling of all content
User-agent: *
Disallow:

This explicitly allows every crawler to visit every page. The empty Disallow: line is correct robots.txt syntax for "no paths are blocked."

Caution

robots.txt is not access control. URLs disallowed there can still be indexed (just not crawled), and the file itself is publicly readable — listing private paths in Disallow: advertises their existence. Use authentication for anything that must stay private.

Dotfiles

File What it does
.editorconfig Forces 2-space indent, LF endings, UTF-8, trailing-whitespace trim across editors
.gitattributes Forces LF line endings on .css, .html, .js, .json, .md, .mjs, .sh, .txt, .xml, .yml
.gitignore Ignores node_modules, dist/package-lock.json, archive output

Don't delete the .editorconfig or .gitattributes — they prevent cross-platform contributors from accidentally introducing CRLF / indentation drift.

Important

The full architectural reference for these files (and the upstream test suite that asserts the exact byte content of style.css) lives in the Administrator & Developer Guide section 8 (Developer Internals).


4. Core Usage

The day-to-day surface for downstream users is just two commands and a small set of files you edit. This section covers exactly what each command does and what each shipped file is for.

The two scripts you run

npm run start
> webpack serve --open --config webpack.config.dev.js

This starts the webpack dev server with liveReload, hot-module replacement, and open: true (auto-opens your default browser). The server uses an inline source map (devtool: 'inline-source-map') so debugging works directly in the browser DevTools.

npm run build
> webpack --config webpack.config.prod.js

This produces a dist/ folder containing index.html (templated by HtmlWebpackPlugin), 404.html, the icon set, js/app.js (bundled), js/vendor/* (copied verbatim), css/, img/, robots.txt, and site.webmanifest. Upload the contents of dist/ to any static host.

There is no other npm script you typically run. (The default npm test script in src/package.json is echo "Error: no test specified" && exit 1 — a placeholder for you to replace with your own test runner.)

Note

The npm run start and npm run build scripts live in src/package.json (the per-site package.json that ships with the template), not in the upstream repo's root package.json. After scaffolding, your project root is what was previously src/.

Shipped file inventory

When you scaffold a new project, you get exactly this layout. (The build output in dist/ is the same files, plus a bundled js/app.js.)

.
├── 404.html              # Placeholder 404 error page (inline-styled, no external deps)
├── css/
│   └── style.css         # Sourced from h5bp/main.css with autoprefixer + version banner
├── favicon.ico           # Default browser tab icon
├── icon.png              # 192×192 PNG icon (Apple Touch Icon, manifest)
├── icon.svg              # Scalable icon for modern browsers
├── img/                  # Drop your images here
│   └── .gitkeep
├── index.html            # The default HTML skeleton
├── js/
│   ├── app.js            # Your entry-point JavaScript (empty)
│   └── vendor/           # Third-party libraries (copied as-is by webpack)
│       └── .gitkeep
├── package.json          # Webpack scripts + devDependencies
├── robots.txt            # Allow-everything by default
├── site.webmanifest      # Web app manifest stub
├── webpack.common.js     # Shared webpack config (entry + output)
├── webpack.config.dev.js # Dev server overrides
├── webpack.config.prod.js# Production build overrides
├── .editorconfig         # Cross-editor formatting rules
├── .gitattributes        # Force LF line endings on text files
└── .gitignore            # node_modules, archive, lockfiles

Total source footprint: about 41 files in the upstream repo, dominated by the icon set, manifests, and webpack scaffolding. The HTML skeleton itself is intentionally minimal — fewer than 25 lines of body markup.

Tip

The template is "delete-key friendly." If you don't need a piece (Open Graph, the manifest, the 404 page, the SVG icon), just delete it AND remove its CopyPlugin entry from webpack.config.prod.js. There are no hidden cross-references that will break.

index.html — the skeleton

index.html is the document you'll edit most. Its purpose is to:

  1. Declare a UTF-8 charset and a responsive viewport.
  2. Link the bundled stylesheet (css/style.css) and the bundled script (js/app.js).
  3. Reference the icon set (favicon .ico, scalable .svg, and Apple Touch icon .png).
  4. Reference the web app manifest.
  5. Carry placeholder Open Graph metadata for social sharing.

The webpack production build runs this file through HtmlWebpackPlugin, which auto-injects the bundled <script> tag (so manual edits to the <script src="js/app.js"> line are not strictly required — webpack rewrites it).

404.html — error page

404.html is a self-contained, inline-styled error page with no external CSS or JS dependencies. It uses a CSS table layout to vertically center the error message and a single @media (max-width: 280px) query to stay legible on narrow screens.

Note

The 404 page intentionally inlines its CSS. Internet Explorer's older "friendly error pages" feature replaced custom 404 responses smaller than 512 bytes; the inlined CSS keeps the page above that threshold and avoids a blank fallback in legacy contexts.

app.js — your entry point

js/app.js ships empty. It is the webpack entry point declared in webpack.common.js:

const path = require('path');

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

Replace its contents with your application code, or wire in a framework — webpack will bundle whatever you import.

js/vendor/ — drop-in libraries

Anything you place under js/vendor/ is copied verbatim into dist/js/vendor/ by the production webpack CopyPlugin. This is the right place for prebuilt third-party scripts you do not want bundled (analytics snippets, polyfills served as-is, jQuery if you still need it).

css/style.css — base styles

The template ships with style.css derived from h5bp/main.css. The base styles do not include a CSS reset framework like Normalize.css — that was intentionally dropped in v9.0.0. Instead, you get a small set of opinionated defaults: typography readability, text-shadow protection during selection, alignment fixes for img, video, fieldset, textarea, and a styled outdated-browser prompt.

Icons

File Purpose When the browser uses it
favicon.ico Tab icon, bookmarks All browsers; auto-discovered if at site root
icon.svg Scalable tab icon Modern browsers that support image/svg+xml favicons
icon.png (192×192) Apple Touch Icon, home screen iOS and Android home-screen install

Replace them with your own. For multi-resolution Apple Touch Icons across older iOS versions, see Section 5.

Webpack — three files, one entry

webpack.common.js       — entry + output (shared)
webpack.config.dev.js   — merges common + dev-server settings
webpack.config.prod.js  — merges common + HtmlWebpackPlugin + CopyPlugin patterns

The two derived configs use webpack-merge to compose. If you need to add a webpack rule used by both modes (e.g., a TypeScript loader), put it in webpack.common.js.

The CopyPlugin patterns in webpack.config.prod.js enumerate every static file that round-trips from your project root to dist/:

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' },
  ],
}),

When you add a new static file, add a CopyPlugin entry for it. Without an entry, the file is not copied into dist/ and your deploy will be missing that asset.

Warning

webpack.config.prod.js is the source of truth for "what files end up in the production build." Forgetting to add a new asset's CopyPlugin pattern is the single most common deploy bug for this template.


5. Common Tasks

Recipe-style walkthroughs of the most common things you'll do once. Each recipe lists the goal, the prerequisites, the exact steps, and how to verify it worked.

Recipe: Start a brand-new project

Goal: A fresh HTML5 Boilerplate project running locally with hot reload.

Prerequisites: Node.js >=20. Optionally Volta for pinned Node management.

Steps:

npx create-html5-boilerplate my-site
cd my-site
npm install
npm run start

Verify: Your default browser opens to http://localhost:8080/ and shows "Hello world! This is HTML5 Boilerplate." Editing index.html triggers an auto-refresh.

If it fails: See the troubleshooting section. Common causes are wrong Node version (EBADENGINE) and port 8080 already in use (EACCES or EADDRINUSE).

Recipe: Customize meta tags for SEO and social sharing

Goal: A page that renders correctly when shared on Facebook, LinkedIn, Slack, Discord, iMessage, and surfaces with the right title in Google.

Prerequisites: Project scaffolded. A 1200×630 PNG saved to img/social-preview.png.

Steps: Edit index.html:

<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Acme Widget Co — Quality widgets since 1923</title>
    <meta name="description" content="Acme Widget Co builds artisan widgets for the discerning factory.">

    <meta property="og:url" content="https://www.acmewidget.example/">
    <meta property="og:title" content="Acme Widget Co — Quality widgets since 1923">
    <meta property="og:type" content="website">
    <meta property="og:description" content="Acme Widget Co builds artisan widgets for the discerning factory.">
    <meta property="og:image" content="https://www.acmewidget.example/img/social-preview.png">
    <meta property="og:image:alt" content="A row of polished brass widgets on a workbench.">
    <meta property="og:site_name" content="Acme Widget Co">

    <meta name="theme-color" content="#bf3f00">

Verify: Use Facebook's Sharing Debugger, LinkedIn's Post Inspector, and the Twitter/X Card Validator. Each platform shows your image, title, and description as configured.

If it fails: Social platforms cache scraped Open Graph metadata for hours. Use the platform's debug tool to force a re-scrape.

Tip

Always provide og:image:alt. It is read by screen readers when the image is announced and is required for accessible link previews. The empty placeholder shipped in index.html exists exactly because you must fill this in.

Recipe: Configure caching headers via the companion server configs

HTML5 Boilerplate intentionally does not ship server configs in the template anymore — the .htaccess file was removed in v9.0.0. The h5bp organization maintains separate, server-specific repos with hardened defaults (security headers, content-type mappings, gzip/brotli, immutable caching for hashed assets):

Goal: Cache hashed bundles aggressively (max-age=31536000, immutable) and HTML conservatively (max-age=0, must-revalidate).

Prerequisites: A static host that respects .htaccess (Apache), nginx.conf, IIS web.config, or your platform's caching directives.

Steps (Apache example): Pull the relevant snippet from h5bp/server-configs-apache:

# .htaccess fragment from h5bp/server-configs-apache
<IfModule mod_headers.c>
  # Hashed JS/CSS — cache forever
  <FilesMatch "\.(js|css)$">
    Header set Cache-Control "max-age=31536000, immutable"
  </FilesMatch>

  # HTML — never cache long
  <FilesMatch "\.html$">
    Header set Cache-Control "max-age=0, must-revalidate"
  </FilesMatch>
</IfModule>

Steps (Nginx example): From h5bp/server-configs-nginx:

location ~* \.(js|css)$ {
  add_header Cache-Control "max-age=31536000, immutable";
}

location ~* \.html$ {
  add_header Cache-Control "max-age=0, must-revalidate";
}

Verify: Use curl -I or Chrome DevTools' Network tab to inspect the Cache-Control header on a deployed asset.

If it fails: Most managed static hosts (Netlify, Vercel, Cloudflare Pages, GitHub Pages) override your Cache-Control headers with their own. Check the platform's caching docs — many of them already use sensible defaults equivalent to the h5bp configs.

Important

Do not copy the entire server-configs-* repos into your project blindly. Pull only the snippets that match your hosting environment. The configs include security headers (HSTS, X-Frame-Options, Content-Security-Policy) that you must understand before enabling.

Recipe: Integrate with a build tool (TypeScript, Sass, frameworks)

The bundled webpack config is intentionally minimal. To add TypeScript, Sass, or a framework, edit webpack.common.js (so both dev and prod inherit the change) and add the relevant loader.

Goal: Add TypeScript compilation.

Prerequisites: Project scaffolded. npm install already run.

Steps:

npm install --save-dev typescript ts-loader

Edit webpack.common.js:

const path = require('path');

module.exports = {
  entry: { app: './js/app.ts' },
  module: {
    rules: [
      { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },
    ],
  },
  resolve: { extensions: ['.tsx', '.ts', '.js'] },
  output: {
    path: path.resolve(__dirname, 'dist'),
    clean: true,
    filename: './js/app.js',
  },
};

Rename js/app.js to js/app.ts. Add a tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "sourceMap": true
  },
  "include": ["js/**/*"]
}

Verify: npm run start. The dev server compiles app.ts and serves the resulting JavaScript.

If it fails: Confirm ts-loader is in devDependencies, not dependencies. Check that tsconfig.json is in the project root (where webpack runs). Issue #3229 tracks adding official TypeScript docs to the upstream project.

Recipe: Deploy to GitHub Pages

Goal: A site at https://your-username.github.io/your-repo/ updated on every push to main.

Prerequisites: A GitHub repo containing your scaffolded project.

Steps:

  1. In your repo root, create .github/workflows/deploy.yml:
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

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
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4
  1. In your repo's GitHub settings, set Pages → Source to GitHub Actions.
  2. Push to main.

Verify: GitHub Actions workflow run shows the deploy step in green. Your site is live at the URL printed in the workflow summary.

If it fails: Confirm your repo's Pages settings use GitHub Actions as the source, not "Deploy from a branch." If asset paths break (404s on js/app.js), you may need to set webpack's output.publicPath to /your-repo/ to match the repo path GitHub Pages serves under.

Recipe: Deploy to Netlify

Goal: A site auto-deploying on every push.

Steps:

  1. Push your project to GitHub.
  2. In Netlify, click Add new site → Import an existing project and select the repo.
  3. Set:
    • Build command: npm run build
    • Publish directory: dist
  4. Click Deploy.

Verify: Netlify reports a successful build. Your site is at the auto-generated *.netlify.app URL.

If it fails: Netlify's default Node version may be older than 20. In your repo, add a .nvmrc file containing 20 or set NODE_VERSION = "20" in netlify.toml.

Recipe: Deploy to Vercel

Goal: A site auto-deploying on every push.

Steps:

  1. Push your project to GitHub.
  2. In Vercel, click Add New → Project and select the repo.
  3. Vercel auto-detects "Other" — set:
    • Build Command: npm run build
    • Output Directory: dist
  4. Click Deploy.

Verify: Vercel reports a successful build. Your site is live.

If it fails: Vercel sometimes assumes public/ as the output directory. Override it explicitly to dist. Set engines.node in package.json to 20.x so Vercel uses Node 20.

Recipe: Run linters (upstream contributor flow)

Goal: Lint your JavaScript and format Markdown / YAML / JSON before committing.

Prerequisites: Only relevant if you cloned the upstream repo to contribute back. Downstream-site users do not need this.

Steps:

npm run prettier
> prettier --write ./**/*.{js,json,md,mjs,yml}

This rewrites every file in place using the rules from .prettierrc.json (single quotes, brackets on the same line, double quotes in YAML).

To run ESLint via gulp:

gulp lint:js

The flat config in eslint.config.mjs enforces 2-space indent, single quotes, and required semicolons across src/js/*.js, src/*.js, and test/*.mjs.

Verify: No errors reported. The exit code is 0.

If it fails: Most lint failures are easy fixes. The strictest rules are around indentation and quote style — both are auto-fixed by npm run prettier.

Recipe: Build for production

Goal: A clean, minified dist/ folder ready to upload to any static host.

Prerequisites: Project ready. All assets in place. index.html filled in.

Steps:

npm run build

Confirm dist/ contains exactly:

dist/
├── 404.html
├── css/style.css
├── favicon.ico
├── icon.png
├── icon.svg
├── img/
├── index.html
├── js/app.js
├── js/vendor/   (if you put files in src/js/vendor/)
├── package.json
├── robots.txt
├── site.webmanifest
├── webpack.common.js
├── webpack.config.dev.js
├── webpack.config.prod.js
├── .editorconfig
├── .gitattributes
└── .gitignore

Deploy: Upload the contents of dist/ (not the dist/ folder itself) to your web host's document root.

Verify: curl -I https://your-site.example/ returns 200 OK. curl https://your-site.example/site.webmanifest | head shows your manifest. curl https://your-site.example/robots.txt shows your crawler config.

Tip

If you want to include the package.json, dotfiles, and webpack configs in your repo but not in your published site, add a post-build cleanup step or add them to a .gitignore-style deploy ignore list. Most hosts (Netlify, Vercel, GitHub Pages) ignore those files automatically when serving dist/.


6. Troubleshooting

Error: npm run start fails with EACCES: permission denied or EADDRINUSE

Cause: webpack-dev-server can't bind to port 8080 because another process is using it (or your firewall is blocking it).

Solution: Add a port to the devServer block in webpack.config.dev.js:

devServer: {
  liveReload: true,
  hot: true,
  open: true,
  static: ['./'],
  port: 9000,
}

Prevention: Pick a port your team agrees on; avoid privileged ports (<1024) on Unix-like systems.

Error: Module not found: Error: Can't resolve './index.html' in production build

Cause: webpack.config.prod.js uses HtmlWebpackPlugin({ template: './index.html' }). If you renamed or moved index.html, the plugin can't find it.

Solution: Update the template path to match your file's actual location:

new HtmlWebpackPlugin({ template: './src/index.html' })

Prevention: If you rename index.html, search the project for the old name and update every reference.

Error: Browser tab shows generic favicon instead of your icon

Cause: Browsers cache favicons aggressively. Your replacement isn't being fetched.

Solution: Append a query parameter to the favicon link to bust the cache during development:

<link rel="icon" href="/favicon.ico?v=2" sizes="any">

In production, change the filename (e.g., favicon-v2.ico) and update the link.

Prevention: Hard-refresh (Ctrl+F5 / Cmd+Shift+R) when previewing icon changes; browsers won't pick up a new favicon on a soft reload.

Error: theme-color is ignored on iOS

Cause: iOS Safari only honors theme-color for sites added to the home screen, and only in iOS 15 and later. In a regular Safari tab on older iOS, the meta tag is ignored entirely.

Solution: This is browser behavior, not a bug. Test on Chrome on Android or iOS 15+ for visible effect. There is no fallback that works in older Safari.

Prevention: Don't rely on theme-color being globally supported — design your branding to work without it.

Error: og: previews don't update on Facebook/LinkedIn after editing index.html

Cause: Social platforms cache scraped Open Graph metadata for hours or days.

Solution: Use the platform's debug tool to force a re-scrape:

Prevention: Get your og:* tags right before sharing the URL anywhere. The first scrape is sticky.

Error: Web app manifest not picked up — no install prompt appears

Cause: PWA install criteria are strict. Common gaps: site not served over HTTPS (or localhost), manifest's start_url is unreachable, missing 192×192 and 512×512 icons, no service worker registered.

Solution: Open Chrome DevTools → Application → Manifest. It lists exactly which criteria are unmet. Add the missing pieces (HTML5 Boilerplate intentionally ships only a 192×192 icon — add 512×512 for installability).

Prevention: Test PWA install on localhost (HTTPS is auto-relaxed there) before deploying.

Error: Live reload doesn't trigger on index.html edits

Cause: The dev server's static setting is ['./'], which serves files from the project root. If you moved index.html into a subfolder without updating static, the watcher misses it.

Solution: Update webpack.config.dev.js:

devServer: {
  static: ['./src'],
  // ...
}

Prevention: If you reorganize the project layout, update static and the HtmlWebpackPlugin({ template }) path together.

Error: npm install fails with EBADENGINE on Node 18 or older

Cause: The template's package.json specifies "engines": { "node": ">=20" }.

Solution: Upgrade Node:

nvm install 20
nvm use 20

The template's volta field pins Node 20.19.1 if you use Volta.

Prevention: Use a Node version manager (nvm, Volta, asdf) and check node --version matches the template's requirement before running install.

Error: GitHub auto-publishes a Twitter Card preview that looks broken

Cause: Twitter has deprecated Twitter Cards and now falls back to Open Graph metadata. Old <meta name="twitter:*"> tags can conflict with newer og: tags.

Solution: Remove the twitter:* tags entirely and rely on og:title, og:description, og:image, og:image:alt. Twitter/X uses Open Graph as its source of truth now.

Prevention: Don't add twitter:* tags to new projects. Open Graph is the present and future of social previews.

Error: Modernizr is missing — feature detection no longer works

Cause: Modernizr was removed in v9.0.0. The template no longer ships any feature-detection library.

Solution: Either drop the Modernizr dependency entirely (most modern browsers support enough of HTML5/CSS3 that detection is unnecessary), or npm install modernizr and configure your own custom build. The template won't add it back.

Prevention: Use @supports CSS at-rules for CSS feature detection — they cover most modern needs without a runtime library.

Error: GitHub Pages 404s on js/app.js

Cause: GitHub Pages serves your repo under https://USERNAME.github.io/REPO/. The webpack-emitted <script src="js/app.js"> resolves relative to the document, which is fine — but if you have an absolute-path resource like <link rel="manifest" href="/site.webmanifest">, the leading / points at https://USERNAME.github.io/, not your repo's subpath.

Solution: Either deploy under a custom domain (which lives at the root) or change all leading-slash paths to relative paths in index.html:

<link rel="manifest" href="site.webmanifest">
<link rel="icon" href="favicon.ico" sizes="any">

Prevention: Use relative paths exclusively if you intend to host under a subpath.

Error: Test suite fails with mismatch on style.css banner

Cause: The upstream test/file_content.mjs test asserts that dist/css/style.css contains an exact-byte banner. Line ending conversion to CRLF on Windows breaks the byte match. (This affects upstream contributors, not downstream users.)

Solution: Make sure .gitattributes is in place and your local repo has been re-checked-out with git rm --cached -r . && git reset --hard HEAD to apply LF normalization. (Tests on Windows were explicitly fixed in v9.0.1.)

Prevention: Configure git's core.autocrlf to input on Windows, or rely on .gitattributes (which the template already provides).


7. FAQ

Q: Do I need to upgrade my site each time a new version of HTML5 Boilerplate is released?

No. Just as you don't normally replace the foundation of a house once it's built. There's nothing stopping you from working in the latest changes, but assess the cost/benefit first. Most version bumps are tooling and metadata updates that don't change the on-page output.

Q: Where can I get help with support questions?

Ask on Stack Overflow with the html5boilerplate tag. The h5bp org also enables GitHub Discussions, but Stack Overflow is the more active venue. Bugs and feature requests go to GitHub Issues.

Q: Can I use HTML5 Boilerplate with React, Vue, Svelte, or another framework?

Yes. The template doesn't impose any framework — replace app.js with your framework's entry point and adjust webpack as needed. Most teams using a modern framework will eventually outgrow the bundled webpack config and adopt the framework's CLI (vite, next, nuxt, svelte-kit, etc.) instead — that's fine. Use the boilerplate's index.html, icons, manifest, and metadata patterns as a starting point even when you swap out the build system.

Q: Why was Modernizr removed in v9.0.0?

Modern browsers cover enough of HTML5/CSS3 that feature detection has become a niche need. Carrying Modernizr by default added bytes most sites didn't use. Add it back yourself if you specifically need it.

Q: Why was Normalize.css dropped?

The maintainers moved CSS into a separate main.css repo and decided that Normalize's opinionated cross-browser leveling was beyond the scope of a "starting line." main.css includes a smaller set of base styles instead.

Q: Why does the project not ship .htaccess anymore?

Server config was removed from the core template in v9.0.0. The h5bp organization maintains separate, server-specific repos with hardened defaults (security headers, content-type mappings, gzip/brotli, immutable caching for hashed assets). See Section 5's recipe on caching headers for the full list of companion repos.

Q: Why is there no JavaScript framework, no router, no state management?

By design. HTML5 Boilerplate is a template, not an application scaffold. It targets the lowest common denominator (a static HTML site or a single-page app you wire up yourself). Tools like create-react-app, vite, and the framework-specific CLIs cover the "give me a working app skeleton" niche.

Q: Should I commit dist/ to source control?

No. dist/ is build output — it's regenerated by npm run build. The default .gitignore already excludes dist/package-lock.json. Add dist/ itself if your project doesn't ship it.

Q: Where do I put third-party libraries that I don't want webpack to bundle?

js/vendor/. The production webpack config copies that folder verbatim into dist/js/vendor/. Reference scripts in your HTML with <script src="js/vendor/library.js"></script>.

Q: Can I delete 404.html if my hosting provider already has a 404 page?

Yes. Delete the file and remove its entry from the webpack.config.prod.js CopyPlugin patterns. Same applies to any of the other static files — the build config is the only place that needs to know.

Q: Can I delete the .editorconfig file?

You can, but you probably shouldn't. It's free and silently improves cross-editor consistency for your team. The only "cost" is one extra dotfile.

Q: How do I add TypeScript?

The template ships pure JavaScript. Install typescript and ts-loader, add a tsconfig.json, and update webpack.common.js to register the .ts loader rule. Section 5 has the full recipe. (#3229 tracks adding official upstream TypeScript docs.)

Q: How is HTML5 Boilerplate different from create-react-app or vite?

HTML5 Boilerplate create-react-app / vite
Output Plain HTML/CSS/JS skeleton Framework-specific app scaffold
Build webpack (configurable) Locked to the framework's tool
Framework None React / Vue / Svelte / etc.
Best for Static sites, marketing pages, framework-agnostic apps Single-page apps in a specific framework

Q: Does HTML5 Boilerplate include analytics?

No. Google Analytics was removed in v9.0.0. Add your analytics snippet to index.html yourself, or use a privacy-respecting alternative like Plausible or Fathom.

Q: Can I use jQuery?

Yes, just install it. jQuery was bundled in v6 and v7 but removed in v8.0.0. Drop it into js/vendor/ if you want it served verbatim, or npm install jquery and import $ from 'jquery' from app.js if you want webpack to bundle it.

Q: What's the license?

MIT. Use it commercially, modify it, ship it without attribution if you want — all permitted.

Q: Where do I report a bug or contribute?

GitHub issues for bugs and feature requests. Pull requests are reviewed by project maintainers; the upstream repo's .github/CONTRIBUTING.md has the workflow details. Open work items as of this guide include #3411 (modernize npm publishing & auth), #3412 (template-push fix), and #3373 (remove Twitter Cards docs).

Q: How do I disable iOS phone-number auto-detection?

Add to index.html head:

<meta name="format-detection" content="telephone=no">

This stops Safari on iOS from auto-linking sequences of digits as tel: links.

Q: How do I declare a security.txt channel?

Serve .well-known/security.txt:

Contact: mailto:[email removed]
Expires: 2027-01-01T00:00:00Z
Preferred-Languages: en
Canonical: https://www.example.com/.well-known/security.txt

See securitytxt.org for the full specification.

Q: How do I add an RSS or Atom feed link to my page?

<link rel="alternate" type="application/rss+xml" title="My Site RSS" href="/rss.xml">
<link rel="alternate" type="application/atom+xml" title="My Site Atom" href="/atom.xml">

Q: How do I tell browsers about my OpenSearch endpoint?

<link rel="search" title="Search Example" type="application/opensearchdescription+xml" href="/opensearch.xml">

Browsers detect the descriptor and offer to add your search engine.

Q: What does @media (scripting: enabled) do, and is it supported?

It's a modern alternative to <noscript> for hiding/showing content based on JavaScript availability. Supported in current Chrome, Edge, Safari, and Firefox. Useful for avoiding flash of unstyled content (FOUC) on JS-dependent UI.

@media (scripting: enabled) {
  .js-only { display: block; }
}
@media (scripting: none) {
  .js-only { display: none; }
}

Q: Where is the official documentation?

The upstream repo's docs/ folder (TOC, usage, html, css, js, misc, faq, extend, about-this-repo). The companion site at html5boilerplate.com is also separately maintained.