Features
Dynamic Routes
Dynamic routes allow you to generate multiple static HTML pages from a single template file at build time. By combining bracket syntax in filenames with a route generation script, you can prerender blogs, product catalogs, documentation sets, and localized sites with zero runtime JavaScript overhead.
How It Works
When Bascik processes your src/pages directory, any file containing square brackets in its name, such as src/pages/blog/[slug].html or src/pages/[category]/[id].html, is treated as a dynamic route template.
During the build or development startup:
- Bascik discovers the dynamic route template and extracts all parameter placeholders inside brackets.
- Bascik executes the
<script data-bascik-routes>script inside the template. - The script outputs a JSON list of route objects defining parameters and optional payload data.
- Bascik expands the single template into multiple concrete HTML files, writing them to
dist/and updating the sitemap.
data-bascik-routes
To define the routes to generate, place a <script data-bascik-routes> element inside the template file. The script runs in Node.js at build time and must print a valid JSON array to standard output using console.log().
<script data-bascik-routes>
const posts = [
{ slug: 'hello-world', title: 'Hello World', date: '2026-01-15' },
{ slug: 'second-post', title: 'Second Post', date: '2026-02-01' }
];
const routes = posts.map(post => ({
params: { slug: post.slug },
data: post
}));
console.log(JSON.stringify(routes));
</script> Route Object Format
Each element in the emitted array must be an object with:
params(required): An object whose keys match the bracket names in the template filename. For[slug].html,paramsmust contain{ slug: "..." }. Values are automatically converted to strings.data(optional): Any serializable value (object, array, string, number, or boolean) to pass directly to build scripts without re-fetching.
[
{
"params": { "slug": "getting-started" },
"data": { "title": "Getting Started", "author": "Alice" }
},
{
"params": { "slug": "advanced-patterns" },
"data": { "title": "Advanced Patterns", "author": "Bob" }
}
] Strict validation. Every required bracket parameter in the filename must be present in params. If a bracket name is missing, or if the output is not a valid JSON array, the build halts with a descriptive error.
Accessing Route Data in Build Scripts
Inside your template, <script data-bascik-build> blocks can read the current route parameters and associated data from the BASCIK_ROUTE environment variable.
<!DOCTYPE html>
<html lang="en">
<head>
<script data-bascik-build>
const route = JSON.parse(process.env.BASCIK_ROUTE || '{}');
const { title } = route.data || {};
console.log(`<title>${title || 'Blog'} - My Site</title>`);
</script>
</head>
<body>
<article>
<script data-bascik-build>
const route = JSON.parse(process.env.BASCIK_ROUTE || '{}');
const { params, data } = route;
console.log(`<h1>${data.title}</h1>`);
console.log(`<p class="slug">Slug: ${params.slug}</p>`);
console.log(`<p class="date">Published: ${data.date}</p>`);
</script>
</article>
</body>
</html> Coexistence with Server Scripts
Dynamic route templates can contain both <script data-bascik-build> and <script data-bascik-server> blocks.
- The
<script data-bascik-routes>script expands the template into concrete HTML files at build time (for example,dist/blog/hello-world.htmlanddist/blog/second-post.html). - The
<script data-bascik-server>blocks remain in the generated HTML files and execute per request when served by the Bascik production server. - Note that
<script data-bascik-routes>cannot be combined with<script data-bascik-build>or<script data-bascik-server>on the same script tag.
Cache Invalidation
Bascik caches build script executions to optimize build performance. When compiling dynamic routes, the current BASCIK_ROUTE environment payload is incorporated into the build script cache key.
This ensures that build scripts in the same template re-execute accurately for each unique route without returning stale cached content from preceding routes.
Worker Pool Parallelization
When compiling large sets of dynamic routes, Bascik distributes page rendering jobs across all available worker threads in the worker pool.
Each route job receives its specific parameters and data payload, enabling fast parallel static generation across multi-core CPUs.
Sitemap Integration
Dynamic routes automatically integrate into Bascik's built-in sitemap generator (sitemap.xml and robots.txt).
- Concrete generated paths (such as
/blog/hello-worldand/blog/second-post) are included insitemap.xml. - The template file with literal brackets (like
/blog/[slug]) is never added to the sitemap. - Non-ASCII or special characters in route parameters are percent-encoded according to the XML sitemap standard.
Error Handling and Diagnostics
Bascik validates routes scripts, route parameters, and output destinations during the build:
- Collision Detection: If a dynamic route and a static page resolve to the same output path (e.g.
src/pages/blog/hello.htmlandsrc/pages/blog/[slug].htmlwithslug: "hello"), or if two dynamic templates produce colliding paths, Bascik fails the build with an error naming both sources. - URL-Safe Route Parameters: Route parameter values must be valid filename and URL tokens. Characters such as
#,%,&,',+, spaces, leading dots, and Windows reserved names (CON,PRN,AUX,NUL,COM1-9,LPT1-9) are rejected with descriptive warnings. - Zero Routes: If a routes script returns an empty array
[], Bascik emits a warning indicating the template produced 0 routes. - Missing Parameters: If a template is named
[category]/[id].htmland a route object only provides{ category: "news" }, Bascik throws an error indicating that parameteridwas not supplied. - Invalid Output Format: If the script prints text that is not valid JSON or does not resolve to an array of objects with
params, Bascik throws a descriptive error detailing the received output. - Conflicting Directives: Specifying
data-bascik-routesalongsidedata-bascik-buildordata-bascik-serveron a single script tag is prevented with a validation error.
Common How-to Examples
1. Markdown Blog Posts
Generate blog posts from local Markdown files:
<script data-bascik-routes>
import { readdir, readFile } from 'node:fs/promises';
import { join } from 'node:path';
const files = await readdir('./content/posts');
const posts = [];
for (const file of files) {
if (!file.endsWith('.md')) continue;
const slug = file.replace(/\.md$/, '');
const content = await readFile(join('./content/posts', file), 'utf8');
posts.push({
params: { slug },
data: { content }
});
}
console.log(JSON.stringify(posts));
</script> 2. Product Catalog from an API
Fetch product listings from a CMS or REST API:
<script data-bascik-routes>
const res = await fetch('https://api.example.com/products');
const products = await res.json();
const routes = products.map(product => ({
params: { id: String(product.id) },
data: product
}));
console.log(JSON.stringify(routes));
</script> 3. Multi-Language / i18n Pages
Generate localized pages using multiple parameters in filenames such as src/pages/[lang]/[page].html:
<script data-bascik-routes>
const languages = ['en', 'es', 'fr', 'de'];
const pages = ['about', 'pricing', 'contact'];
const routes = [];
for (const lang of languages) {
for (const page of pages) {
routes.push({
params: { lang, page },
data: { lang, page }
});
}
}
console.log(JSON.stringify(routes));
</script> Next: See Build Scripts to learn more about running Node.js code at transpile time, or explore Production Server for dynamic runtime handling.