# notACMS — Symfony Static Site Generator
php,symfony,static-site,nginx,architecturenotACMS 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.mdin the same directory, automatic translation mapping,hreflangtags, 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 pattern —
local/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.