HAM is a lightweight static site framework that lets you build websites using modular, reusable HTML components. Instead of duplicating headers, footers, and navigation across every page, you define them once and compose them together. HAM compiles everything into plain HTML files ready for deployment anywhere.
- Installation
- Quick Start
- Core Concepts
- How Compilation Works
- Template Variables
- TypeScript Support
- Commands
- Project Structure
- Reverse Proxy
- Contributing
- License
Requires: Go 1.21+
go install github.com/fobilow/ham/cmd/ham@latestTo install a specific version, replace @latest with the version number (e.g. @v1.0.0).
Build from source:
git clone https://github.com/fobilow/ham.git
cd ham
make installVerify the installation:
ham versionCreate a new project, start the dev server, and open it in your browser:
ham init mysite
cd mysite
npm install
ham serve -w ./src -p 4120Open http://localhost:4120 to see your site. The dev server watches for file changes and automatically reloads the browser.
When you're ready to deploy, build the production files:
ham build -w ./src -o ./publicYour compiled site is now in the public/ directory, ready to host on any static file server.
HAM has three building blocks: Layouts, Pages, and Partials. They fit together like this:
Layout (page skeleton)
βββ Page (content for a specific route)
βββ Partial (reusable HTML fragment)
HAM uses file extensions to distinguish between component types:
| Component | Extension | Example |
|---|---|---|
| Layout | .lhtml |
default.lhtml, blog.lhtml |
| Page | .html |
index.html, about.html |
| Partial | .phtml |
header.phtml, footer.phtml, card.phtml |
This is important β HAM uses these extensions to identify what each file is. Using the wrong extension will cause compilation errors.
A layout defines the outer shell of a web page β the <html>, <head>, and <body> structure. Use special <embed> and <link> tags to mark where dynamic content should be injected.
Layout files use the .lhtml extension.
<!-- src/default.lhtml -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Site</title>
<link type="ham/layout-css"/>
</head>
<body>
<embed type="ham/page"/>
<embed type="ham/layout-js"/>
</body>
</html>| Placeholder | What it does |
|---|---|
<link type="ham/layout-css"/> |
Replaced with <link> tags for all CSS files listed in the page config |
<embed type="ham/page"/> |
Replaced with the compiled page content |
<embed type="ham/layout-js"/> |
Replaced with <script> tags for all JS files listed in the page config |
A page provides the content for a specific route. Every page must reference a layout via the data-ham-page-config attribute. This is where you declare which layout, CSS, and JavaScript files the page needs.
Page files use the .html extension.
<!-- src/index.html -->
<div class="page"
data-ham-page-config='{
"layout": "default.lhtml",
"css": ["index.css"],
"js": ["app.js"]
}'
>
<embed type="ham/partial" src="header.phtml"/>
<h1>Welcome to my site</h1>
<p>This is the home page.</p>
</div>Page config options:
| Key | Type | Description |
|---|---|---|
layout |
string | Path to the layout file (relative to the page) |
css |
string[] | CSS files to include |
js |
string[] | JavaScript files to include |
js-mod |
string[] | TypeScript/ES module files (rendered as <script type="module">) |
id |
string | Sets the id attribute on the <body> tag |
Partials are reusable HTML fragments β things like headers, footers, cards, or any component you want to share across pages. Include them with <embed type="ham/partial">.
Partial files use the .phtml extension.
<!-- src/header.phtml -->
<header>
<nav>
<a href="/">Home</a>
<a href="/about.html">About</a>
</nav>
</header>Include it in a page or another partial:
<embed type="ham/partial" src="header.phtml"/>Partials can be nested β a partial can include other partials, and HAM will resolve them all recursively.
When you run ham build, HAM takes your source files and assembles them into complete HTML pages:
- Parse β Reads each
.htmlpage file and extracts thedata-ham-page-config - Embed partials β Recursively replaces all
<embed type="ham/partial">tags with the contents of the referenced.phtmlfiles - Inject into layout β Inserts the compiled page content into the layout at
<embed type="ham/page"/> - Attach resources β Replaces
<link type="ham/layout-css"/>and<embed type="ham/layout-js"/>with the actual<link>and<script>tags - Write output β Saves the final HTML files to the output directory, preserving the folder structure
Before (source files):
src/default.lhtml β layout shell
src/header.phtml β reusable nav partial
src/index.html β home page
src/index.css β home page styles
After (ham build):
<!-- public/index.html -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Site</title>
<link rel="stylesheet" href="/assets/css/index.css"/>
</head>
<body>
<div class="page">
<header>
<nav>
<a href="/">Home</a>
<a href="/about.html">About</a>
</nav>
</header>
<h1>Welcome to my site</h1>
<p>This is the home page.</p>
</div>
<script src="/assets/js/app.js"></script>
</body>
</html>You can pass variables into partials using data-ham-replace. This lets you reuse the same partial with different content.
Define a partial with placeholders (use double underscores):
<!-- src/card.phtml -->
<div class="card">
<h2>__title__</h2>
<p>__description__</p>
</div>Use it with different values:
<embed type="ham/partial" src="card.phtml"
data-ham-replace="title:Getting Started,description:Learn the basics of HAM"/>
<embed type="ham/partial" src="card.phtml"
data-ham-replace="title:Advanced Usage,description:Take your site to the next level"/>HAM projects come pre-configured with Rollup for TypeScript bundling. Use the js-mod config key instead of js to include TypeScript files:
<div class="page"
data-ham-page-config='{
"layout": "default.lhtml",
"css": ["index.css"],
"js-mod": ["index.ts"]
}'
>These are rendered as <script type="module"> tags in the final output.
| Command | Description |
|---|---|
ham init <sitename> |
Create a new project with boilerplate files |
ham build -w <dir> -o <dir> |
Compile the site for production |
ham serve -w <dir> -p <port> |
Start a dev server with hot reload (default port: 4120) |
ham proxy |
Run a reverse proxy for API + static files |
ham version |
Print the current HAM version |
ham help |
Show usage information |
| Flag | Default | Description |
|---|---|---|
-w |
./ |
Working directory (where your src/ folder is) |
-o |
./public |
Output directory for compiled files |
| Flag | Default | Description |
|---|---|---|
-w |
./ |
Working directory |
-p |
4120 |
Port number |
The dev server uses Server-Sent Events to trigger browser reloads when source files change. No browser extension required.
After running ham init mysite, you get:
mysite/
βββ ham.json # Project configuration
βββ package.json # npm dependencies (Rollup, TypeScript)
βββ tsconfig.json # TypeScript configuration
βββ rollup.config.js # JS/TS bundler configuration
βββ .gitignore
βββ src/
βββ default.lhtml # Default layout
βββ index.html # Home page
βββ index.css # Home page styles
βββ index.ts # Home page script
You're free to organize src/ however you like. A typical multi-page site might look like:
src/
βββ default.lhtml # Shared layout
βββ header.phtml # Shared nav partial
βββ footer.phtml # Shared footer partial
βββ index.html # Home page
βββ index.css
βββ about/
β βββ index.html # About page
β βββ index.css
βββ blog/
βββ index.html # Blog listing page
βββ post.html # Individual blog post page
ham proxy runs a production-ready server that serves your compiled site and proxies API requests to a backend. Configure it with environment variables:
| Variable | Default | Description |
|---|---|---|
WEB_ROOT |
./public |
Directory containing compiled HTML |
PROXY_PORT |
8082 |
Port to listen on |
API_ENDPOINT |
http://localhost:8080 |
Backend API URL |
API_PROXY_PREFIX |
/api/ |
URL prefix for API routes |
Any request matching the API prefix is forwarded to your backend; everything else is served as a static file.
Pages can require authentication by adding data-ham-proxy="requires-authentication" to the page element. The proxy checks for a valid session token (set via the X-HAM-PROXY-TOKEN header) before serving the page.
HAM is open source and contributions are welcome! Whether it's fixing a bug, improving documentation, or suggesting a new feature β we'd love to hear from you.
- Report bugs or request features β Open an issue
- Submit a pull request β Fork the repo, make your changes, and open a PR
- Ask questions or share feedback β Start a discussion or open an issue
If you're new to open source, HAM is a great project to get started with. The codebase is small and approachable. Pick an issue, ask questions, and don't be afraid to submit a PR β even small improvements make a difference.
HAM is released under the MIT License.