Skip to content

treyburn/vanity

Repository files navigation

Vanity Logo

Vanity

An opinionated, minimalist, static site generator CLI for redirecting vanity Go module URLs. Take control of your Go modules by owning your namespace.

Go Reference Version License GHA Build Go Report Card codecov

Why?

Go is unique. Unlike other languages with centralized package registries (npm, PyPI, Crates.io, etc.), Go embraced a fully distributed module system. Your domain is your namespace. Yet GitHub has become the de facto registry, bringing vendor lock-in with it. Vanity fixes that by giving you control on how users go get and go install your code.

Ownership

GitHub doesn't own your code; you do.

Owning your Go module namespace makes you sovereign over your code. This sets you free to switch to any git provider behind the scenes with minimal disruption to your users. No more vendor lock-in.

Simplicity

Vanity generates plain-Jane static HTML pages. No servers, no databases, no runtime dependencies, no BS. Point to your domain, push your pages, and you're done.

Vanity makes decisions so you don't have to. With built-in templates, most users won't need to configure anything beyond their domain and list of modules, but you can customize this further if you bring your own templates.


Installation

You can install vanity directly with your Go toolchain.

go install go.treyburn.dev/vanity@latest

Once installed, you can bootstrap your local config with the just required fields.

vanity init

Usage

Vanity is driven by a single YAML file and a handful of CLI commands. See CLI Commands for available commands and Configuration for setting up your .vanity.yml.

For most users, it's as simple to get going as running vanity init, filling in a few required fields, then running vanity generate. With that you're ready to ship your static pages.

CLI Commands

Most users can use the following commands as a basic quickstart to get their pages generated.

# install Vanity
go install go.treyburn.dev/vanity@latest

# move to the dir you want to store these pages in
cd ~/repo/deployment

# generate your config file
vanity init

# add your domain and module redirect info and save
vi .vanity.yml

# check that everything is configured correct
vanity check

# generate your pages
vanity generate

# run a localhost server with some sample curl calls to validate
vanity serve

# and now you're ready to publish
scp -r ./dist [email protected]

If you want to see the full set of commands and flags available you can simply run vanity --help.

# vanity --help

Usage: vanity <command> [flags]
 A static site generator CLI for Go vanity URLs.

Flags:
  -h, --help                 Show context-sensitive help.
      --log-level=STRING     Override log level (debug, info, warn, error) ($VANITY_LOG_LEVEL).
      --log-format=STRING    Override log format (text, json) ($VANITY_LOG_FORMAT).

Commands:
  init [flags]
    Generate a minimal .vanity.yml config file (use --verbose for full spec).

  generate [flags]
    Generate static HTML files from configuration.

  check [flags]
    Validate the .vanity.yml configuration.

  preview [flags]
    Generate in-memory and serve via local HTTP.

  serve [flags]
    Serve already-generated output directory.

  clean [flags]
    Remove the generated output directory.

  version [flags]
    Print version information.

Help:
  Run "vanity <command> --help" for more information on a command.

Configuration

All configuration is handled via the .vanity.yml YAML file. You can generate a minimal configuration file with just the required fields by running:name: release.

vanity init

Or you can generate a full configuration on all possible fields (ore-filled with example values and comments) by running:

vanity init --verbose

See the Configuration Reference for the full set of all available configurations.

Required fields

The following fields are required to be populated.

# .vanity.yml

# Your vanity URL domain (may include subdomains and url paths)
domain: example.com # REQUIRED
# Module definitions (at least one required)
modules:
  - name: my-module # REQUIRED: import path becomes {domain}/{module.name}
    repo: https://github.com/example/my-module # REQUIRED: full git repository URL

Examples

As you might have guessed, Vanity uses Vanity to provide it's vanity URL redirection.

I've set this up to push to Cloudflare Pages for my go.treyburn.dev domain as an action on release in this repository. It kicks offs an action to update and publish in another repo. You can check out the repository for that here, and I have a small write-up on my blog explaining how it works.


Configuration Reference

# .vanity.yml

# CLI behavior (overridable via --flags and VANITY_* env vars)
log:
  level: info # Options: debug | info | warn | error
  format: text # Options: text (human-friendly) | json (structured)
  color: true # Colorize text output (no effect on json)
# Output settings
output:
  dir: dist # Relative to .vanity.yml location
  clean: true # Remove output dir before generating
  index: true # Generate root index.html listing all modules
  not_found: true # Generate 404.html redirecting to index
  robots: true # Generate robots.txt (permissive, links to sitemap)
  sitemap: true # Generate sitemap.xml listing all module URLs
# Your vanity URL domain (may include subdomains and url paths)
domain: example.com # REQUIRED
# Default values applied to all modules (overridable per-module)
defaults:
  branch: main # Used in go-source meta tag URL templates (defaults to 'main')
  go_source: true # Include go-source meta tag for pkg.go.dev source links
  redirect_root: https://pkg.go.dev # Redirect = redirect_root/domain/name
