notACMS is a Symfony-based static site generator I built in early 2026 to replace 19 years of WordPress on holas.pl. No database, no CMS admin panel, no PHP involved in serving content. The entire site — blog posts, category pages, tag listings, archive months, search, RSS feed, sitemap — is pre-rendered to static HTML files and served by nginx.

The codebase is open-source under Apache 2.0, with 368 PHPUnit tests, CI/CD via GitHub Actions, and a local override pattern that lets users customise templates, CSS, and translations without forking.

Architecture

All content lives in Markdown files with YAML frontmatter:

content/
├── blog/
│   └── tutorials/
│       └── my-post/
│           ├── en.md   ← English version
│           ├── pl.md   ← Polish version
│           └── files/  ← images, served at /media/my-post/
└── pages/
    └── about/
        ├── en.md
        └── pl.md

ContentTreeBuilder scans the filesystem, parses each file with league/commonmark (GitHub Flavoured Markdown + YAML frontmatter), and builds a typed ContentTree — an in-memory index of all posts and pages for a given locale. Tags, categories, archive months, and URL maps are computed from that index.

The static build uses Symfony sub-requests: HttpKernelInterface::handle() with SUB_REQUEST renders every URL through the full kernel without touching the network. If a URL works in development, it will be in the static build. No separate template engine, no build configuration.

Key Features

  • Multi-language — co-located en.md + pl.md in the same directory, automatic translation mapping, hreflang tags, language switcher
  • Search — Pagefind WASM-based static index, client-side, no Elasticsearch or Algolia
  • Responsive images — automatic srcset generation via ImageMagick, configured variant widths
  • Draft & scheduled posts — dev-only preview toggles, scheduled posts automatically published at build time
  • Contact form — Cloudflare Turnstile CAPTCHA, tight CSP, single POST endpoint
  • Styleguide — dev-only page documenting every component with real CSS classes
  • Local override patternlocal/ directory merges on top of base at build time, customise without forking

Tech Stack

  • PHP 8.5, Symfony 7.x
  • Twig templates, SCSS (dart-sass via symfonycasts/sass-bundle)
  • Symfony AssetMapper (no webpack, no Vite, no Node.js build pipeline)
  • nginx + PHP-FPM (two Docker containers in production)
  • Pagefind for search
  • PHPUnit 13, PHPStan level 6, Rector, PHP CS Fixer

Why Build It?

Hugo, Jekyll, and Eleventy were obvious candidates. I chose to build a custom Symfony application because I already know Symfony well, and owning the full stack turned out to have real advantages: the same templates, controllers, and content pipeline serve both development and production. There is no "build-time template engine" separate from the "runtime template engine." It's just Symfony.

The full story of why WordPress had to go is in Why I left WordPress. The architecture deep dive is in Symfony as a Static Site Generator.

Open Source

notACMS is available on GitHub under Apache 2.0. The preparation for release — testing, AI code review, security fixes, the local override pattern — is documented in the open-source series.