Static Site Generation (SSG)

Build fast static sites with SemaJSX SSG

Static Site Generation

semajsx/ssg provides a powerful static site generator with collections, MDX support, and incremental builds.

Installation

bun add semajsx

Quick Start

Create a build script (build.tsx):

import { createSSG, defineCollection, fileSource, z } from "semajsx/ssg";

// Define a collection
const blog = defineCollection({
  name: "blog",
  source: fileSource({
    directory: "content/blog",
  }),
  schema: z.object({
    title: z.string(),
    date: z.date(),
  }),
});

// Create SSG instance
const ssg = createSSG({
  outDir: "./dist",
  collections: [blog],
  routes: [
    { path: "/", component: HomePage },
    {
      path: "/blog/:slug",
      component: BlogPost,
      getStaticPaths: async (ssg) => {
        const posts = await ssg.getCollection("blog");
        return posts.map((post) => ({
          params: { slug: post.slug },
          props: { post },
        }));
      },
    },
  ],
});

// Build
await ssg.build();

Collections

Collections are type-safe content sources with schema validation:

import { defineCollection, fileSource, z } from "semajsx/ssg";

const docs = defineCollection({
  name: "docs",
  source: fileSource({
    directory: "content/docs",
    include: "**/*.{md,mdx}",
  }),
  schema: z.object({
    title: z.string(),
    description: z.string().optional(),
    order: z.number().default(999),
  }),
});

Data Sources

File Source

Load content from Markdown/MDX files:

import { fileSource } from "semajsx/ssg";

const source = fileSource({
  directory: "content/blog",
  include: "**/*.{md,mdx}",
  watch: true,
});

Git Source

Load data from Git history:

import { gitSource } from "semajsx/ssg";

// From commits
const changelog = gitSource({
  type: "commits",
  filter: { since: "2024-01-01" },
  limit: 100,
});

// From tags
const releases = gitSource({
  type: "tags",
  pattern: "v*",
});

Remote Source

Fetch content from APIs:

import { remoteSource } from "semajsx/ssg";

const posts = remoteSource({
  fetch: async () => {
    const res = await fetch("https://api.example.com/posts");
    return res.json();
  },
  pollInterval: 60000, // Poll every minute in dev
});

Routes

Static Routes

{
  path: "/about",
  component: AboutPage,
  props: { title: "About Us" },
}

Dynamic Routes

{
  path: "/blog/:slug",
  component: BlogPost,
  getStaticPaths: async (ssg) => {
    const posts = await ssg.getCollection("blog");
    return posts.map(post => ({
      params: { slug: post.slug },
      props: { post },
    }));
  },
}

MDX Support

Use MDX with custom components:

const ssg = createSSG({
  outDir: "./dist",
  collections: [blog],
  mdx: {
    components: {
      Callout,
      CodeBlock,
      Counter, // Interactive islands!
    },
  },
});

Incremental Builds

Track build state for faster rebuilds:

import { readFile, writeFile } from "fs/promises";

// Load previous state
let state;
try {
  state = JSON.parse(await readFile(".ssg-state.json", "utf-8"));
} catch {
  state = undefined;
}

// Build incrementally
const result = await ssg.build({
  incremental: true,
  state,
});

// Save state
await writeFile(".ssg-state.json", JSON.stringify(result.state));

console.log(result.stats);
// { added: 2, updated: 1, deleted: 0, unchanged: 47 }
💡Development workflow

Use incremental builds in CI/CD to dramatically reduce build times for large sites.

Watch Mode

Auto-rebuild on content changes:

const watcher = ssg.watch({
  onRebuild: (result) => {
    console.log(`Rebuilt ${result.paths.length} pages`);
  },
});

// Stop watching
process.on("SIGINT", () => {
  watcher.close();
  process.exit();
});

Build Output

SSG generates static HTML with Island architecture:

dist/
├── index.html
├── blog/
│   ├── hello-world/
│   │   └── index.html
│   └── getting-started/
│       └── index.html
├── _islands/          # Interactive island bundles
│   └── counter-xxx.js
└── _assets/           # Static assets

Next Steps