logo

Set sail with Saga,
a static site generator in Swift

Type-safe, extensible, and fast. Build your site with Swift's compile-time safety, from metadata to markup.

Up and running in seconds

Install the CLI, scaffold a project, and start a dev server with auto-reload. Your site is a Swift program, ready to build and serve.

Homebrew brew install loopwerk/tap/saga
Mint mint install loopwerk/saga-cli
$ saga init mysite
$ cd mysite
$ saga dev

Built for developers who love Swift

Everything you need to build fast, type-safe static sites.

Type-Safe Metadata

Define custom metadata types with full compile-time safety. Catch errors before they reach production.

Reader → Writer Pipeline

A clean, extensible architecture with pluggable readers, processors, and writers.

Multiple Content Types

Articles, apps, pages: each with their own metadata type, readers, and rendering pipeline.

Dev Server

Run saga dev for a local server with file watching and automatic browser reload.

Powerful Writers

Item, list, tag, and year writers, Atom feed generation, and cache-busting hashed static assets, all built in.

Code over Configuration

No YAML, no magic. Your site is a Swift program: explicit, readable, debuggable.

Everything is Swift, everything is typed

Define your entire pipeline in Swift. From a simple blog...

struct ArticleMetadata: Metadata {
  let tags: [String]
  let summary: String
  var public: Bool = true
}

try await Saga(input: "articles", output: "deploy")
  .register(
    metadata: ArticleMetadata.self,
    readers: [.parsleyMarkdownReader],
    writers: [
      .itemWriter(swim(renderArticle)),
      .listWriter(swim(renderArticles), paginate: 20),
      .tagWriter(swim(renderTag), tags: \.metadata.tags),
      .listWriter(atomFeed(
        title: "My Blog", 
        baseURL: URL(string: "https://www.example.com")
      ), output: "feed.xml"),
    ]
  )
  .run()

...to a complex documentation site with API references, syntax highlighting, and HTML minification.

let saga = try Saga(input: "content", output: "deploy")

try await saga
  // Guide documentation (from DocC markdown files)
  .register(
    folder: "docs",
    metadata: DocMetadata.self,
    readers: [.parsleyMarkdownReader],
    itemProcessor: sequence(
      processDocItem,
      syntaxHighlight,
      swiftSoupProcessor(processExternalLinks, renderToc)
    ),
    sorting: docSorting,
    writers: [.itemWriter(swim(renderDocPage))]
  )
  
  // API Reference (from symbol graph)
  .register(
    metadata: APIMetadata.self,
    fetch: { try loadSymbolGraph(rootPath: saga.rootPath) },
    writers: [
      .itemWriter(swim(renderAPIPage)),
      .listWriter(swim(renderAPIIndex), output: "api/index.html"),
    ]
  )
  
  // Landing page
  .createPage("index.html", using: swim(renderHomePage))
  
  // Minify all HTML output (prod only)
  .postProcess { html, _ in
    guard !Saga.isDev else { return html }
    return Bonsai.minifyHTML(html)
  }
  
  // Run everything!
  .run()

Grows with your site

From a simple blog to a complex multi-content site, Saga scales with you.

Typed metadata

A single site can include blog articles with tags, a project portfolio with App Store links, movie reviews with ratings. Each with their own strongly typed metadata, indexed, paginated, or grouped independently.

Programmatic content

Not all content lives on disk. Fetch items from APIs, databases, or any async data source and feed them through the same writer pipeline. File-based and programmatic steps can be freely mixed.

Your build, your rules

Register custom processing steps for anything beyond the standard pipeline: generate images, build a search index, or minify HTML. If Swift can do it, your build can too.

Modular by design

Compose your site with readers, renderers, and plugins that fit your needs.

Markdown readers

Renderers

Utilities

Tap into an ecosystem of Swift packages

Use these packages directly in your build pipeline for syntax highlighting, HTML transforms, and more.