# HTML5 Boilerplate User Guide

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.

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

```text
> 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`](https://www.npmjs.com/package/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](https://volta.sh/). |
| Operating system | Any | macOS, Linux, Windows (CRLF normalization handled by `.gitattributes`). |
| Browser (for previewing) | Latest stable | The template targets [Browserslist `default`](https://browsersl.ist/#q=defaults). |

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.

    ```bash
    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](https://github.com/h5bp/html5-boilerplate-template) 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:

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

    ```bash
    npm install html5-boilerplate
    ```

    ```bash
    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](https://github.com/h5bp/html5-boilerplate/releases). The zip is `html5-boilerplate_v9.0.1.zip` (or the current version) and contains the same files as `dist/`.

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

    ```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.

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

```bash
npm run start
```

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

```bash
npm run build
```

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

```html
<!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`

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

```text
# 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

```bash
npm run start
```

```text
> 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.

```bash
npm run build
```

```text
> 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`.)

```text
.
├── 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`:

```javascript
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`](https://github.com/h5bp/main.css). The base styles do **not** include a CSS reset framework like Normalize.css — that was [intentionally dropped in v9.0.0](https://github.com/h5bp/html5-boilerplate/pull/2960). 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

```text
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`](https://www.npmjs.com/package/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/`:

```javascript
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](https://volta.sh/) for pinned Node management.

**Steps:**

```bash
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
<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](https://developers.facebook.com/tools/debug/), LinkedIn's [Post Inspector](https://www.linkedin.com/post-inspector/), and the [Twitter/X Card Validator](https://cards-dev.twitter.com/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):

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

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

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

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

```bash
npm install --save-dev typescript ts-loader
```

Edit `webpack.common.js`:

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

```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](https://github.com/h5bp/html5-boilerplate/issues/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`:

```yaml
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
```

2. In your repo's GitHub settings, set **Pages → Source** to **GitHub Actions**.
3. 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:**

```bash
npm run prettier
```

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

```bash
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:**

```bash
npm run build
```

Confirm `dist/` contains exactly:

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

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

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

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

- Facebook: [Sharing Debugger](https://developers.facebook.com/tools/debug/)
- LinkedIn: [Post Inspector](https://www.linkedin.com/post-inspector/)
- Twitter/X: [Card Validator](https://cards-dev.twitter.com/validator)

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

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

```bash
nvm install 20
nvm use 20
```

The template's `volta` field pins Node 20.19.1 if you use [Volta](https://volta.sh/).

**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](https://github.com/h5bp/html5-boilerplate/issues/3373) 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](https://developer.mozilla.org/en-US/docs/Web/CSS/@supports) 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`:

```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](https://github.com/h5bp/html5-boilerplate/pull/3110).)

**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](https://stackoverflow.com/questions/tagged/html5boilerplate). The h5bp org also enables [GitHub Discussions](https://github.com/h5bp/html5-boilerplate/discussions), but Stack Overflow is the more active venue. Bugs and feature requests go to [GitHub Issues](https://github.com/h5bp/html5-boilerplate/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](https://github.com/h5bp/main.css) 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](https://github.com/h5bp/html5-boilerplate/issues/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](https://plausible.io/) or [Fathom](https://usefathom.com/).

**Q: Can I use jQuery?**

Yes, just install it. jQuery was bundled in v6 and v7 but [removed in v8.0.0](https://github.com/h5bp/html5-boilerplate/pull/2225). 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](https://github.com/h5bp/html5-boilerplate/blob/main/LICENSE.txt). 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](https://github.com/h5bp/html5-boilerplate/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](https://github.com/h5bp/html5-boilerplate/issues/3411) (modernize npm publishing & auth), [#3412](https://github.com/h5bp/html5-boilerplate/issues/3412) (template-push fix), and [#3373](https://github.com/h5bp/html5-boilerplate/issues/3373) (remove Twitter Cards docs).

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

Add to `index.html` head:

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

```text
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](https://securitytxt.org/) for the full specification.

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

```html
<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?**

```html
<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.

```css
@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](https://html5boilerplate.com/) is also separately maintained.

---

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