vite-plugin-html-pages 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,133 +5,98 @@
5
5
  [![license](https://img.shields.io/npm/l/vite-plugin-html-pages.svg)](LICENSE)
6
6
  [![vite](https://img.shields.io/badge/vite-plugin-646CFF?logo=vite&logoColor=white)](https://vitejs.dev)
7
7
 
8
- Minimal **static site generation for Vite** using JavaScript files that return HTML.
8
+ **Static site generation for Vite no framework, no components, no magic.**
9
9
 
10
- Generate static HTML pages from `*.ht.js` modules using Vite and
11
- [`javascript-to-html`](https://www.npmjs.com/package/javascript-to-html).
10
+ Write JavaScript (or TypeScript, or JSX) functions that return HTML.
11
+ Get a complete static site with file-based routing, dynamic pages, data
12
+ loading, an asset pipeline, sitemap, RSS, and a live dev server.
12
13
 
13
14
  ⭐ If this project helps you, please consider starring it.
14
15
 
15
- # Built for the Vite ecosystem
16
-
17
- Works seamlessly with:
18
-
19
- - ⚡ **Vite**
20
- - 📦 **Rollup / Rolldown**
21
- - 🧩 **javascript-to-html**
22
-
23
- A minimal **static site generator for Vite** that keeps pages as simple
24
- JavaScript functions returning HTML.
25
-
26
16
  ---
27
17
 
28
- # TL;DR
18
+ ## TL;DR
29
19
 
30
- Write:
20
+ Write a function that returns HTML:
31
21
 
32
22
  ```js
33
23
  // src/index.ht.js
34
24
 
35
- import { fragment, html, body, head, title, h1 } from 'javascript-to-html'
36
-
37
- export default () => fragment(
38
- '<!doctype html>',
39
- html({lang: "en"},
40
- head(
41
- title("My website")
42
- ),
43
- body(
44
- h1('Hello world')
45
- )
46
- )
47
- )
25
+ export default () => `
26
+ <html lang="en">
27
+ <head>
28
+ <title>My website</title>
29
+ <link rel="stylesheet" href="/styles.css">
30
+ </head>
31
+ <body>
32
+ <h1>Hello world</h1>
33
+ </body>
34
+ </html>
35
+ `
48
36
  ```
49
37
 
50
- Run:
38
+ Run:
51
39
 
52
- ``` bash
40
+ ```bash
53
41
  vite build
54
42
  ```
55
43
 
56
44
  Get:
57
45
 
58
- ```html
59
- <!-- dist/index.html -->
60
-
61
- <!doctype html>
62
- <html lang="en">
63
- <head>
64
- <title>My website</title>
65
- </head>
66
- <body>
67
- <h1>Hello world</h1>
68
- </body>
69
- </html>
46
+ ```
47
+ dist/
48
+ index.html ← rendered, with <!DOCTYPE html> added for you
49
+ styles.css ← bundled + minified, because your page referenced it
50
+ 404.html generated automatically
70
51
  ```
71
52
 
72
- ---
73
-
74
- # Features
75
-
76
- - File-based routing
77
- - Dynamic routes `[slug]`
78
- - Multiple parameters `[year]/[slug]`
79
- - Catch-all routes `[...slug]`
80
- - Optional catch-all routes `[...slug]?`
81
- - Route groups `(admin)/users.ht.js`
82
- - Index routes `blog/[slug]/index.ht.js`
83
- - Static params generation
84
- - Fetch caching for data loading
85
- - Dev server SSR rendering
86
- - Parallel static generation
87
- - Automatic `404.html`
88
- - Automatic `sitemap.xml`
89
- - Optional RSS feed generation
90
- - Debug logging
53
+ That's the whole mental model. Everything else is convenience on top.
91
54
 
92
55
  ---
93
56
 
94
- # Installation
57
+ ## Features
95
58
 
96
- ```bash
97
- npm install vite-plugin-html-pages javascript-to-html
98
- ```
59
+ - **File-based routing** — `src/about.ht.js` → `/about`
60
+ - **Dynamic routes** — `[slug]`, `[year]/[slug]`, catch-all `[...path]`, optional catch-all `[...path]?`
61
+ - **Route groups** — `(admin)/users.ht.js` → `/users`
62
+ - **Bring your own HTML** — template literals, [javascript-to-html](https://www.npmjs.com/package/javascript-to-html), or JSX/TSX
63
+ - **Data loading** — `data()` runs at build time, with built-in fetch caching
64
+ - **Typed pages** — per-route param types inferred from the filename
65
+ - **Smart asset pipeline** — JS/TS/CSS referenced by your HTML is bundled and minified; server-only code never leaks into `dist`
66
+ - **Asset validation** — broken `<script src>` / stylesheet links fail the build
67
+ - **Real dev server** — pages render on request with Vite HMR-style full reload and readable error frames
68
+ - **Parallel static generation** — renders large sites concurrently
69
+ - **`404.html`, `sitemap.xml`, RSS** — generated for you
99
70
 
100
71
  ---
101
72
 
102
- # Why this exists
73
+ ## Why this exists
103
74
 
104
- Modern static site tools are powerful, but they often introduce:
105
-
106
- - frameworks
107
- - component systems
108
- - complex build pipelines
109
- - opinionated conventions
75
+ Modern static site tools are powerful, but they bring frameworks,
76
+ component systems, hydration strategies, and opinionated conventions.
110
77
 
111
78
  Sometimes you just want to:
112
79
 
113
- - write HTML
114
- - organize pages in folders
115
- - generate static files
116
-
117
- `vite-plugin-html-pages` exists for that use case.
80
+ - write HTML
81
+ - organize pages in folders
82
+ - run `vite build`
118
83
 
119
- It gives you:
120
-
121
- - file‑based routing
122
- - dynamic pages
123
- - static generation
124
- - dev server rendering
125
-
126
- while keeping pages as **simple JavaScript functions that return HTML**.
84
+ `vite-plugin-html-pages` exists for exactly that. Pages are plain
85
+ functions that return a string of HTML. No runtime ships to the
86
+ browser unless *you* add a script.
127
87
 
128
88
  ---
129
89
 
130
- # Quick Start
90
+ ## Installation
91
+
92
+ ```bash
93
+ npm install -D vite-plugin-html-pages
94
+ ```
131
95
 
132
- ### vite.config.js
96
+ Requires **Node 18+** and **Vite 8+**.
133
97
 
134
98
  ```js
99
+ // vite.config.js
135
100
  import { defineConfig } from "vite"
136
101
  import htmlPages from "vite-plugin-html-pages"
137
102
 
@@ -140,230 +105,205 @@ export default defineConfig({
140
105
  })
141
106
  ```
142
107
 
143
- ---
144
-
145
- # Example Project Structure
146
-
108
+ ```bash
109
+ vite # dev server with live rendering
110
+ vite build # static site in dist/
147
111
  ```
148
- src/
149
-
150
- index.ht.js
151
- about.ht.js
152
112
 
153
- blog/
154
- index.ht.js
155
- [slug].ht.js
156
- [year]/[slug].ht.js
113
+ Add the generated helper types to your `.gitignore`:
157
114
 
158
- docs/
159
- [...slug]?.ht.js
160
-
161
- (admin)/
162
- users.ht.js
115
+ ```
116
+ .vite-plugin-html-pages/
163
117
  ```
164
118
 
165
119
  ---
166
120
 
167
- # Routing
168
-
169
- Routes are generated directly from the filesystem.
170
-
171
-
172
- | Feature | File | URL |
173
- |-----|-----|-----|
174
- | static routes | `index.ht.js` | `/` |
175
- | dynamic routes | `blog/[slug].ht.js` | `/blog/my-post` |
176
- | multiple params | `blog/[year]/[slug].ht.js` | `/blog/2026/my-post` |
177
- | catch-all | `docs/[...slug].ht.js` | `/docs/api/auth/login` |
178
- | optional catch-all | `docs/[...slug]?.ht.js` | `/docs` or `/docs/getting-started` |
179
- | index routes | `products/[product]/index.ht.js` | `/products/iphone-18` |
180
- | route groups | `(admin)/users.ht.js` | `/users` |
181
-
182
- ---
183
-
184
- # Dynamic Routes
121
+ ## Project structure
185
122
 
186
123
  ```
187
- src/blog/[slug].ht.js
188
- ```
124
+ src/
125
+ index.ht.js → /
126
+ about.ht.js → /about
127
+ styles.css → bundled if referenced
128
+ main.js → bundled if referenced
129
+ lib/
130
+ api.js → build-time only (never emitted unless referenced)
189
131
 
190
- Matches:
132
+ blog/
133
+ index.ht.js → /blog
134
+ [slug].ht.js → /blog/:slug
191
135
 
192
- ```
193
- /blog/hello-world
194
- /blog/my-first-post
195
- ```
136
+ docs/
137
+ [...path]?.ht.js → /docs, /docs/a, /docs/a/b, ...
196
138
 
197
- Example:
139
+ (admin)/
140
+ users.ht.js → /users
198
141
 
199
- ``` js
200
- import { fragment, html, body, h1 } from 'javascript-to-html'
142
+ 404.ht.js → dist/404.html
143
+ ```
201
144
 
202
- export function generateStaticParams() {
203
- return [
204
- { slug: 'hello-world' },
205
- { slug: 'my-first-post' }
206
- ]
207
- }
145
+ Any file ending in a page extension is a page. Everything else in
146
+ `src/` is treated as an asset (see [Assets](#assets--styling)).
208
147
 
209
- export default ({ params }) => fragment(
210
- '<!doctype html>',
211
- html(
212
- body(
213
- h1(params.slug)
214
- )
215
- )
216
- )
217
- ```
148
+ Default page extensions: `.ht.js`, `.html.js`, `.ht.ts`, `.html.ts`,
149
+ `.ht.jsx`, `.html.jsx`, `.ht.tsx`, `.html.tsx`.
218
150
 
219
151
  ---
220
152
 
221
- # Multiple Parameters
153
+ ## Writing pages
222
154
 
223
- ```
224
- src/blog/[year]/[slug].ht.js
225
- ```
155
+ A page module's **default export** can be any of the following.
226
156
 
227
- Matches:
157
+ ### 1. A function returning an HTML string
228
158
 
229
- ```
230
- /blog/2026/vite-routing
231
- /blog/2025/my-first-post
159
+ ```js
160
+ export default ({ params, data, dev }) => `
161
+ <html>
162
+ <body><h1>Hello</h1></body>
163
+ </html>
164
+ `
232
165
  ```
233
166
 
234
- Example:
235
-
236
- ``` js
237
- import { fragment, html, body, h1 } from 'javascript-to-html'
238
-
239
- export function generateStaticParams() {
240
- return [
241
- {
242
- year: 2026,
243
- slug: "vite-routing"
244
- },
245
- {
246
- year: 2025,
247
- slug: 'my-first-post'
248
- }
249
- ]
250
- }
167
+ ### 2. A plain string
251
168
 
252
- export default ({ params }) => fragment(
253
- '<!doctype html>',
254
- html(
255
- body(
256
- h1(params.slug)
257
- )
258
- )
259
- )
169
+ ```js
170
+ export default `<html><body><h1>Static as it gets</h1></body></html>`
260
171
  ```
261
172
 
262
- ---
173
+ ### 3. A structured module
263
174
 
264
- # Catch-All Routes
175
+ Keeps `render`, `data`, and `generateStaticParams` together in one object:
265
176
 
266
- ```
267
- src/docs/[...slug].ht.js
177
+ ```js
178
+ export default {
179
+ generateStaticParams: () => [{ slug: "hello" }],
180
+ data: ({ params }) => ({ title: params.slug }),
181
+ render: ({ data }) => `<html><body><h1>${data.title}</h1></body></html>`,
182
+ }
268
183
  ```
269
184
 
270
- Matches:
185
+ ### 4. JSX / TSX
271
186
 
272
- ```
273
- /docs/api/auth/login
274
- /docs/guides/rendering/static
275
- ```
276
-
277
- Params:
187
+ Name the file `*.ht.jsx` or `*.ht.tsx` and return JSX — it is rendered
188
+ to **static HTML** at build time with `react-dom/server`:
278
189
 
279
- ```
280
- params.slug === "api/auth/login"
190
+ ```tsx
191
+ // src/index.ht.tsx
192
+ export default function Home() {
193
+ return (
194
+ <html lang="en">
195
+ <head><title>My site</title></head>
196
+ <body><h1>Hello from TSX</h1></body>
197
+ </html>
198
+ )
199
+ }
281
200
  ```
282
201
 
283
- ---
202
+ JSX pages require `react` and `react-dom` in your project (they are
203
+ optional peer dependencies — string-based pages don't need them).
204
+ Since output is static, event-handler props like `onClick` won't do
205
+ anything in the browser; the dev server warns you if it finds any.
284
206
 
285
- # Optional Catch-All Routes
207
+ ### 5. javascript-to-html
286
208
 
287
- ```
288
- src/docs/[...slug]?.ht.js
289
- ```
209
+ Prefer composable functions over template strings? The companion
210
+ library [javascript-to-html](https://www.npmjs.com/package/javascript-to-html)
211
+ works great:
290
212
 
291
- Matches both:
213
+ ```js
214
+ import { html, head, title, body, h1 } from 'javascript-to-html'
292
215
 
216
+ export default () =>
217
+ html({ lang: 'en' },
218
+ head(title('My website')),
219
+ body(h1('Hello world'))
220
+ )
293
221
  ```
294
- /docs
295
- /docs/getting-started
296
- /docs/api/auth/login
297
- ```
298
-
299
- Params:
300
-
301
- | URL | params.slug |
302
- |-----|-------------|
303
- | `/docs` | "" |
304
- | `/docs/api` | "api" |
305
- | `/docs/api/auth` | "api/auth" |
306
-
307
- ---
308
-
309
- # Route Groups
310
222
 
311
- Folders wrapped in parentheses are ignored in URLs.
223
+ > If a page's output starts with `<html>`, `<!DOCTYPE html>` is
224
+ > prepended automatically.
312
225
 
313
- ```
314
- src/(admin)/users.ht.js
315
- ```
226
+ ### Render context
316
227
 
317
- URL:
228
+ Every page function receives one argument:
318
229
 
319
- ```
320
- /users
321
- ```
230
+ | Property | Type | Description |
231
+ |----------|------|-------------|
232
+ | `params` | `Record<string, string \| string[]>` | Route params for this page |
233
+ | `data` | `unknown` | Whatever your `data()` returned |
234
+ | `page` | `object` | Route metadata (`routePath`, `relativePath`, ...) |
235
+ | `dev` | `boolean` | `true` in the dev server, `false` at build |
322
236
 
323
237
  ---
324
238
 
325
- # Index Routes
239
+ ## Routing
326
240
 
327
- Files named `index.ht.js` map to the parent route.
241
+ Routes come straight from the filesystem:
328
242
 
329
- ```
330
- src/blog/index.ht.js -> /blog
331
- src/blog/[slug]/index.ht.js -> /blog/my-post
332
- ```
243
+ | Feature | File | URL |
244
+ |---------|------|-----|
245
+ | Static routes | `index.ht.js` | `/` |
246
+ | Nested routes | `blog/index.ht.js` | `/blog` |
247
+ | Dynamic routes | `blog/[slug].ht.js` | `/blog/my-post` |
248
+ | Multiple params | `blog/[year]/[slug].ht.js` | `/blog/2026/my-post` |
249
+ | Catch-all | `docs/[...path].ht.js` | `/docs/api/auth/login` |
250
+ | Optional catch-all | `docs/[...path]?.ht.js` | `/docs` and `/docs/anything/below` |
251
+ | Index routes | `products/[id]/index.ht.js` | `/products/iphone-18` |
252
+ | Route groups | `(admin)/users.ht.js` | `/users` |
333
253
 
334
- ---
254
+ More specific routes always win: static segments beat dynamic ones,
255
+ dynamic beat catch-alls. Two files generating the same URL is a build
256
+ error, not a silent overwrite.
335
257
 
336
- # Static Params
258
+ ### Static params
337
259
 
338
- Dynamic routes can export `generateStaticParams`.
260
+ Dynamic routes declare their pages by exporting `generateStaticParams`:
339
261
 
340
262
  ```js
263
+ // src/blog/[slug].ht.js
341
264
  export function generateStaticParams() {
342
265
  return [
343
- { slug: "hello-world" },
344
- { slug: "vite-routing" }
266
+ { slug: 'hello-world' },
267
+ { slug: 'my-first-post' },
345
268
  ]
346
269
  }
270
+
271
+ export default ({ params }) => `
272
+ <html><body><h1>${params.slug}</h1></body></html>
273
+ `
347
274
  ```
348
275
 
276
+ Values can be strings, numbers, or booleans — they are stringified and
277
+ URL-encoded for you. Catch-all params accept arrays (`{ path: ['a', 'b'] }`)
278
+ or slash-separated strings (`{ path: 'a/b' }`).
279
+
280
+ A dynamic page that generates zero routes prints a warning so it can't
281
+ silently vanish from your site.
282
+
349
283
  ---
350
284
 
351
- # Data Loading
285
+ ## Data loading
352
286
 
353
- Pages can export a `data()` function.
287
+ Export a `data()` function and its result appears as `ctx.data` in your
288
+ render function. It runs at build time (and per-request in dev):
354
289
 
355
290
  ```js
356
- export async function data({ params }) {
357
- return { title: params.slug }
291
+ export async function data({ params, dev }) {
292
+ const res = await fetch(`https://api.example.com/posts/${params.slug}`)
293
+ return await res.json()
358
294
  }
359
- ```
360
295
 
361
- ---
296
+ export default ({ data }) => `
297
+ <html><body>
298
+ <h1>${data.title}</h1>
299
+ ${data.body}
300
+ </body></html>
301
+ `
302
+ ```
362
303
 
363
- # Caching
304
+ ### fetchWithCache
364
305
 
365
- Use `fetchWithCache` for HTTP requests during static generation. Responses are
366
- cached to avoid repeated network calls across page builds.
306
+ Building 500 pages against the same API? Cache the responses:
367
307
 
368
308
  ```js
369
309
  import { fetchWithCache } from 'vite-plugin-html-pages'
@@ -371,162 +311,230 @@ import { fetchWithCache } from 'vite-plugin-html-pages'
371
311
  export async function data({ params }) {
372
312
  const res = await fetchWithCache(
373
313
  `https://api.example.com/posts/${params.slug}`,
374
- {
375
- // fetch API options
376
- },
314
+ { /* standard fetch options */ },
377
315
  { maxAge: 3600 }
378
316
  )
379
- const post = await res.json()
380
- return { post }
317
+ return { post: await res.json() }
381
318
  }
382
319
  ```
383
320
 
384
- ## Options
385
-
386
321
  | Option | Description |
387
322
  |--------|-------------|
388
- | `maxAge` | Cache TTL in seconds (default: 3600) |
389
- | `cacheKey` | Custom cache key (default: hash of URL + method + headers) |
390
- | `forceRefresh` | Bypass cache and fetch fresh |
323
+ | `maxAge` | Cache TTL in seconds (default: `3600`) |
324
+ | `cacheKey` | Custom cache key (default: hash of URL + method + headers + body) |
325
+ | `forceRefresh` | Bypass the cache and fetch fresh |
391
326
  | `cache` | `'auto'` \| `'memory'` \| `'fs'` \| `'none'` |
392
327
 
393
- ## Cache modes
328
+ Cache modes:
394
329
 
395
- - **`auto`** (default): memory in dev, filesystem in production
396
- - **`memory`**: in-process cache, cleared when the build process exits
397
- - **`fs`**: persisted under `node_modules/.cache/vite-plugin-html-pages/fetch/`
398
- - **`none`**: no caching, always fetches
330
+ - **`auto`** (default) memory in dev, filesystem in production builds
331
+ - **`memory`** in-process, cleared when the process exits
332
+ - **`fs`** persisted in `node_modules/.cache/vite-plugin-html-pages/fetch/`
333
+ - **`none`** always fetch
399
334
 
400
- Only `GET` requests are cached by default. For other methods, provide a
401
- `cacheKey` to enable caching.
335
+ Only `GET` requests are cached by default (pass a `cacheKey` to cache
336
+ other methods), and error responses are never cached — a flaky API
337
+ during one build won't poison the next one.
402
338
 
403
339
  ---
404
340
 
405
- # Layouts
341
+ ## TypeScript & typed params
406
342
 
407
- Reusable layout functions work naturally with HT.js.
343
+ Pages can be written in TypeScript (`.ht.ts` / `.ht.tsx`) with zero
344
+ configuration.
408
345
 
409
- ``` js
410
- import { fragment, html, head, body } from 'javascript-to-html'
346
+ Helper functions give your page modules full type inference:
411
347
 
412
- export default (...content) => fragment(
413
- '<!doctype html>',
414
- html(
415
- head(),
416
- body(
417
- ...content
418
- )
419
- )
420
- )
348
+ ```ts
349
+ // src/blog/[slug].ht.ts
350
+ import { definePageModule } from 'vite-plugin-html-pages/page'
351
+
352
+ export default definePageModule({
353
+ generateStaticParams: () => [{ slug: 'hello' }],
354
+ data: ({ params }) => ({ title: params.slug }),
355
+ render: ({ data }) => `<html><body><h1>${data.title}</h1></body></html>`,
356
+ })
421
357
  ```
422
358
 
423
- ---
359
+ Individual helpers (`definePage`, `defineData`, `defineStaticParams`)
360
+ are also exported. At build time this import is transparently swapped
361
+ for a **per-route generated module** whose `PageParams` are inferred
362
+ from the filename: `[slug]` → `{ slug: string }`, `[...path]` →
363
+ `{ path: string[] }`, `[...path]?` → `{ path?: string[] }`.
424
364
 
425
- # Plugin Options
426
-
427
- | Option | Description |
428
- |------|------|
429
- | `pagesDir` | root directory for pages |
430
- | `include` | page glob |
431
- | `exclude` | excluded files |
432
- | `cleanUrls` | `/page/index.html` instead of `/page.html` |
433
- | `renderConcurrency` | parallel rendering |
434
- | `renderBatchSize` | batch size |
435
- | `debug` | enable debug logging |
436
- | `site` | base URL for sitemap |
437
- | `rss` | RSS configuration |
365
+ Matching type declarations are generated into
366
+ `.vite-plugin-html-pages/types/` whenever the dev server or a build
367
+ runs add that folder to `.gitignore`.
438
368
 
439
369
  ---
440
370
 
441
- # Debug Mode
371
+ ## Assets & styling
442
372
 
443
- Enable debug logging when troubleshooting.
373
+ Reference assets from your HTML with root-relative URLs and the plugin
374
+ handles the rest:
444
375
 
445
376
  ```js
446
- htmlPages({
447
- debug: true
448
- })
377
+ export default () => `
378
+ <html>
379
+ <head>
380
+ <link rel="stylesheet" href="/styles.css">
381
+ <script type="module" src="/main.js"></script>
382
+ </head>
383
+ <body>...</body>
384
+ </html>
385
+ `
386
+ ```
387
+
388
+ At build time:
389
+
390
+ - **Referenced JS / TS / CSS is bundled** with esbuild — imports are
391
+ inlined, output is minified, and `.ts` files compile to `.js`.
392
+ - **Unreferenced code files are not emitted.** A helper like
393
+ `src/lib/api.ts` that you only import from `data()` stays out of
394
+ `dist/` — server-only code (and its secrets) never ships by accident.
395
+ - **Everything else is copied** (images, fonts, videos, ...), so CSS
396
+ `url()` references keep working.
397
+ - **`public/` behaves like normal Vite** — copied verbatim.
398
+
399
+ In dev, the same URLs are served through Vite's transform pipeline, so
400
+ TypeScript and CSS work identically without a build.
401
+
402
+ ### Missing-asset validation
403
+
404
+ Every generated page is checked: a `<script src="/x.js">` or stylesheet
405
+ `href` pointing at a file that exists in neither `src/` nor `public/`
406
+ **fails the build** with the exact paths that were checked. Prefer a
407
+ warning instead?
408
+
409
+ ```js
410
+ htmlPages({ missingAssets: 'warn' })
449
411
  ```
450
412
 
451
- Example output:
413
+ ---
414
+
415
+ ## Dev server
452
416
 
453
- [vite-plugin-html-pages] discovered entries [...]
454
- [vite-plugin-html-pages] dev pages [...]
455
- [vite-plugin-html-pages] render bundle ...
417
+ `vite dev` gives you the real site, not an approximation:
456
418
 
457
- This helps diagnose routing or build issues without modifying plugin
458
- code.
419
+ - Pages render **on request** through Vite's SSR module runner — edit a
420
+ page, its `data()`, or any imported module and reload.
421
+ - File changes inside your pages directory trigger an automatic
422
+ **full-reload** in the browser.
423
+ - Errors show a **source-mapped code frame** in the terminal pointing
424
+ at the exact line in your page — the server stays alive while you fix it.
459
425
 
460
- ---
426
+ ```
427
+ ── PAGE RELOAD ERROR ───────────────────── src/index.ht.js:6:20
461
428
 
462
- # Automatic Sitemap
429
+ ReferenceError: title is not defined
463
430
 
464
- A `sitemap.xml` is generated automatically.
431
+ > 6 │ head(title('My website')),
432
+ │ ^
465
433
 
466
- ```
467
- dist/sitemap.xml
434
+ Fix the error and save again.
435
+ Watching for file changes...
468
436
  ```
469
437
 
470
438
  ---
471
439
 
472
- # Optional RSS Feed
440
+ ## Generated extras
441
+
442
+ ### 404 page
443
+
444
+ Create `src/404.ht.js` and it's emitted as `dist/404.html` (the
445
+ convention GitHub Pages, Netlify, and Cloudflare Pages all understand).
446
+ No 404 page? A clean default is generated.
447
+
448
+ ### Sitemap
449
+
450
+ Set your site URL and `dist/sitemap.xml` is generated from all static
451
+ routes, correctly escaped:
452
+
453
+ ```js
454
+ htmlPages({ site: 'https://example.com' })
455
+ ```
456
+
457
+ ### RSS feed
473
458
 
474
459
  ```js
475
460
  htmlPages({
476
461
  rss: {
477
- site: "https://example.com",
478
- title: "My Blog",
479
- description: "Latest posts",
480
- routePrefix: "/blog"
462
+ site: 'https://example.com',
463
+ title: 'My Blog',
464
+ description: 'Latest posts',
465
+ routePrefix: '/blog', // which routes become feed items
481
466
  }
482
467
  })
483
468
  ```
484
469
 
485
- Produces:
470
+ Produces `dist/rss.xml` with an item for every page under `routePrefix`.
486
471
 
487
- ```
488
- dist/rss.xml
472
+ ---
473
+
474
+ ## Plugin options
475
+
476
+ ```js
477
+ htmlPages({
478
+ pagesDir: 'src',
479
+ cleanUrls: true,
480
+ site: 'https://example.com',
481
+ missingAssets: 'error',
482
+ debug: false,
483
+ })
489
484
  ```
490
485
 
491
- ---
486
+ | Option | Default | Description |
487
+ |--------|---------|-------------|
488
+ | `pagesDir` | `'src'` | Directory containing pages and assets |
489
+ | `pageExtensions` | `['.ht.js', '.html.js', ...]` | Which file suffixes are pages |
490
+ | `include` | derived from `pagesDir` | Custom glob(s) for page discovery |
491
+ | `exclude` | `[]` | Glob(s) to exclude from discovery |
492
+ | `root` | Vite root | Override the project root |
493
+ | `cleanUrls` | `true` | `/about/index.html` (`/about`) instead of `/about.html` |
494
+ | `site` | — | Base URL; enables `sitemap.xml` |
495
+ | `rss` | — | RSS config (`site`, `title`, `description`, `routePrefix`) |
496
+ | `missingAssets` | `'error'` | `'error'` or `'warn'` for broken asset references |
497
+ | `mapOutputPath` | — | `(page) => string` to customize output filenames |
498
+ | `renderConcurrency` | `8` | Pages rendered in parallel |
499
+ | `renderBatchSize` | `max(concurrency, 32)` | Pages per render batch |
500
+ | `debug` | `false` | Verbose logging of discovery, routing, and emission |
492
501
 
493
- # Performance
502
+ ### Performance
494
503
 
495
- Large sites can increase concurrency:
504
+ Large sites can raise the parallelism:
496
505
 
497
506
  ```js
498
507
  htmlPages({
499
508
  renderConcurrency: 16,
500
- renderBatchSize: 128
509
+ renderBatchSize: 128,
501
510
  })
502
511
  ```
503
512
 
504
513
  ---
505
514
 
506
- # Comparison
515
+ ## Comparison
507
516
 
508
- | Tool | Focus |
509
- |-----|-----|
510
- | Astro | component‑based SSG |
511
- | Next.js | React SSR framework |
512
- | vite-plugin-html-pages | minimal HTML SSG for Vite |
513
-
514
- ---
517
+ | Tool | What it is |
518
+ |------|------------|
519
+ | Astro | Component-based SSG with its own compiler and islands |
520
+ | Next.js | Full React framework with SSR/ISR |
521
+ | Eleventy | Template-language SSG (Nunjucks, Liquid, ...) |
522
+ | **vite-plugin-html-pages** | **Functions returning HTML, powered by plain Vite** |
515
523
 
516
- # Use Cases
524
+ If you want components, hydration, and a framework — use a framework.
525
+ If you want HTML files out of JavaScript functions with the Vite dev
526
+ experience, this is the smallest tool that does the whole job.
517
527
 
518
- `vite-plugin-html-pages` works well for:
528
+ ## Good fits
519
529
 
520
- - **Vite static site generation**
521
- - **File-based routing with Vite**
522
- - **Generating static HTML with Vite**
523
- - **Vite blog generators**
524
- - **Documentation sites**
525
- - **Minimal static site generators**
526
- - **HTML‑first Vite projects**
530
+ - Marketing and landing pages
531
+ - Blogs and documentation sites
532
+ - HTML-first projects with a sprinkle of JS
533
+ - API-driven static sites (with `fetchWithCache`)
534
+ - Any site where "view source" should show exactly what you wrote
527
535
 
528
536
  ---
529
537
 
530
- # License
538
+ ## License
531
539
 
532
540
  MIT