Guide
Publishing to GitHub Pages
You have Markdown in a repository and you want it on the web, without adopting a framework to get there. This page is the whole recipe: one workflow file, two commands, and the three things that are easy to get wrong. The site you are reading is built this way, which is the only reason to trust any of it.
No framework No configuration file Two commands
The shape
Three files and a folder
| Path | What it is |
|---|---|
docs/*.md |
Your pages. Ordinary Markdown, with constructs where you want them. |
.github/workflows/pages.yml |
The workflow below. Renders and deploys on every push to main. |
theme.css |
Optional. Your own stylesheet, layered over the default one. |
Enable Pages once in the repository settings, with GitHub Actions as the source rather than a branch. That is the only thing you do in a browser.
The workflow
Copy this file
name: Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm i -g @markset-lang/cli@0.2
- name: Render
run: |
mkdir -p dist
markset css -o dist/markset.css
for f in docs/*.md; do
markset html "$f" --css markset.css -o "dist/$(basename "$f" .md).html"
done
- uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
@0.2 is what makes the build reproducible: a rebuild next year renders with the version it rendered with today, and changes within v0 are additive only (spec §0), so the pin costs you nothing. Installing globally is what keeps the rest of the recipe short — your repository needs no package.json and no lockfile, because the only tool involved is this one.
The build
Two commands do all the work
-
Write the stylesheet once
markset css -o dist/markset.cssThis is the default stylesheet — tokens, the eight constructs, print rules, and light and dark. Emitting it once and linking it keeps every page small. Skip this step and pass no
--cssat all and each page carries its own copy instead, which is fine for one page and wasteful for forty. -
Render each document
markset html docs/guide.md --css markset.css -o dist/guide.htmlEach output is a complete HTML page:
<html>, a title taken from the first level-one heading, the theme tokens from the document's frontmatter, and the body. There is no template to write. -
Check before you publish, not after
markset check docs/*.mdWorth its own step in the workflow, before the render. It exits non-zero on any error, so a misspelled directive fails the build rather than shipping a page with a fence printed in it.
Three things to get right
The parts that bite
Links are relative
A project site is served from https://you.github.io/repo/, not from the root. Link with guide.html or ../index.html and never with a leading slash: a path beginning at the root leaves your project entirely and lands on the user page, which probably does not exist. This site has a test that fails the build on one, because it is the mistake that looks fine locally and breaks only once published.
Names become URLs
docs/guide.md becomes /guide.html. For a directory-style URL like /guide/, write to dist/guide/index.html instead. Pick one and keep it: changing later breaks every link anyone saved.
Jekyll is not involved
Deploying the artifact through Actions serves your files exactly as built, so no .nojekyll file is needed and a folder beginning with an underscore is safe. That is only true on this path — deploying from a branch still runs Jekyll.
Making it yours
A theme, and diagrams
Your own stylesheet. Pass --theme theme.css and it is linked after the default one, so it can override any token and style any author class you invent. The examples are six documents that differ only in their theme; the source of each names no color and no width.
markset html docs/guide.md --css markset.css \
--theme theme.css -o dist/guide.html
Diagrams cost nothing until they do. An ascii fence in a captioned figure is drawn with no extra setup, in CI as anywhere else. Any other language needs an engine you name, and mermaid's brings a headless browser with it — a large download in every build, so add it deliberately or not at all. See diagrams.
Honest limits
When you outgrow this
This recipe has no navigation, no index page generated from the others, and no search. That is not an oversight: Markset is a document format, not a site generator, and the moment you want those you are writing a generator, however small.
Two reasonable directions, neither of which is Markset's business:
-
Write the twenty lines
A loop that collects each document's first heading and writes an index is short, and you already have the parse result —
parseDocumentreturns an AST that any unified tool can walk. That is how this site's own generator started. -
Keep the generator you have
If you already run Eleventy, Hugo, Astro or anything else, do not replace it. Render each page to a fragment with
--fragmentand hand it to your existing layout, or lower the constructs away entirely withmarkset downgrade. Those routes are on the adoption page.
This site is built by site/build.ts in the Markset repository, which is the twenty lines above grown up: it adds navigation, a table of contents, generated reference pages and per-page themes. Read it if you want the next step after this page, but do not read it as the minimum — the minimum is the workflow above.