Gulp + Nunjucks: The Pragmatic Workflow
In an era of complex 'Dragons' like Vite and Eleventy, there is immense value in a build system that is explicit, fast, and deterministic. For landing pages and microsites, the Gulp + Nunjucks stack remains a powerhouse of productivity.
Modern Comparison
Before diving into the code, let's look at where this stack sits in the 2026 landscape:
| Feature | Eleventy + Vite | Gulp + Nunjucks | SvelteKit |
|---|---|---|---|
| Complexity | High (The Dragon) | Medium (Explicit) | Low (Integrated) |
| DX | Sync-Heavy | Reliable / Manual | Seamless |
| Best For | Large SSG Sites | Marketing / Microsites | Interactive Apps |
1. Core Project Structure
A clean architecture is the foundation of a scalable static site. By separating source logic from the distribution build, we ensure the dist/ folder is always ready for Firebase or S3 hosting.
/
├── dist/ # Compiled output (Production Ready)
├── src/
│ ├── data/ # JSON data models
│ ├── views/ # Nunjucks templates
│ │ └── _parts/ # Macros, Layouts, Partials
│ ├── scss/ # SCSS entry points
│ └── js/ # Modern JavaScript
├── gulpfile.js
└── package.json2. The Gulp Pipeline
The beauty of Gulp 4 is task orchestration. We use a series-parallel approach to ensure the build folder is wiped clean before the new assets are streamed in.
const { src, dest, watch, parallel, series } = require('gulp');
const nunjucksRender = require('gulp-nunjucks-render');
const sass = require('gulp-sass')(require('sass'));
const cleanCSS = require('gulp-clean-css');
const del = require('del');
function clean() {
return del(['dist']);
}
function styles() {
return src('src/scss/main.scss')
.pipe(sass().on('error', sass.logError))
.pipe(cleanCSS())
.pipe(dest('dist/css'));
}
function templates() {
return src('src/views/*.{html,njk}')
.pipe(nunjucksRender({
path: ['src/views/_parts'],
data: { siteName: 'KLPGo Static' }
}))
.pipe(dest('dist'));
}
exports.build = series(clean, parallel(styles, templates));3. Nunjucks Macros: The Razor Ancestor
Macros are the secret weapon of this stack. If you've ever worked with Razor View Components or Svelte Components, Nunjucks Macros will feel familiar. They allow you to encapsulate HTML logic into reusable functions.
{# src/views/_parts/macros.njk #}
{% macro card(title, content, type='info') %}
<div class="card shadow-sm border-{{ type }}">
<div class="card-body">
<h5 class="card-title text-capitalize">{{ title }}</h5>
<p class="card-text">{{ content }}</p>
</div>
</div>
{% endmacro %}4. Data Modeling without a Database
Nunjucks allows for sophisticated data manipulation right in the template. This is perfect for generating lists, galleries, or even blog feeds from local arrays.
{% set features = [
{ name: 'Fast', icon: 'zap' },
{ name: 'Reliable', icon: 'shield' },
{ name: 'Static', icon: 'lock' }
] %}
<div class="row">
{% for item in features %}
<div class="col"> {{ item.name }} </div>
{% endfor %}
</div>Conclusion
While the industry pushes toward increasingly complex build tools, the Gulp + Nunjucks workflow remains a 'defensible' choice. It offers the perfect balance of templating power and architectural simplicity, making it my go-to for HTML-first development in 2026.