notACMS 1.1.0 landed on April 24, 1.1.1 two days later, and 1.1.2 followed about a week on. Three releases that change not what notACMS does, but how you start with it — and what you get out of the box.


notACMS grew out of my own site and for the first release it was one piece: clone the repo, get a full design, start overriding. It worked, but anyone who wanted to build their own look from scratch had to fight against things they didn't need. 1.1.0 fixes this by splitting core from the demo theme.

The core is a skeleton

templates/, assets/, translations/ are now a minimal wireframe. System fonts, light mode, ~200 lines of CSS. All features work — blog, pages, search, RSS, sitemap, responsive images, contact form — but it looks like a site from the 90s. Deliberately. If you're building your own design, you don't fight a theme that imposes a visual language on you.

The demo theme lives in docs/demo/ and is the default seed — ./notACMS deploy or ddev build will lay it down on first run. Pass --bare if you want the skeleton instead:

./notACMS deploy           # amber-phosphor (default), ready to tweak
./notACMS deploy --bare    # skeleton, build your own from scratch

Everything else — the local override pattern, no core editing, clean git pull — works the same regardless of your choice.

What else shipped in 1.1.0

Reading time and reading progress on posts and docs. Language switcher as a Twig extension. Post excerpts that no longer leak # from heading anchors. PHPUnit test scaffolding under tests/. AI-agent skills for working with the repo. An old-template compatibility package at docs/customization/old-template/ — one cp -r and you're back to the 1.0.0 look.

1.1.1 — the patch that real use forced

Preparing this post on holas.pl, deploying to production, I noticed something annoying: ./notACMS deploy --prod backed up and replaced local/ on every run. If you already had content there, it was gone. Deploy couldn't tell "user wants to replace the whole theme" from "user just wants to build their site with existing content."

1.1.1 fixes it so deploy behaves like ddev build: it seeds local/ only when the directory is missing or empty. Content you already have stays untouched. Want to force a re-seed? Pass --bare or --demo explicitly.

The other change is frontmatter-driven navigation labels. Until now every tab in the menu needed a translation key in every locale file — nav.home, nav.about, site.releases and so on. Adding a page meant updating N YAML files. Now menu.label in a page's frontmatter is enough:

---
title: "Architecture guide"
menu:
  label: "Architecture"
  weight: 30
---

A new content_item() Twig function reads it without extra config:

{{ content_item('architecture-guide', 'en').menuLabel() }}

Polish, German, and French demo content got a full review across every page and blog post.

1.1.2 — review-driven hardening

1.1.2 is what happens when you sit down with the code before tagging and look at it with fresh eyes. Two security bugs surfaced during the review and got fixed before release: an open-redirect through path normalisation (Request::getPathInfo() doesn't collapse repeated slashes, so /<default-locale>//evil.com would have produced a Location: //evil.com cross-origin redirect), and an XSS in the search results page where Pagefind's excerpt was injected into innerHTML without escaping.

The headline feature is canonical URLs. If your default locale is en, /en/blog/ and /blog/ were both reachable and rendered the same content — two indexable URLs for one page. A new event listener now issues 301s from /<default-locale>/... to the unprefixed form before Symfony's router even runs.

Internally, the inline |json_encode|raw JSON-LD blocks scattered across templates are gone. A small StructuredDataBuilder service plus two Twig functions (json_ld() and structured_data()) replace them with a fluent, typed API. JSON_THROW_ON_ERROR is on, so a bad UTF-8 byte in frontmatter raises an exception during render instead of silently shipping <script>false</script>. The bare core templates for contact, default, and projects pages now emit Schema.org markup too — bare deploys no longer have weaker SEO than every customisation example.

The demo as living proof

The most satisfying part of this release is not the code — it's what ships in docs/demo/. Not the theme. A complete, four-language site that lives in the repository. It has its own manual, architecture docs, styleguide, and a releases blog — all running, all rendered by the same system you get after git clone.

  • Manual — install, config, content structure, frontmatter, build commands, deploy, environment variables, troubleshooting
  • Architecture — routing, content pipeline, static build, search, multi-language, deployment
  • Styleguide — every component documented with real SCSS tokens
  • Releases blog — posts about every version, rendered by the system itself

"I trust you that it works, but show me" — this is that. The demo is not an example, it's proof. Multi-language routing, static pre-rendering, Pagefind search, responsive images, contact form, RSS, sitemap — everything runs in the demo content. Someone doing a fresh git clone followed by ddev build sees exactly this site.

Links

Full changelog with every change categorised: CHANGELOG.md.

Breaking changes and migration from 1.0.0: UPGRADE-1.1.md.

Repository: GitHub / holas1337/notACMS — Apache 2.0.