Configuration @ stwoo.net

Gohyde reads _config.yml from the site root. Keys mirror Jekyll's, in
snake_case. A _config.local.yml, if present, is merged on top (last wins) for
local-only overrides that you don't commit.

Core keys

title: My Site
description: A site built with Gohyde.
url: "https://example.com"   # host + protocol — used by {{ site.url }}
baseurl: "/blog"             # sub-path the site is served under
lang: en

source: .                    # default
destination: ./_site         # output dir (some sites use ./_builds)
permalink: pretty            # date | pretty | ordinal | none | custom pattern
markdown: goldmark           # the only processor; kramdown sites work unchanged

url vs baseurl

These are distinct and both matter:

<link rel="canonical" href="{{ site.url }}{{ site.baseurl }}{{ page.url }}" />
<!-- → https://example.com/blog/about/ -->

If url is blank (e.g. commented out for local dev), {{ site.url }} renders
empty and links resolve relative — this is expected, not a bug.

Collections

Collections use list syntax (preferred):

collections:
  - name: projects
    label: Projects
    output: true          # write pages to the site (default true)
    layout: project       # default layout for items
    blueprint: showcase   # optional: share a field schema (see CMS docs)
    src: _projects        # source dir (default _<name>)

The old Jekyll map syntax is still accepted:

collections:
  projects:
    output: true

output: false collections are loaded (available as site.<name>) but not
written to the output directory.

Every collection is exposed two ways in Liquid:

{% for item in site.projects %}{{ item.title }}{% endfor %}

{% for collection in site.collections %}
  {{ collection.label }}: {{ collection.docs | size }} docs
{% endfor %}

site.<name> (e.g. site.projects) is the array of that collection's docs.
site.collections is an array of {label, docs} — one entry per collection
(posts plus every custom collection), matching Jekyll's real shape (not a
bare {name: [...]} map).

The :path permalink token is relative to the collection's own directory and
excludes the source extension: _projects/alpha.mdalpha, not
_projects/alpha.md.

collections:
  - name: projects
    permalink: /work/:path/   # → /work/alpha/, not /work/alpha.md/

Pagination

Jekyll-compatible jekyll-paginate (v1) behavior:

paginate: 5                 # posts per page
paginate_path: /page:num/   # default; /blog/page:num/ paginates blog/index.html

The index.html in paginate_path's directory expands into one page per
chunk, each with a top-level paginator object:

{% for post in paginator.posts %}
  <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
{% endfor %}

{% if paginator.previous_page %}
  <a href="{{ paginator.previous_page_path }}">Newer</a>
{% endif %}
{% if paginator.next_page %}
  <a href="{{ paginator.next_page_path }}">Older</a>
{% endif %}
<span>Page {{ paginator.page }} of {{ paginator.total_pages }}</span>

Page 1 is the index itself; page N lives at the paginate_path URL. As in
Jekyll, previous_page/next_page are nil on the first/last page, so plain
{% if %} guards work.

Per-page pagination: front matter (jekyll-paginate-v2 style) overrides the
globals — any page can opt in, wherever it lives:

---
title: Blog
permalink: /blog/
pagination:
  enabled: true          # false opts a page out of global pagination
  per_page: 6            # overrides site `paginate:`
  permalink: /blog/:num/ # overrides site `paginate_path:`
---

Posts with hidden: true front matter are excluded from pagination chunks and
the page count (they remain in site.posts).

Defaults

Per-path front-matter defaults, exactly like Jekyll:

defaults:
  - scope: { path: "", type: posts }
    values: { layout: post, author: Vedad }

Assets

See Assets for the full pipeline. Key block:

assets:
  digest: false           # content-hash fingerprinting (true in production; always off under `gohyde serve`)
  destination: assets     # output subdir
  sources:                # dirs scanned for assets
    - _assets
    - _assets/css
    - _assets/js
    - assets
  cdn:
    url: "https://cdn.example.com"   # when set, overrides baseurl on asset URLs

Sass

sass:
  sass_dir: _sass
  style: compressed       # or expanded
  source_maps: false

Gohyde uses Dart Sass when a genuine sass/dart-sass binary is on PATH. The
legacy Ruby sass gem is detected and skipped (it can't take --no-source-map);
if no real compiler is found, SCSS passes through unchanged with a stderr
warning — the build never hard-fails.

Tailwind

tailwind:
  enabled: true
  input: assets/css/input.css     # default
  output: assets/css/tailwind.css
  minify: true                    # default; serve always compiles unminified
  bundles:                        # optional: extra css compilations
    - input: _assets/css/admin.css
      output: assets/admin.min.css

Each bundles entry gets its own CLI run — and its own watcher under
gohyde serve; a failing bundle doesn't block the others.

Under gohyde serve, the Tailwind --watch subprocess starts only when
tailwind.enabled: true or the input file exists. Merely having the
tailwindcss CLI on PATH does not start it.

Excludes

exclude:
  - Gemfile
  - node_modules
  - README.md
  - "*.psd"

Environments

JEKYLL_ENV is honored. The convention is to gate production behavior:

assets:
  digest: false        # flip to true on JEKYLL_ENV=production in your build script
JEKYLL_ENV=production gohyde build