Page-Aware Scripts

Some pages need content that is specific to the current page, such as a canonical URL in the head, an Open Graph image, a structured-data block, or even a page-specific sidebar. Hardcoding those values in every page file works. However, a shared script is easier to maintain. You can change the logic once and every page picks it up automatically.

Bascik makes this possible by injecting three environment variables into every data-bascik-build subprocess:

VariableValue
BASCIK_PAGE_FILEAbsolute path to the HTML file currently being transpiled
BASCIK_PAGES_DIRAbsolute path to the configured pages directory
BASCIK_SITE_URLThe siteUrl from bascik.config.ts

A build script reads these and computes whatever it needs, without the page knowing anything about the logic.

Canonical URL Example

A canonical URL tag tells search engines which URL is the authoritative version of a page. Every docs page on this site uses a shared scripts/canonical.ts that derives the URL from BASCIK_PAGE_FILE:

ts
// scripts/canonical.ts
export async function canonical(): Promise<string> {
  const siteUrl = (process.env.BASCIK_SITE_URL ?? '').replace(/\/$/, '');
  const pageFile = process.env.BASCIK_PAGE_FILE ?? '';
  const pagesDir = process.env.BASCIK_PAGES_DIR ?? '';

  if (!siteUrl || !pageFile || !pagesDir) return '';

  // Convert the absolute file path to a root-relative route
  const relPath = pageFile
    .slice(pagesDir.length)
    .replace(/^[\\/]/, '')
    .replace(/\\/g, '/');

  const withoutExt = relPath.replace(/\.html$/, '');
  // index files map to the parent path
  const route = withoutExt === 'index' ? '' : withoutExt.replace(/\/index$/, '/');
  const urlPath = route ? `/${route}` : '/';

  return `<link rel="canonical" href="${siteUrl}${urlPath}" />`;
}

Use it from any page's <head>:

html
<head>
  <script data-bascik-build>
    import { join } from 'node:path';
    import { pathToFileURL } from 'node:url';
    const { canonical } = await import(
      pathToFileURL(join(process.cwd(), 'scripts/canonical.ts')).href
    );
    console.log(await canonical());
  </script>
</head>

src/pages/getting-started.html emits:

html
<link rel="canonical" href="https://yourdomain.com/getting-started" />

src/pages/internals/architecture.html emits:

html
<link rel="canonical" href="https://yourdomain.com/internals/architecture" />

The script block is identical on every page. Bascik injects a different BASCIK_PAGE_FILE for each, so the output is always specific to that page.

Reading the Page's Own HTML

For richer outputs, including Open Graph tags or JSON-LD structured data, a script can also read the page file itself to extract metadata. BASCIK_PAGE_FILE is an absolute path, so readFile works directly:

ts
// scripts/article-schema.ts (simplified)
import { readFile } from 'node:fs/promises';

export async function articleSchema(): Promise<string> {
  const pageFile = process.env.BASCIK_PAGE_FILE ?? '';
  const pagesDir = process.env.BASCIK_PAGES_DIR ?? '';
  const siteUrl = (process.env.BASCIK_SITE_URL ?? '').replace(/\/$/, '');
  if (!pageFile || !siteUrl) return '';

  // Read the page's own source HTML to extract its title and description
  const html = await readFile(pageFile, 'utf8');
  const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/);
  const descMatch = html.match(/<meta\s+name="description"\s+content="([^"]+)"/);
  if (!titleMatch || !descMatch) return '';

  const headline = titleMatch[1].trim();
  const description = descMatch[1].trim();

  // ... compute url, build schema object, return JSON-LD script tag
}

The script runs on the source HTML before any other build scripts have fired, so <title> and <meta name="description"> are always present as written.

One catch: build order. data-bascik-build scripts on the same page run in document order. If one script generates content that another script should read, such as a title generated by a build script, the second script will see the original source, not the output of the first. For metadata like <title> and <meta name="description"> that are hardcoded in the page, this is never an issue.

The Pattern in Practice

Any tag or content block that should be consistent across every page but derived from per-page values is a good fit for this pattern:

OutputWhat to read from the page
<link rel="canonical">Derived from file path alone, with no file read needed
Open Graph tags<title>, <meta name="description">
TechArticle JSON-LD<title>, <meta name="description">
BreadcrumbList JSON-LDFile path, page <title>
FAQPage JSON-LDThe content Markdown file, not the HTML page

Write the script once in scripts/, add the same 8-line build script block to each page, and every page gets the right output automatically.

Next: Build Scripts covers the full set of env vars, caching, and shared script patterns.