Skip to content

vdsabev/cmless

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cmless

Seamless blogging with GitHub issues.

  • Open GitHub issues in your repo to write posts
  • Drag-and-drop or paste images in the issue to upload them to GitHub
  • Blog post automatically rebuilds when its issue is created or updated
  • Manually manage post publishing by applying labels: state: draft, state: unlisted, state: published
  • Set metadata via Frontmatter
  • Add a link to a page in the header or footer
  • Full-text client-side search (Pagefind) — titles, content, tags, and descriptions

🚀 Get Started

  1. Click "Use this template" → create a new repository
  2. Naming the repo <your-username>.github.io hosts it at https://<your-username>.github.io, naming it <your-repo> hosts it at https://<your-username>.github.io/<your-repo>
  3. In repo Settings → Pages set Source to GitHub Actions.
  4. Create or edit an issue to trigger the first build.

The labels are provided by the "Blog post" issue template. Or create them with the gh CLI:

gh label create "status: draft"    --color "d73a4a" --description "Not published"
gh label create "status: unlisted" --color "5319e7" --description "Published but hidden from list"
gh label create "status: published" --color "0e8a16" --description "Published and listed"

🏷️ Publishing States

Label Listed on homepage? Reachable by direct URL? Best for
status: draft No No Work in progress, only visible on GitHub
status: unlisted No Yes Private links / previews / pages
status: published Yes Yes Public posts

Note

Publishing is only controlled by the status labels which are limited to contributors by default, but if you want to prevent random people from opening issues in your blog repo - limit issue creation to collaborators only in Settings → General.

Tip

When you're done writing and editing a post you can close its respective issue.

✍️ Writing Posts

On GitHub

  1. Issues → New issue.
  2. Choose the "Blog post" template or start blank.
  3. Title = post title. Body = Markdown (drag and drop images straight in, or link external images by URL).
  4. Apply status: published (or unlisted).
  5. Site updates in ~1 minute.

The template documents Frontmatter options.

With the gh CLI

# Draft
gh issue create --title "Hello" --body '---
title: Hello
date: 2026-07-10
description: Short blurb.
author: Name
tags: tech
---
Content here.'

# Publish it
gh issue edit 42 --add-label "status: published"

# Update content
gh issue edit 42 --body 'New markdown...'

Re-applying the status label (or just editing a published/unlisted issue) triggers a rebuild.

Warning

You cannot upload images through the gh CLI.

🛠️ Local Development

git clone https://.../your-repo.git
cd your-repo
npm install # or bun, etc.
npm run dev # run the current folder's repo as a blog
GH_REPO=<username>/<repo> npm run dev # run another cmless-based repo as a blog

🔍 Search

Pagefind indexes published posts and navigation pages (title, description, tags, body). Build writes the index into dist/pagefind/ and copies it to gitignored public/pagefind/ so dev can serve it; re-run build (or pagefind if dist/ is current) after content changes. Assets and result links follow Astro’s base for project-site subpaths.

⚙️ Configuration

Site title is taken from the GitHub repository description.

Author / avatar / profile: auto from the GitHub issue author (or override with author, authorUrl, authorAvatar).

Series: from series: My Series Name Frontmatter. Posts that share the same series name get previous/next navigation and a list of posts in the series, ordered by date (oldest first).

Tags: from tags: a, b Frontmatter.

Navigation pages: useful for custom pages like "About me". Add navigation: Label and navigationIndex: N to an issue's Frontmatter. Negative indices place the link before "Posts" in the header, non-negative after. Footer links are sorted by index and appear before "powered by". These pages render as plain prose without blog metadata.

Custom domain: Place a CNAME file in public/ and set up DNS.

🔄 How It Works

A GitHub Actions workflow runs scripts/generate.ts (via gh) on issue events and pushes. It turns qualifying issues into Markdown files in src/content/blog/. Astro builds a static site that GitHub Pages serves. Nothing about individual posts is stored in the repository.

⬆️ Syncing updates from cmless

Template clones have their own history. To sync:

# first time
git remote add cmless https://github.com/vdsabev/cmless.git
git fetch cmless && git merge cmless/master --allow-unrelated-histories

# thereafter
git fetch cmless && git merge cmless/master

For conflicts on engine files: git checkout --theirs <file>.

Custom README

You can replace cmless's template-oriented README in your own blog repo. To protect it from merges, add this to .gitattributes:

README.md merge=ours

📄 License

MIT — use it for anything.


Made for people who want to write in GitHub Issues and have a fast, reliable blog.

About

cmless - use GitHub as a blog

Resources

Stars

Watchers

Forks

Contributors

Languages