Recipes
Templating Recipes
Bascik does not ship a template language or a custom server-side object model. The core stays small: static HTML, scoped CSS, and plain Node.js scripts at build time or request time. If you want loops, partials, or layout composition, use an existing template library or a tiny project helper file.
This page shows the patterns Bascik recommends: explicit recipes, not framework magic.
Why Bascik stays out of the template layer
The goal is to keep the runtime boring and predictable.
- Bascik compiles HTML and CSS, not a second app framework
data-bascik-serverruns plain Node.js ESM withprocess.env.BASCIK_REQUEST- rendering helpers stay in your app code where they belong
- escaping and HTML composition remain explicit, not hidden behind globals
If a project needs a template engine, use one that already solves the problem well. Bascik is the static-site engine; the template library is an app choice.
Recipe 1: plain JS template literals
For many pages, the simplest approach is still ordinary JavaScript string templates.
<script data-bascik-server>
const { headers } = JSON.parse(process.env.BASCIK_REQUEST);
const user = String(headers['x-display-name'] ?? 'Guest')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
console.log(`
<section class="welcome">
<h2>Hello, ${user}</h2>
<ul>
<li>Overview</li>
<li>Reports</li>
<li>Settings</li>
</ul>
</section>
`);
</script>This is often enough for dashboards, landing pages, and one-off server-rendered sections. It is explicit, familiar, and easy to reason about.
Recipe 2: a tiny shared HTML helper
When the same escaping or list-rendering logic appears repeatedly, keep it in a small helper module and import it.
// lib/html.mjs
export const escapeHtml = (value) => String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
export const renderList = (items) =>
`<ul>${items.map(item => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`;<script data-bascik-server>
import { escapeHtml, renderList } from './lib/html.mjs';
const { searchParams } = JSON.parse(process.env.BASCIK_REQUEST);
const items = (searchParams.tags ?? 'news,updates').split(',');
const safeTags = items.map(v => escapeHtml(v.trim())).filter(Boolean);
console.log(`
<section>
<h2>Topics</h2>
${renderList(safeTags)}
</section>
`);
</script>This is the same philosophy as the server-script rule: keep the runtime small, but let your app own the reusable helpers it wants.
Recipe 3: EJS for loops and includes
If the project needs more structure, use a template library like EJS for layout fragments and repeated markup.
npm install ejs<script data-bascik-server>
import { readFile } from 'node:fs/promises';
import ejs from 'ejs';
const rows = [
{ title: 'First post', href: '/posts/first' },
{ title: 'Second post', href: '/posts/second' },
];
const template = await readFile('./templates/post-list.ejs', 'utf8');
console.log(ejs.render(template, { rows }));
</script><!-- templates/post-list.ejs -->
<ul class="post-list">
<% rows.forEach((row) => { %>
<li><a href="<%= row.href %>"><%= row.title %></a></li>
<% }); %>
</ul>This works well when the HTML is large, repetitive, or needs layout-like partials. Bascik still stays out of the way because the template engine is just a dependency in the app layer.
Recipe 4: Nunjucks for richer template composition
Nunjucks is a good fit for pages that want includes, layout blocks, and more opinionated template syntax without turning Bascik into a framework.
npm install nunjucks<script data-bascik-server>
import nunjucks from 'nunjucks';
const html = nunjucks.render('./templates/page.njk', {
title: 'Projects',
items: ['Alpha', 'Bravo', 'Charlie'],
});
console.log(html);
</script>{# templates/page.njk #}
<section>
<h2>{{ title }}</h2>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</section>Nunjucks is useful when the site has a lot of repetitive HTML and a real template structure. Bascik still remains the static compiler; the template library just renders fragments into ordinary HTML before they are injected.
Recipe 5: Handlebars
Handlebars is a good choice when the team prefers a logic-less template syntax and wants helpers registered separately from template files.
npm install handlebars<script data-bascik-server>
import { readFile } from 'node:fs/promises';
import Handlebars from 'handlebars';
const { searchParams } = JSON.parse(process.env.BASCIK_REQUEST);
const page = Math.max(1, Number(searchParams.page ?? 1));
const src = await readFile('./templates/article-list.hbs', 'utf8');
const template = Handlebars.compile(src);
const items = [
{ title: 'First article', href: '/posts/first' },
{ title: 'Second article', href: '/posts/second' },
];
console.log(template({ items, page }));
</script>{{! templates/article-list.hbs }}
<section>
<h2>Articles — page {{page}}</h2>
<ul>
{{#each items}}
<li><a href="{{href}}">{{title}}</a></li>
{{/each}}
</ul>
</section>Handlebars HTML-escapes {{value}} expressions by default. Use the triple-stache {{{value}}} only when you have already sanitized the value yourself.
Keep the boundary explicit
The best rule is simple:
- do not add a template language to the core
- do not add a custom request/session/cookie object to the runtime
- do not hide escaping or HTML assembly behind globals
- do document recipes for the common patterns people actually reach for
If a project wants a template language, it should be an explicit dependency and an explicit choice. That keeps Bascik focused on the job it actually does best: compiling static HTML, CSS, and JS without becoming a framework.