npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-html-pages

v1.3.7

Published

Minimal static site generation (SSG) for Vite using JavaScript functions that return HTML

Readme

vite-plugin-html-pages

npm version npm downloads license vite

Minimal static site generation for Vite using JavaScript files that return HTML.

Generate static HTML pages from *.ht.js modules using Vite and javascript-to-html.

⭐ If this project helps you, please consider starring it.

Built for the Vite ecosystem

Works seamlessly with:

  • Vite
  • 📦 Rollup / Rolldown
  • 🧩 javascript-to-html

A minimal static site generator for Vite that keeps pages as simple JavaScript functions returning HTML.


TL;DR

Write:

// src/index.ht.js

import { fragment, html, body, head, title, h1 } from 'javascript-to-html'

export default () => fragment(
  '<!doctype html>',
  html({lang: "en"},
    head(
      title("My website")
    ),
    body(
      h1('Hello world')
    )
  )
)

Run:

vite build

Get:

<!-- dist/index.html -->
 
<!doctype html>
<html lang="en">
  <head>
    <title>My website</title>
  </head>
  <body>
    <h1>Hello world</h1>
  </body>
</html>

Features

  • File-based routing
  • Dynamic routes [slug]
  • Multiple parameters [year]/[slug]
  • Catch-all routes [...slug]
  • Optional catch-all routes [...slug]?
  • Route groups (admin)/users.ht.js
  • Index routes blog/[slug]/index.ht.js
  • Static params generation
  • Fetch caching for data loading
  • Dev server SSR rendering
  • Parallel static generation
  • Automatic 404.html
  • Automatic sitemap.xml
  • Optional RSS feed generation
  • Debug logging

Installation

npm install vite-plugin-html-pages javascript-to-html

Why this exists

Modern static site tools are powerful, but they often introduce:

  • frameworks
  • component systems
  • complex build pipelines
  • opinionated conventions

Sometimes you just want to:

  • write HTML
  • organize pages in folders
  • generate static files

vite-plugin-html-pages exists for that use case.

It gives you:

  • file‑based routing
  • dynamic pages
  • static generation
  • dev server rendering

while keeping pages as simple JavaScript functions that return HTML.


Quick Start

vite.config.js

import { defineConfig } from "vite"
import htmlPages from "vite-plugin-html-pages"

export default defineConfig({
  plugins: [htmlPages()]
})

Example Project Structure

src/

  index.ht.js
  about.ht.js

  blog/
    index.ht.js
    [slug].ht.js
    [year]/[slug].ht.js

  docs/
    [...slug]?.ht.js

  (admin)/
    users.ht.js

Routing

Routes are generated directly from the filesystem.

| Feature | File | URL | |-----|-----|-----| | static routes | index.ht.js | / | | dynamic routes | blog/[slug].ht.js | /blog/my-post | | multiple params | blog/[year]/[slug].ht.js | /blog/2026/my-post | | catch-all | docs/[...slug].ht.js | /docs/api/auth/login | | optional catch-all | docs/[...slug]?.ht.js | /docs or /docs/getting-started | | index routes | products/[product]/index.ht.js | /products/iphone-18 | | route groups | (admin)/users.ht.js | /users |


Dynamic Routes

src/blog/[slug].ht.js

Matches:

/blog/hello-world
/blog/my-first-post

Example:

import { fragment, html, body, h1 } from 'javascript-to-html'

export function generateStaticParams() {
  return [
    { slug: 'hello-world' },
    { slug: 'my-first-post' }
  ]
}

export default ({ params }) => fragment(
  '<!doctype html>',
  html(
    body(
      h1(params.slug)
    )
  )
)

Multiple Parameters

src/blog/[year]/[slug].ht.js

Matches:

/blog/2026/vite-routing
/blog/2025/my-first-post

Example:

import { fragment, html, body, h1 } from 'javascript-to-html'

export function generateStaticParams() {
  return [
    { 
      year: 2026,
      slug: "vite-routing"
    },
    { 
      year: 2025,
      slug: 'my-first-post'
    }
  ]
}

