element.version === currentVersion)) { versionNodes.unshift({ version: currentVersion, url: "#" }); } document.querySelector("#project-version").innerHTML = versionNodes.reduce( (acc, element) => { const status = currentVersion === element.version ? "selected disabled" : ""; return ` ${acc} `; }, `
gssg · v1.0.0

gssg

Package Version Hex Docs

Batteries-not-included, templating library agnostic SSG library for Gleam.

Features

  • Simple API: Just four functions to build a complete static site
  • Type-safe: Leverages Gleam’s type system for compile-time guarantees
  • Flexible: Works with any page type (Lustre elements, strings, custom types)
  • Dynamic routes: Generate multiple pages from data (perfect for blog posts)
  • Zero config: No configuration files needed, just code

Installation

gleam add gssg

Quick Start

import gleam/dict
import gleam/io
import gleam/string
import lustre/element
import gssg/ssg

pub fn main() -> Nil {
  let result =
    ssg.new("priv", element.to_document_string)
    |> ssg.add_static_route("/", home_view())
    |> ssg.add_static_route("/about", about_view())
    |> ssg.build()

  case result {
    Ok(_) -> io.println("Build successful!")
    Error(e) -> io.println("Build failed: " <> string.inspect(e))
  }
}

fn home_view() {
  element.html([], [
    element.head([], [element.title([], "Home")]),
    element.body([], [element.h1([], [element.text("Welcome!")])]),
  ])
}

fn about_view() {
  element.html([], [
    element.head([], [element.title([], "About")]),
    element.body([], [element.h1([], [element.text("About Us")])]),
  ])
}

This generates:

  • priv/index.html - Your home page
  • priv/about/index.html - Your about page

Usage

Creating a site

Start by creating an SSG instance with an output directory and a render function:

import lustre/element
import gssg/ssg

let site = ssg.new("output", element.to_document_string)

The render function converts your page type to HTML strings. For Lustre elements, use element.to_document_string.

Adding static routes

Add individual pages with add_static_route:

ssg.new("priv", element.to_document_string)
|> ssg.add_static_route("/", home.view())
|> ssg.add_static_route("/projects", projects.view())
|> ssg.add_static_route("/contact", contact.view())

Each route generates a file at {path}/index.html.

Adding dynamic routes

Generate multiple pages from a collection of data:

import gleam/dict
import markup/markup

pub fn main() -> Nil {
  // Parse blog posts from markdown files
  let assert Ok(posts) = markup.parse_dir("data/posts")

  ssg.new("priv", element.to_document_string)
  |> ssg.add_static_route("/", home.view())
  |> ssg.add_static_route("/blog", blog_index.view(posts))
  |> ssg.add_dynamic_route("/blog", dict.from_list(posts), blog_post.view)
  |> ssg.build()
}

If posts contains:

  • #("hello-world", post1)
  • #("getting-started", post2)

This generates:

  • priv/blog/hello-world/index.html
  • priv/blog/getting-started/index.html

Building the site

Call build() to render and write all files:

let result = ssg.build(site)

case result {
  Ok(_) -> io.println("Build successful!")
  Error(ssg.CannotWriteFile(path, reason)) -> {
    io.println("Failed to write: " <> path)
  }
}

Complete Example

Here’s a complete example with static pages and a blog:

import gleam/dict
import gleam/io
import gleam/string
import lustre/element
import markup/markup
import pages/home
import pages/writing/post
import pages/writing/writing
import gssg/ssg

pub fn main() -> Nil {
  let assert Ok(posts) = markup.parse_dir("data/posts")

  let result =
    ssg.new("priv", element.to_document_string)
    |> ssg.add_static_route("/", home.view())
    |> ssg.add_static_route("/projects", markup.from("data/pages/projects.dj"))
    |> ssg.add_static_route("/writing", writing.view(posts))
    |> ssg.add_dynamic_route("/writing", dict.from_list(posts), post.view)
    |> ssg.build()

  case result {
    Ok(_) -> io.println("build successful.")
    Error(e) -> io.println("build failed: " <> string.inspect(e))
  }
}

License

This project is licensed under the Apache-2.0 License.

Search Document