# Module definitions (at least one required)
modules:
  - name: my-module # REQUIRED: import path becomes {domain}/{module.name}
    repo: https://github.com/example/my-module # REQUIRED: full git repository URL
    branch: main # Override defaults.branch for this module
    go_source: true # Override defaults.go_source for this module
    redirect: https://pkg.go.dev/example.com/my-module # Override browser redirect URL for this module
    local_path: ./my-module # Local checkout path (default: in-memory clone from repo remote if not specified)
    # Subpackage discovery settings (enabled by default in 'auto' mode - set subpackages:mode:off to disable)
    subpackages:
      mode: auto # Options: off | auto | explicit (defaults to 'auto')
      exclude: # Directories to skip in auto mode (defaults to [internal, testdata] in 'auto' mode)
        - internal
        - testdata
      paths: # Allow-list exact subpackage paths (explicit mode only)
        - sub/pkg
# Custom templates and static assets (all paths relative to .vanity.yml)
templates:
  index: templates/index.html # Custom body partial for the root index page
  module: templates/module.html # Custom body partial for module pages
  submodule: templates/submodule.html # Custom body partial for submodule pages (falls back to module)
  not_found: templates/404.html # Custom body partial for the 404 page
  partials: # Reusable template components (referenced via {{template "name" .}})
    - templates/header.html
    - templates/footer.html
  assets: # Static files/dirs copied verbatim into the output directory
    - css/
    - js/app.js

Custom Templates

Vanity generates minimally functional redirect pages by default, but you can customize the look and feel by providing your own HTML template partials. Templates are entirely optional.

How It Works

Vanity owns the <head> section of each page (the go-import and go-source meta tags are critical for go get to work). Your templates can optionally define additional attributes to <head> via {{define "head"}} as well as the <body> via {{define "body"}} blocks that extend the built-in base templates:

<!--example.html-->
{{/* templates/module.html */}}
{{define "head"}}
<link rel="stylesheet" href="/css/style.css">
{{end}}

{{define "body"}}
<div class="module-page">
  <h1>{{upper .Name}}</h1>
  <p>Redirecting to <a href="{{.Redirect}}">documentation</a>...</p>
</div>
{{end}}

The "head" block is injected after Vanity's required meta tags. The "body" block replaces the default body content. Both are optional, and you can omit either one n your template to keep the default behavior.

Template Data

ModuleData -- passed to module and submodule templates

Field Type Description
.Domain string The configured domain (e.g. go.treyburn.dev)
.Name string Module name / import suffix (e.g. vanity or vanity/pkg/cmd)
.ImportPath string Full import path (e.g. go.treyburn.dev/vanity)
.Repo string Git repository URL
.Branch string Git branch
.GoSource bool Whether go-source meta tag is enabled
.Redirect string Redirect URL for documentation
.Subpackages []ModuleData Child subpackage data (only on parent module pages)

SiteData -- passed to index and not_found templates

Field Type Description
.Domain string The configured domain
.Modules []ModuleData All modules with their subpackages

Template Functions

The following functions are available in all templates:

Function Signature Description Example
upper string -> string Uppercase {{upper .Name}}
lower string -> string Lowercase {{lower .Domain}}
title string -> string Title case (English) {{title .Name}}
join []string, string -> string Join with separator {{join .List ", "}}
sprintf string, ...any -> string Formatted string {{sprintf "%s/%s" .Domain .Name}}
now -> time.Time Current datetime in UTC {{now.Format "2006-01-02"}}
year -> int Current year &copy; {{year}}
contains string, string -> bool Substring check {{if contains .Name "api"}}
hasPrefix string, string -> bool Prefix check {{if hasPrefix .Name "pkg"}}
hasSuffix string, string -> bool Suffix check {{if hasSuffix .Repo ".git"}}
replace string, string, string -> string Replace all occurrences {{replace .Name "-" "_"}}
trimSpace string -> string Trim whitespace {{trimSpace .Name}}

now returns a time.Time value, so you can call any method on it -- .Year, .Month, .Format, etc. Go uses a reference-time-based format system (not YYYY-MM-DD). See the Go time package docs for format strings.

Common patterns:

  • {{now.Format "2006-01-02"}} -- ISO date (2026-04-18)
  • {{now.Format "Jan 2, 2006"}} -- human-friendly (Apr 18, 2026)
  • {{now.Format "January 2006"}} -- month and year (April 2026)

Template Composition

You can define reusable components in files listed under templates.partials and reference them via {{template "name" .}}:

{{/* templates/header.html */}}
{{define "header"}}
    <nav>
        <a href="/">{{.Domain}}</a>
    </nav>
{{end}}
{{/* templates/module.html */}}
{{define "body"}}
  {{template "header" .}}
  <main>
      <h1>{{.Name}}</h1>
  </main>
  {{template "footer" .}}
{{end}}

Static Assets

Files and directories listed under templates.assets are copied verbatim into the output directory, preserving their relative path structure. No processing or minification is performed.

templates:
  assets:
    - css/          # css/style.css -> dist/css/style.css
    - js/app.js     # js/app.js -> dist/js/app.js

Validation

The vanity check command validates custom templates when configured:

  • Verifies all referenced template and asset files exist.
  • Parses templates to catch syntax errors.
  • Detects collisions between assets and generated output files.

About

A static site generator for redirecting vanity Go module URLs

Topics

Resources

License

Stars

Watchers

Forks

Contributors