export default ({ params }) => fragment(
  '<!doctype html>',
  html(
    body(
      h1(params.slug)
    )
  )
)

Catch-All Routes

src/docs/[...slug].ht.js

Matches:

/docs/api/auth/login
/docs/guides/rendering/static

Params:

params.slug === "api/auth/login"

Optional Catch-All Routes

src/docs/[...slug]?.ht.js

Matches both:

/docs
/docs/getting-started
/docs/api/auth/login

Params:

| URL | params.slug | |-----|-------------| | /docs | "" | | /docs/api | "api" | | /docs/api/auth | "api/auth" |


Route Groups

Folders wrapped in parentheses are ignored in URLs.

src/(admin)/users.ht.js

URL:

/users

Index Routes

Files named index.ht.js map to the parent route.

src/blog/index.ht.js        -> /blog
src/blog/[slug]/index.ht.js -> /blog/my-post

Static Params

Dynamic routes can export generateStaticParams.

export function generateStaticParams() {
  return [
    { slug: "hello-world" },
    { slug: "vite-routing" }
  ]
}

Data Loading

Pages can export a data() function.

export async function data({ params }) {
  return { title: params.slug }
}

Caching

Use fetchWithCache for HTTP requests during static generation. Responses are cached to avoid repeated network calls across page builds.

import { fetchWithCache } from 'vite-plugin-html-pages'

export async function data({ params }) {
  const res = await fetchWithCache(
    `https://api.example.com/posts/${params.slug}`,
    {
      // fetch API options
    },
    { maxAge: 3600 }
  )
  const post = await res.json()
  return { post }
}

Options

| Option | Description | |--------|-------------| | maxAge | Cache TTL in seconds (default: 3600) | | cacheKey | Custom cache key (default: hash of URL + method + headers) | | forceRefresh | Bypass cache and fetch fresh | | cache | 'auto' | 'memory' | 'fs' | 'none' |

Cache modes

  • auto (default): memory in dev, filesystem in production
  • memory: in-process cache, cleared when the build process exits
  • fs: persisted under node_modules/.cache/vite-plugin-html-pages/fetch/
  • none: no caching, always fetches

Only GET requests are cached by default. For other methods, provide a cacheKey to enable caching.


Layouts

Reusable layout functions work naturally with HT.js.

import { fragment, html, head, body } from 'javascript-to-html'

export default (...content) => fragment(
  '<!doctype html>',
  html(
    head(),
    body(
      ...content
    )
  )
)

Plugin Options

| Option | Description | |------|------| | pagesDir | root directory for pages | | include | page glob | | exclude | excluded files | | cleanUrls | /page/index.html instead of /page.html | | renderConcurrency | parallel rendering | | renderBatchSize | batch size | | debug | enable debug logging | | site | base URL for sitemap | | rss | RSS configuration |


Debug Mode

Enable debug logging when troubleshooting.

htmlPages({
  debug: true
})

Example output:

[vite-plugin-html-pages] discovered entries [...]
[vite-plugin-html-pages] dev pages [...]
[vite-plugin-html-pages] render bundle ...

This helps diagnose routing or build issues without modifying plugin code.


Automatic Sitemap

A sitemap.xml is generated automatically.

dist/sitemap.xml

Optional RSS Feed

htmlPages({
  rss: {
    site: "https://example.com",
    title: "My Blog",
    description: "Latest posts",
    routePrefix: "/blog"
  }
})

Produces:

dist/rss.xml

Performance

Large sites can increase concurrency:

htmlPages({
  renderConcurrency: 16,
  renderBatchSize: 128
})

Comparison

| Tool | Focus | |-----|-----| | Astro | component‑based SSG | | Next.js | React SSR framework | | vite-plugin-html-pages | minimal HTML SSG for Vite |


Use Cases

vite-plugin-html-pages works well for:

  • Vite static site generation
  • File-based routing with Vite
  • Generating static HTML with Vite
  • Vite blog generators
  • Documentation sites
  • Minimal static site generators
  • HTML‑first Vite projects

License

MIT