Menu

Sol SSG

Experimental: Sol SSG is under active development. APIs may change.

Sol SSG is Luna's static site generator for documentation and content sites.

Features

  • Markdown-based - Write content in Markdown with frontmatter

  • Syntax Highlighting - Code blocks highlighted with Shiki

  • i18n Support - Multi-language documentation

  • Auto Sidebar - Automatic navigation generation from directory structure

  • Islands Architecture - Embed interactive Luna/MoonBit components

  • Dynamic Routes - Generate pages from _slug_ patterns with staticParams

  • ISR - Incremental Static Regeneration with Stale-While-Revalidate

  • HMR - Fast development with Hot Module Replacement

Guides

  • Dynamic Routes - Generate static pages from parameters

  • Islands - Embed interactive components in static pages

  • Deploy - Deploy to various platforms

  • ISR - Incremental Static Regeneration for dynamic content

  • Components - Reusable UI components (Header, Footer, Blog, Themes)

Quick Start

1. Create a New Project

npx @luna_ui/sol new my-docs --ssg
cd my-docs
npm install

2. Start Development Server

npm run dev

Open http://localhost:3355 to preview your documentation with HMR.

3. Build for Production

npm run build

Static files are generated in dist-docs/.

CLI Reference

Sol SSG is now part of the unified sol CLI. When your project has an sol.config.json or sol.config.json with ssg section, Sol automatically runs in SSG mode.

# Create a new SSG project
sol new <name> --ssg [options]
  -t, --title <text>  Site title (default: project name)
  -h, --help          Show help

# Start development server with HMR
sol dev [options]
  -p, --port <port>    Port to listen on (default: 3355)
  -c, --config <path>  Config file path
  -h, --help           Show help

# Build static site
sol build [options]
  -c, --config <path>  Config file path (default: sol.config.json or sol.config.json)
  -o, --output <dir>   Output directory (overrides config)
  -h, --help           Show help

# Lint SSG content
sol lint [options]
  -c, --config <path>  Config file path

# Show help
sol --help

# Show version
sol --version

Configuration

Create sol.config.json in your project root:

{
  "docs": "docs",
  "output": "dist",
  "title": "My Docs",
  "base": "/",
  "sidebar": "auto"
}

Basic Options

OptionTypeDefaultDescription
docsstring"docs"Source directory
outputstring"dist"Output directory
titlestring"Documentation"Site title
basestring"/"Base URL path
trailingSlashbooleantrueUse trailing slashes in URLs
excludestring[][]Directories to exclude
{
  "nav": [
    { "text": "Guide", "link": "/guide/" },
    { "text": "API", "link": "/api/" },
    { "text": "GitHub", "link": "https://github.com/..." }
  ]
}

Auto Mode

{
  "sidebar": "auto"
}

Generates sidebar from directory structure automatically.

Manual Mode

{
  "sidebar": [
    {
      "text": "Introduction",
      "items": [
        { "text": "Getting Started", "link": "/getting-started/" },
        { "text": "Installation", "link": "/installation/" }
      ]
    },
    {
      "text": "Guide",
      "collapsed": true,
      "items": [
        { "text": "Basics", "link": "/guide/basics" },
        { "text": "Advanced", "link": "/guide/advanced" }
      ]
    }
  ]
}

Internationalization (i18n)

{
  "i18n": {
    "defaultLocale": "en",
    "locales": [
      { "code": "en", "label": "English", "path": "" },
      { "code": "ja", "label": "Japanese", "path": "ja" }
    ]
  }
}

Directory structure for i18n:

docs/
โ”œโ”€โ”€ index.md           # English (default)
โ”œโ”€โ”€ guide/
โ”‚   โ””โ”€โ”€ basics.md
โ””โ”€โ”€ ja/                # Japanese
    โ”œโ”€โ”€ index.md
    โ””โ”€โ”€ guide/
        โ””โ”€โ”€ basics.md

Theme

{
  "theme": {
    "primaryColor": "#6366f1",
    "logo": "/logo.svg",
    "footer": {
      "message": "Released under the MIT License.",
      "copyright": "Copyright 2024 Your Name"
    },
    "socialLinks": [
      { "icon": "github", "link": "https://github.com/..." }
    ]
  }
}

OGP (Open Graph Protocol)

{
  "ogp": {
    "siteUrl": "https://example.com",
    "image": "/og-image.png",
    "twitterHandle": "@yourhandle",
    "twitterCard": "summary_large_image"
  }
}

Content Structure

docs/
โ”œโ”€โ”€ index.md              # Home page (/)
โ”œโ”€โ”€ 00_introduction/      # /introduction/
โ”‚   โ””โ”€โ”€ index.md
โ”œโ”€โ”€ 01_guide/             # /guide/
โ”‚   โ”œโ”€โ”€ index.md
โ”‚   โ”œโ”€โ”€ 01_basics.md      # /guide/basics/
โ”‚   โ””โ”€โ”€ 02_advanced.md    # /guide/advanced/
โ””โ”€โ”€ components/           # Web Components
    โ””โ”€โ”€ my-counter.js

Numeric prefixes (00_, 01_) control ordering but are stripped from URLs.

Markdown Features

Frontmatter

---
title: Page Title
description: Page description for SEO
layout: doc
sidebar: true
---

# Content here

Frontmatter Options

OptionTypeDefaultDescription
titlestring-Page title
descriptionstring-SEO description
layoutstring"doc"Layout type: doc, home
sidebarbooleantrueShow sidebar
imagestring-OGP image (overrides site default)
revalidateint-ISR TTL in seconds (details)

Code Blocks

```typescript
const greeting = "Hello, World!";
```

```moonbit
fn main {
  println("Hello, World!")
}
```

Web Components

Embed interactive Web Components in your static pages.

Creating a Component

Place components in docs/components/:

// docs/components/my-counter.js
export function hydrate(element, state, name) {
  let count = parseInt(element.getAttribute('initial') || '0', 10);

  const render = () => {
    element.innerHTML = `<button>${count}</button>`;
    element.querySelector("button").onclick = () => {
      count++;
      render();
    };
  };

  render();
}

Using in Markdown

<my-counter initial="5" luna:trigger="load"></my-counter>

Trigger Types

TriggerDescription
loadHydrate immediately on page load (default)
idleHydrate when browser is idle
visibleHydrate when element enters viewport
mediaHydrate when media query matches
noneManual hydration only

Full Configuration Example

{
  "docs": "docs",
  "output": "dist",
  "title": "My Documentation",
  "base": "/",
  "trailingSlash": true,
  "exclude": ["internal", "drafts"],
  "i18n": {
    "defaultLocale": "en",
    "locales": [
      { "code": "en", "label": "English", "path": "" },
      { "code": "ja", "label": "Japanese", "path": "ja" }
    ]
  },
  "nav": [
    { "text": "Guide", "link": "/guide/" },
    { "text": "API", "link": "/api/" }
  ],
  "sidebar": "auto",
  "theme": {
    "primaryColor": "#6366f1",
    "logo": "/logo.svg",
    "footer": {
      "message": "Released under the MIT License.",
      "copyright": "Copyright 2024"
    },
    "socialLinks": [
      { "icon": "github", "link": "https://github.com/..." }
    ]
  },
  "ogp": {
    "siteUrl": "https://example.com",
    "image": "/og-image.png"
  }
}