A minimal recipe to build modern, fast websites using plain HTML, Tailwind CSS, and Web Components without a Javascript UI framework.
Compose HTML at build time. Ship static files at runtime.
lean-html is a tiny build-time HTML compiler that lets you reuse HTML fragments without client-side JavaScript. Think PHP includes, but done at build time for static sites, with modern tooling.
Your marketing page doesn't need 847KB of JavaScript.
Typical React landing page:
- Bundle size: 2.3MB transferred
- Time to Interactive: 4.2s on 3G
- Lighthouse score: 67
- Dependencies: 412 packages
- Works without JS: ❌
Same page with lean-html:
- Bundle size: 12KB transferred
- Time to Interactive: 0.3s on 3G
- Lighthouse score: 100
- Dependencies: 0 runtime packages
- Works without JS: ✅
Most websites are just content. They don't need a JavaScript application framework.
Build your site the same way the web actually works:
- HTML is the primary artifact — semantic, readable, view-source friendly
- CSS does the styling — utility-first via Tailwind (optional)
- JavaScript is optional — progressive enhancement only
Everything is composed before deployment. If JavaScript fails to load, your site is still fully readable and functional.
Reuse HTML fragments with simple directives:
<html-include src="fragment.html" title="Hello" theme="dark" />Fragments are expanded at build time and fully inlined into the output HTML.
Fragments accept parameters that are substituted during the build:
<!-- sections/hero.html -->
<section class="hero {{theme}}">
<h1>{{title}}</h1>
<p>{{subtitle}}</p>
</section><!-- pages/index.html -->
<html-include
src="hero.html"
title="Welcome"
subtitle="Build fast sites"
theme="dark"
/><!-- dist/index.html -->
<section class="hero dark">
<h1>Welcome</h1>
<p>Build fast sites</p>
</section>Fragments can include other fragments. Circular includes are detected and rejected at build time:
<!-- sections/layout.html -->
<div class="layout">
<html-include src="header.html" />
<main>{{content}}</main>
<html-include src="footer.html" />
</div>CSS is generated using precompiled Tailwind binaries:
- No node_modules in production
- No lockfiles to manage
- No dependency resolution issues
- Just pure CSS output
The correct binary is selected automatically based on your OS.
Client-side interactivity uses standards-based Web Components:
<rx-accordion>
<h2>Section One</h2>
<div>
<p>This is plain HTML content.</p>
<p>It works without JavaScript.</p>
</div>
<h2>Section Two</h2>
<div>
<p>More plain HTML content.</p>
</div>
</rx-accordion>Without JavaScript: Renders as plain headings and content.
With JavaScript: Upgrades into an interactive accordion.
Components enhance existing markup without requiring Shadow DOM. HTML must remain readable without JavaScript.
Generate custom-elements.json for IDE autocomplete of fragment parameters.
├── dist/ # Build output (deploy this)
│ ├── index.html
│ ├── style.css
│ └── blog/
│ └── hello-world/
│ └── index.html
├── src/
│ ├── pages/ # Entry HTML files
│ │ ├── index.html
│ │ └── blog/
│ │ └── hello-world/
│ │ └── index.html
│ ├── sections/ # Reusable HTML fragments
│ │ ├── header.html
│ │ ├── footer.html
│ │ └── card.html
│ ├── components/ # Web Components (optional JS)
│ │ └── accordion.js
│ └── styles/
│ └── tailwind.css
├── tools/
│ ├── build.js # Build script
│ └── tailwind/
│ └── tailwindcss-* # Platform binaries
├── tailwind.config.js
├── package.json # Dev scripts only
└── custom-elements.json # Editor autocomplete# Build for production
npm run build
# Watch mode for development
npm run dev
# Clean build artifacts
npm run cleanThese scripts are dev-only — the produced dist/ contains everything you need to deploy.
- Developer writes pages in
src/pages/and fragments insrc/sections/ - Build script:
- Resolves
<html-include>directives and substitutes parameters - Runs Tailwind via the precompiled binary to produce
dist/style.css - Copies assets and optional component JS into
dist/
- Resolves
- Deploy
dist/to any static host (Netlify, Cloudflare Pages, S3, etc.)
- ✅ Static marketing pages and landing pages
- ✅ Documentation sites and blogs
- ✅ Portfolio sites
- ✅ Internal tools and dashboards
- ✅ Sites where SEO and fast first-load matter
- ✅ Projects that should still work in 10 years
Not a fit when you need:
- Complex single-page app with client-only routing
- Heavy client-side rendering with complex state frameworks
- A tiny build-time HTML compiler
- Parameterized includes for reusability
- Zero runtime JavaScript dependency
- Utility-first CSS through Tailwind, delivered via static files
- A clean foundation for Web Components
- A reminder that HTML is enough
- Not a framework
- Not a runtime component system
- Not a replacement for React/Vue/Svelte — but an alternative for sites that don't need their complexity
Q: Is this just PHP includes?
A: Yes. And that's fine. PHP includes were a good idea. This is the same concept, done at build time with modern tooling.
Q: What about state management?
A: For the 2% of your site that needs state, use Web Components with the Platform (localStorage, sessionStorage, etc.). For the other 98%: it's just content.
Q: What about routing?
A: It's just files. /about is about/index.html. Your web server handles routing. Like it has since 1991.
Q: Can I use TypeScript?
A: It's build time. Use whatever you want in your build script. Want TypeScript? Write build.ts instead of build.js.
Q: What about SEO?
A: It's literally HTML. Your content is already there. This is the optimal SEO setup.
Q: Won't this be slow for large sites?
A: It's HTML concatenation. Computers are fast at this. A thousand-page site builds in under a second.
Q: What about hot reload?
A: npm run dev watches your files and rebuilds on change. Pair with live-server for browser refresh.
- Keep HTML semantic and readable — the site should be meaningful without JS
- Use Web Components only for interactive parts; lean on HTML/CSS for layout and content
- Build fragment parameters intentionally; prefer explicit props over implicit logic
- Avoid hiding essential content behind JavaScript
This is intentionally minimal and lightly maintained. Pull requests for clear fixes, docs, and small improvements are welcome. For bigger design changes, open an issue first to discuss direction.
Consider this 'source-available' — a stable foundation, not an actively maintained alternative to React.
MIT — provided AS-IS. Test thoroughly before using in production.
Ship HTML. Be done.
