Astro Cactus ships the boring parts of a blog so you only write posts
A simple Astro theme. Use it to create your blog or website.
At a glance
- What is it?
- An opinionated Astro starter for a personal blog or portfolio, with search, RSS, webmentions, generated OG images and auto-generated sitemap already wired up. The cost of that convenience is that several paths are hardcoded and version 8 tracks Astro 7 closely.
- Who is it for?
- Adopt Astro Cactus if your site is a personal blog or a small portfolio and you would rather not assemble search, RSS and social cards yourself, since those are wired end to end and the dev server starts on localhost:3000 with one command. Do not adopt it if you need a multi-author publication, an i18n matrix or a CMS: the repository is a single blog with two content directories and no i18n layer described.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 31 days ago.
- What is it written in?
- Mainly Astro, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two content directories, not a blog engine
Astro Cactus calls itself a simple opinionated starter, and the opinion is visible in the directory layout. Posts live in src/content/post/ and notes live in src/content/note/, each holding plain .md or .mdx files. Tags are files too, added under src/content/tag/ with the file name matching the tag, so a tag page is a file rather than a dynamic route.
Content is organised with Astro's Content Collections, which means frontmatter is type-checked against a schema rather than being trusted. The README names that schema file, which is the piece to look at first when you add a field.
There is a small inconsistency worth knowing about. The commands table describes pnpm sync as generating types based on your config in src/content/config.ts, while the content collections paragraph points at src/content.config.ts. The repository root carries a content/ directory as well as src/, so the location is worth confirming in your own checkout rather than assuming from the documentation.
MDX is supported, and admonitions are called out as a feature with a live example on the demo site.
Quick start is a create-astro invocation, not a clone
The documented path is to generate a new repository from the template rather than clone it into place. That matters because it gives you a clean history with no upstream commits, which is what you want for a site that is yours rather than a fork.
npm create astro@latest -- --template chrismwilliams/astro-theme-cactusThe pnpm equivalent uses create-astro directly through dlx.
pnpm dlx create-astro --template chrismwilliams/astro-theme-cactusThere is also a generate link on the template itself, plus one-click deploy buttons for Netlify and Vercel. The demo site is hosted on Netlify, which tells you what the author expects the deployment story to be.
The comment above the npm command says npm 7+, which sets the floor for the package manager that will run it.
The command table shows where the build is split
Six commands cover the whole lifecycle, and one of them exists only because search is built separately from the site.
pnpm install
pnpm dev
pnpm build
pnpm postbuild
pnpm syncpnpm dev starts the local dev server at localhost:3000. pnpm build produces the production site in ./dist/. Then pnpm postbuild runs the Pagefind script that builds the static search index over the blog posts. pnpm sync regenerates the content types from the config file.
That postbuild step is the one to internalise. Search is not part of astro build; it is a separate indexing pass over the output directory, which is why the README warns to run it after building. Skipping it produces a site that deploys fine and has a search box that finds nothing.
The README notes you can replace pnpm with npm or yarn throughout. The package.json scripts themselves are plain astro and pagefind invocations, so nothing is pnpm-specific.
Open Graph images are rendered by Satori, and you can override them
Social card generation is the most opinionated feature here, and the most likely thing to want to change. Satori renders the og:image as a PNG from HTML and Tailwind classes at build time.
Two escape hatches exist. To restyle the generated image, open src/pages/og-image/_ogMarkup.ts and edit the markup function, with the linked Satori playground offered as a design aid. To bypass Satori entirely for a given post, add an ogImage property in that post's frontmatter pointing at your own asset; the README points at src/content/post/testing/social-image.md as a worked example.
The static fallback is in the public folder. social-card.png is the default og:image, and icon.svg is the source for favicons and the manifest icons.
Satori is pinned at 0.33.4 in package.json rather than a caret range, which is unusual in a template and hints that the author found the floating versions unstable.
Search, robots.txt and manifest are three separate packages
The auto-generated files are worth naming because each is a dependency you inherit. The sitemap comes from Astro's own sitemap integration. robots.txt comes from astro-robots-txt, and the web app manifest from astro-webmanifest.
Search is Pagefind, integrated through @pagefind/component-ui and @pagefind/default-ui. The component UI is a dependency, while the default UI is a dev dependency.
Maintenance implication: because robots.txt and the manifest come from a third-party repository rather than Astro, they are pinned to versions the theme author chose. When you upgrade Astro past what this release supports, those two are the likeliest to need attention, and neither is covered by the template's own CI.
Configuration is one file for most of it, and the url field is mandatory
Almost everything is configured in src/site.config.ts, and the README flags one property as important: set the url property with your own domain. That value feeds canonical URLs, the sitemap, RSS and the web manifest, so leaving it pointing at the template's own domain produces a site that ranks for someone else.
Beyond that file, the changes are specific and few. astro.config.ts holds the webmanifest options. src/styles/global.css carries the light and dark styles alongside Tailwind's theme settings. Social links live in src/components/SocialList.astro, edited per profile, with icons pulled from icones.js.org via Astro Icon's instructions.
Code block theming is configured in the site config too. Expressive Code provides it with two themes already set, dracula for dark and github-light for light, and more are available in that project's theme documentation.
Fonts are the one optional item called out by name: the body element is set to the font-mono family in src/layouts/Base.astro, and removing that variant lets Tailwind fall back to its font-sans stack.
Content collections mean a bad frontmatter field fails the build
Frontmatter is where most of the friction lives, and the type-checking is the point rather than an inconvenience. Posts, notes and tags each have their own frontmatter section in the README, with snippets for the common cases. Because the schema is validated at build time, a mistyped field or a malformed date stops astro build rather than rendering an empty page in production.
Webmentions are wired in, and the README points to a post on the demo site explaining how to add them. Tags need a matching file in src/content/tag/, which means tag pages are authored content rather than generated, so a typo in the file name quietly produces a tag with no posts.
That manual step is the trade for having no database. For a personal blog it is a small price; for a site with an editor, it is the point at which the template stops being a good fit.
Version 8 tracks Astro closely, and the tooling is modern
The theme is on Astro v7 with Tailwind v4. package.json specifies astro 7.3.2 and the 4.3.3 Tailwind CSS range. A pnpm-lock.yaml and pnpm-workspace.yaml at the root, plus the README's note that you may substitute npm or yarn, mean the project develops on pnpm while remaining usable with either.
Releases are frequent: v8.3.0 on 2026-09-18, v8.2.0 on 2026-07-14, v8.1.0 on 2026-07-08. The last push was on 2026-09-22. The version in package.json matches the latest tag, so the repository root and the release history are in step.
Linting and formatting are split between Biome and Prettier, with a biome.json at the root and.prettierrc.js alongside prettier-plugin-astro and prettier-plugin-tailwindcss. The check script runs astro check and biome check together; lint writes fixes with biome check --write; format uses prettier with a cache. Three tools for text and code in one small repository is a mild inconsistency worth resolving in your own fork.
There is a postinstall that runs npm rebuild sharp --force. The licence is MIT.
Editorial conclusion
Adopt Astro Cactus if your site is a personal blog or a small portfolio and you would rather not assemble search, RSS and social cards yourself, since those are wired end to end and the dev server starts on localhost:3000 with one command. Do not adopt it if you need a multi-author publication, an i18n matrix or a CMS: the repository is a single blog with two content directories and no i18n layer described. Verify first that pnpm sync succeeds against your own content config, since the README names src/content/config.ts while the content collection section names src/content.config.ts, and confirm the satori version is still compatible with your Node version after the 8.x releases.
Frequently asked questions
How do I create a site from the Astro Cactus template?
Run npm create astro@latest -- --template chrismwilliams/astro-theme-cactus, or with pnpm use pnpm dlx create-astro --template chrismwilliams/astro-theme-cactus. You can also generate a new repository from the template on GitHub, or deploy with the Netlify or Vercel buttons.
Why does search not work after building Astro Cactus?
Search is built by a separate Pagefind pass over the output directory, not by astro build. Run pnpm postbuild after pnpm build; package.json defines postbuild as pagefind --site dist.
Which config file must I edit before deploying Astro Cactus?
Edit src/site.config.ts, and set the url property to your own domain. The README marks that as important, because the value feeds canonical URLs, the sitemap, RSS and the web manifest.
Can I use my own social image instead of the generated one?
Yes. Add an ogImage property in a post's frontmatter pointing at your own asset, which skips Satori for that post, or edit the markup function in src/pages/og-image/_ogMarkup.ts to restyle the generated image.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/chrismwilliams-astro-theme-cactus)Was this useful?