| Author | Alex Grant <alex@localnerve.com> (https://www.localnerve.com) |
|---|---|
| Date | April 25, 2026 |
| Title | Jam-Build Custom Static Site Generator |
Jam-Build features a sophisticated custom static site generator built on top of Gulp, designed specifically for building high-performance, offline-first web applications. The generator combines Handlebars templating, Sass compilation, advanced JavaScript bundling with Rollup, responsive image processing, and comprehensive asset management.
- 📐 Architecture
- 🔧 Build Process Flow
- 👷 Template System
- 🗂️ Asset Processing
- 🗄️ Asset Management
- 😏 Development Features
- ✴️ Integration Points
- 🪁 Usage
- ⛩️ Site Data Configuration
- 🌌 Template System Integration
- 💚 Template Compilation Examples
The static site generator consists of multiple specialized build modules, each handling a specific aspect of the build process:
src/build/
├── index.js # Build orchestration and task sequencing
├── settings.js # Configuration management for prod/dev builds
├── data.js # Site data loading and caching (site-data.json)
├── templates.js # Handlebars template compilation and rendering
├── hb-helpers.js # Custom Handlebars helper functions
├── styles.js # Sass compilation and CSS processing
├── scripts.js # JavaScript bundling with Rollup
├── images.js # Responsive image processing
├── assets.js # Asset generation (robots.txt, security.txt, sitemaps, llms.txt, manifests)
├── html.js # HTML minification
├── revision.js # Asset versioning and cache-busting
├── sw.js # Service worker generation
└── copy.js # File copying utilities
The generator is driven by a central site-data.json file that defines:
- Page structure and metadata
- Navigation configuration
- Image processing rules
- Build settings and paths
- Content organization
The main build sequence follows a carefully orchestrated pipeline:
export async function createBuild (settings, args) {
return gulp.series(
// 1. Environment Setup
prepare, // Clean, create dirs, housekeeping
// 2. Asset Processing
dirCopy.bind(null, settings.copyImages), // Copy static images
imageProcessingSequence, // Generate responsive images
createStyles.bind(null, settings.styles), // Compile Sass → CSS
createScripts.bind(null, settings.scripts), // Bundle JavaScript
generateAssets.bind(null, settings.assets), // Generate robots/security/sitemaps/llms.txt/manifests
// 3. Versioning and Templates
assetRevision.bind(null, settings.revision), // Add cache-busting hashes
renderHtml.bind(null, settings.templates, args),// Render Handlebars templates
pageRevision.bind(null, settings.revision), // Update asset references
// 4. Finalization
buildSwMain.bind(null, settings.sw), // Generate service worker
minifyHtml.bind(null, settings.html), // Minify HTML output
// 5. Optional Debugging
audit // Dump build data if --dump flag provided
);
}Provides an environment-specific build configuration:
export function createSettings (prod = true) {
return {
prod,
// Asset processing configuration
styles: {
srcClient: 'src/application/client',
prod,
webStyles: '/styles' // URL path for CSS files
},
scripts: {
prod,
webScripts: '/', // URL path for JS files
replacements: {
// Environment-specific string replacements
'process.env.AUTHZ_URL': JSON.stringify(process.env.AUTHZ_URL),
'process.env.AUTHZ_CLIENT_ID': JSON.stringify(process.env.AUTHZ_CLIENT_ID)
}
},
// Responsive image configuration
images: {
responsiveConfig: {
'hero-*.jpg': [
{ quality: 80, width: 670, progressive: true },
{ quality: 80, width: 1024, progressive: true },
{ quality: 65, width: 1440, progressive: true },
{ quality: 65, width: 1920, progressive: true }
]
}
}
};
}The template system uses Handlebars with a sophisticated partial system:
data/
├── site-data.json # Central data configuration
├── partials/
│ ├── page/ # Page layout templates
│ │ ├── header.hbs
│ │ ├── footer.hbs
│ │ └── {page}.hbs
│ └── content/ # Page-specific content
│ ├── home/
│ │ ├── intro.hbs
│ │ └── features.hbs
│ └── about/
│ └── mission.hbs
- Data Loading: Loads
site-data.jsonand caches for reuse - Partial Discovery: Automatically discovers page and content templates
- Inline Assets: Compiles inline CSS and JavaScript
- Helper Registration: Registers custom Handlebars helpers
- Template Assembly: Combines layouts with content using the pattern:
Provides powerful template utilities:
// String manipulation helpers
capFirst(sentence) // Capitalize first letter of each word
subWords(sentence, start, end) // Extract word ranges
subChars(word, start, end) // Extract character ranges
concat(...args) // Concatenate strings
strip(subject, ...exclusions) // Remove specific words
// Logic helpers
equals(value1, value2) // Strict equality testing
or(value1, value2) // Logical OR operations
// Asset helpers (bound with context)
imageUrl(file) // Generate image URLs with proper root
styleUrl(file) // Generate stylesheet URLs
scriptUrl(file) // Generate script URLs
svgPage(page) // Dynamic SVG partial selection
// State management
setState(reference, value) // Store temporary template state
getState(reference) // Retrieve template stateThe template system supports inline CSS and JavaScript:
- Sass compilation with production optimization
- Automatic CSS minification in production builds
- Load path resolution for imports and dependencies
- Rollup bundling with tree-shaking
- Environment variable replacement
- Production minification with Terser
- Dart Sass Compilation: Modern Sass processing with enhanced performance
- Asset Functions: Custom Sass functions for dynamic asset path resolution
- Autoprefixer Integration: Automatic vendor prefix handling
- Load Path Management: Supports node_modules and local imports
- Data Integration: Site data available in Sass via custom functions
// Custom Sass functions provide dynamic asset paths
.hero {
background-image: image-url('hero-1440-size.jpg');
font-family: font-url('custom-font.woff2');
}
// Site data integration
@each $page in data('nav-pages') {
.page-#{$page} { /* page-specific styles */ }
}- Multiple Entry Points: Supports main app, admin interface, and page-specific bundles
- Code Splitting: Automatic chunk generation for optimal loading
- Tree Shaking: Dead code elimination in production builds
- Dynamic Imports: Support for lazy loading with variable resolution
[
dynamicImportVariables(), // Dynamic import with variables
outputManifest(), // Asset manifest generation
nodeResolve(), // Node module resolution
replace(), // Environment variable replacement
alias(), // Path aliasing
nodePolyfills(), // Node.js API polyfills for browser
istanbul(), // Code coverage instrumentation (dev)
terser(), // Minification (production)
visualizer() // Bundle analysis
]- Build-time Variables: Injects environment variables and build metadata
- Development/Production Modes: Different optimization strategies
- Source Maps: Full source map support for debugging
The image processing code was moved into its own package
gulp-images. This section talks deeply about its usage and integration into the static site generator (SSG) showcased in this repo.
The image processing system goes far beyond simple resizing - it creates a sophisticated metadata system that drives both CSS generation and HTML optimization. During the image processing phase, the build system captures detailed information about each generated image variant, including dimensions, file sizes, MIME types, and quality settings. This metadata is dynamically injected into the cached site-data.json object in memory, making it available to both the Sass compilation and template rendering phases.
As images are processed and resized, the system builds comprehensive metadata objects that include not just the basic file information, but performance-critical data like optimal breakpoints, loading priorities, and format specifications. This captured metadata becomes part of the site data structure under an images namespace, organized by image patterns and sizes. For example, hero images processed through the responsive pipeline generate entries that map each size variant to its corresponding breakpoint, creating a data structure that can be consumed by both CSS media queries and HTML preload tags.
The captured image metadata powers multiple aspects of the final output. In the Sass compilation phase, custom asset functions can access this metadata to generate responsive CSS with precise breakpoints that match the actual generated image sizes, ensuring perfect alignment between image variants and their corresponding media queries. During template rendering, this same metadata enables the automatic generation of optimized <link rel="preload"> tags with accurate media attributes, ensuring that browsers preload exactly the right image variant for each viewport size, dramatically improving perceived performance.
- Multiple Format Generation: Creates responsive image sets with metadata tracking
- Quality Optimization: Different quality settings per size with performance metrics
- Progressive JPEG: Optimized loading with format specification in metadata
- Breakpoint Intelligence: Automatically determines optimal responsive breakpoints
- WEBP Generation: Automatically creates optimized WEBP versions of all JPEGs and PNGs
The image processing system uses WASM encoders and decoders to optimize images. This offers significant performance advantages in some cases, but also reduces the external package supply chain. This reduces maintenance costs and much of the friction one experiences upgrading the application image and staying on top of CVEs.
Using WASM based image processing tools allows for a much smaller application image to be used. The size reduction Jam-Build experienced removing the typical C language toolchain from the application build was about 1 Gigabyte. This requirement change alone allowed the base image to migrate from Debian Bullseye to Alpine with just a few minor add-ons.
responsiveConfig: {
'hero-*.jpg': [
{ quality: 80, width: 670, progressive: true, rename: { suffix: '-670-size' }},
{ quality: 80, width: 1024, progressive: true, rename: { suffix: '-1024-size' }},
{ quality: 65, width: 1440, progressive: true, rename: { suffix: '-1440-size' }},
{ quality: 65, width: 1920, progressive: true, rename: { suffix: '-1920-size' }}
]
}
// Generated metadata structure in site-data:
{
"images": {
"hero-home": {
"670": { "basename": "hero-home-670-size.jpg", "mimeType": "image/jpeg", "width": 670 },
"1024": { "basename": "hero-home-1024-size.jpg", "mimeType": "image/jpeg", "width": 1024 },
"1440": { "basename": "hero-home-1440-size.jpg", "mimeType": "image/jpeg", "width": 1440 },
"1920": { "basename": "hero-home-1920-size.jpg", "mimeType": "image/jpeg", "width": 1920 }
}
}
}- Asset Hashing: Generates content-based hashes for all static assets
- Manifest Generation: Creates mapping from original to hashed filenames
- Reference Updates: Updates all HTML references to use hashed versions
- Service Worker Integration: Provides asset lists for service worker caching
- Automatic Asset Discovery: Scans build output for cacheable resources
- Version Management: Generates versioned service worker with asset lists
- Custom Logic Integration: Merges with hand-written service worker code
- Cache Strategy Configuration: Supports different caching strategies per asset type
When run with --dump, the build system outputs:
- site-data.json: Processed site data
- build-settings.json: Complete build configuration
- render-templates.json: Compiled template metadata
- hb-partials.json: All registered Handlebars partials
- Rollup Visualizer: Generates interactive bundle analysis reports
- Size Tracking: Monitors asset sizes across builds
- Dependency Analysis: Visualizes module dependencies and relationships
- Data Caching: Site data loaded once and cached across build steps
- Incremental Processing: Only processes changed files where possible
- Parallel Processing: Concurrent asset processing where safe
- Asset Minification: HTML, CSS, and JavaScript minification
- Image Optimization: Automatic image compression and format optimization
- Code Splitting: Optimal JavaScript bundle sizes
- Tree Shaking: Eliminates unused code
- Build-time Injection: Environment variables injected into client code
- Configuration Flexibility: Different settings for development/production
- Security: Sensitive data handled at build time, not runtime
- Asset Lists: Automatic generation of cacheable asset inventories
- Version Management: Coordinated versioning between build and service worker
- Offline Strategy: Pre-caches critical resources for offline functionality
- Dynamic CSP Generation: Generates CSP headers based on actual asset usage
- Hash-based Security: Uses asset hashes for inline script/style security
- External Resource Management: Configurable external resource permissions
# Production build
npm run build
# Development build
npm run build:dev
# Debug build with data dumps
npm run build -- --dump
# Development build with service worker instrumentation
npm run build:dev:sw- Create processor module in
src/build/ - Add configuration to
settings.js - Integrate into build sequence in
index.js - Update template helpers if needed
Add helpers to hb-helpers.js and they'll be automatically registered.
Modify partial discovery logic in templates.js to support new template organizations.
The site-data.json file serves as the central configuration that drives the entire static site generation process. It defines site metadata, page structure, business information, and social media integration.
{
"defaultTitle": "Business Name", // Default page title fallback
"defaultDescription": "This is a description.", // Default meta description
"appHost": "domain.com", // Primary domain for canonical URLs
"elevator": "This is an elevator pitch.", // Tagline/elevator pitch
"themeColor": "#1B7BA1", // PWA theme color
"backgroundColor": "#1B5A7F", // PWA background color
"tileColor": "#DA532C", // Windows tile color
"uaId": "UA-NNNNNNNNN-N" // Google Analytics ID
}{
"business": {
"shortName": "Business Name", // Short business name for PWA
"name": "Business Name, LLC", // Full legal business name
"url": "https://domain.com", // Primary business URL
"domain": "domain.com", // Domain for structured data
"logo": "https://domain.com/images/logo.svg", // Logo URL
"phone": "123-456-7890", // Business phone number
"email": "info@domain.com", // Business email
"address": { // Complete business address
"line1": "123 street st",
"city": "cityname",
"state": "statename",
"zip": "00000-0000",
"country": "US"
}
}
}{
"social": {
"facebook": "https://www.facebook.com/people/business",
"linkedin": "https://www.linkedin.com/in/business",
"twitter": "https://x.com",
"twitterMeta": {
"image": "https://domain.com/images/ogimage-1200x630.png"
},
"og": { // Open Graph metadata
"title": "Business Name",
"description": "This is a description.",
"type": "website",
"image": [ // Multiple image formats for different platforms
{
"url": "https://domain.com/images/ogimage-1200x400.png",
"type": "image/png",
"alt": "Business Name",
"width": "1200",
"height": "400"
}
// ... additional image variants
]
}
}
}Each page is defined with comprehensive metadata that controls generation, SEO, navigation, and behavior:
{
"pages": {
"home": {
"title": "Home", // Page title for <title> tag
"type": "nav", // Page type: nav|admin|legal|none
"name": "home", // Internal page identifier
"route": "/", // URL route for the page
"label": "Home", // Navigation link text
"template": "main-banner", // Handlebars template to use
"file": "home", // Output filename (becomes home.html)
"sitemap": { // XML sitemap generation settings
"changefreq": "monthly",
"priority": 1.0
},
"order": 0 // Navigation order
}
}
}Navigation Pages (type: "nav")
- Appear in main site navigation
- Include full SEO metadata
- Support all template features
- Examples: home, about, contact
Admin Pages (type: "admin")
- Special administrative pages
- May have
external-cssfor additional stylesheets - Can use
skip-allto exclude shared content - Example:
_adminpage
Legal Pages (type: "legal")
- Footer-linked legal documents
- Lower sitemap priority
- Use
skip-allto exclude shared content - Examples: terms, privacy
Error Pages (type: "none")
- HTTP error pages
- Not included in navigation or normal sitemaps
- Use
skip-allto exclude shared content - Examples: 404, 500 error pages
{
"alt-label": "Log Out", // Alternative label (e.g., when logged in)
"skip-all": true, // Exclude shared 'all' content sections
"external-css": "admin.css", // Additional CSS file to load
"description": "Custom page description" // Override default description
}- Template Selection: Each page specifies its
template(e.g., "main-banner") - Content Discovery: System looks for content partials in
data/partials/content/{page}/ - Template Assembly: Combines header + template + footer using pattern:
data/partials/content/
├── all/ # Shared content for all pages
│ └── section-0.hbs # Rendered on most pages
├── home/ # Home page specific content
│ ├── hero-0.hbs # Hero section
│ ├── section-0.hbs # First content section
│ ├── section-1-app-dyn.hbs # Dynamic app data section
│ └── section-2-user-dyn.hbs # Dynamic user data section
└── contact/ # Contact page specific content
├── hero-0.hbs
└── section-0.hbs
- hero-{N}.hbs: Hero/banner sections (typically first)
- section-{N}.hbs: Regular content sections
- section-{N}-{descriptor}.hbs: Specialized sections with descriptive names
All templates have access to the complete siteData object:
The template system supports both static and dynamic content areas:
Static Content: Rendered at build time from Handlebars templates
Dynamic Content Placeholders: Containers for runtime data loading
Templates receive several context variables:
page: Current page name (e.g., "home", "about")siteData: Complete site-data.json objectcontent: Map of content template names by pageinlineCss: Map of inline CSS partial namesactive: Current active page for navigation highlightingnoIndex: Boolean for robots meta tagnoNav: Boolean for navigation visibilityhtmlClasses: Array of CSS classes for html element
This data-driven approach allows for highly maintainable, SEO-optimized static sites with dynamic content capabilities, making it ideal for modern web applications that need both performance and flexibility.
The navigation system uses site data to generate consistent navigation across all pages:
Templates automatically incorporate business data throughout the site:
This comprehensive site data structure enables the generation of professional, SEO-optimized, and maintainable static sites with sophisticated template composition and dynamic content integration capabilities.