Anpassung
Templates, Styles, PHP-Services und Server-Konfiguration überschreiben — alles aus dem local/-Verzeichnis.
Das local/-Override-System
Jede Anpassung lebt in local/. Dieses Verzeichnis ist deine site-spezifische Schicht. Die Core-templates/, assets/ und src/-Verzeichnisse sind das Framework. Deine Overrides in local/ haben Vorrang.
local/
├── assets/ # SCSS-Overrides + custom JS
│ ├── images/ # og-default.jpg überschreiben
│ └── app.js # als zweiter Entrypoint geladen
├── content/ # all dein Content (Seiten, Blog, Config)
├── docker/ # custom Nginx-Config
│ └── nginx/ # .conf-Snippets (Redirects, Fehlerseiten)
├── docs/ # site-spezifische EDITOR_GUIDE.md + STYLEGUIDE.md
├── src/ # custom PHP-Services (NotACms\Local\ Namespace)
├── templates/ # Template-Overrides
└── translations/ # UI-String-Overrides (messages.*.yaml)
Template-Overrides
Um ein Core-Template zu überschreiben, erstelle eine Datei am gleichen Pfad unter local/templates/:
# Standard-Page-Template überschreiben
cp templates/page/default.html.twig local/templates/page/default.html.twig
# Jetzt local/templates/page/default.html.twig editieren
Der Twig-Loader checkt zuerst local/templates/, dann Fallback auf templates/. Du kannst jedes Template überschreiben, ohne Core-Dateien zu touchieren.
Einzelnen Block überschreiben
Verwende den @base-Namespace, um das Original-Template zu erweitern und nur spezifische Blöcke zu überschreiben:
{# local/templates/blog/post.html.twig #}
{% extends '@base/blog/post.html.twig' %}
{% block sidebar_bottom %}
<div class="my-widget">...</div>
{% endblock %}
Der @base-Namespace zeigt immer auf das originale templates/-Verzeichnis, sodass du die echte Datei erweitern kannst, ohne einen Zirkelbezug zu erzeugen.
Template vollständig ersetzen
Erstelle die Datei am gleichen Pfad ohne extends — sie ersetzt das Original komplett:
{# local/templates/components/navigation.html.twig #}
<nav class="my-nav">
<a href="/">Home</a>
</nav>
Häufige Overrides:
| Core-Template | Zweck |
|---|---|
templates/base.html.twig |
Global Layout, Header, Footer |
templates/components/navigation.html.twig |
Nav-Links |
templates/page/default.html.twig |
Standard-Page-Layout |
templates/page/doc.html.twig |
Dokumentations-Layout |
templates/blog/post.html.twig |
Blog-Post-Layout |
SCSS-Overrides
local/assets/ wird als zweiter Importmap-Entrypoint (app-local) geladen. Erstelle ein local/assets/app.js, das dein SCSS importiert:
// local/assets/app.js
import './styles/app_local.scss';
// local/assets/styles/app_local.scss
// WARNUNG: Die Root-Datei muss app_local.scss heißen — nicht app.scss oder ein anderer Name.
// sass-bundle erfordert eindeutige Basisnamen für alle Root-SCSS-Dateien.
// Core SCSS-Variablen (Farben, Abstände, Typografie) für die Verwendung in Overrides importieren:
@import '../../../../assets/styles/variables';
// CSS Custom Properties für deine Brand überschreiben:
:root {
--accent: #2563EB; // auf blau wechseln
--accent-bg: #EFF6FF;
}
// Custom Component Styles darunter
.my-custom-hero {
background: var(--accent);
color: #fff;
}
Wichtig: Bei Verwendung von
app-localmusst du auchbase.html.twigüberschreiben, um beide Entrypoints zu laden. Erstellelocal/templates/base.html.twig:{# local/templates/base.html.twig #} {% extends '@base/base.html.twig' %} {% block stylesheets %} {{ importmap('app') }} {{ importmap('app-local') }} {% endblock %}Beide Entrypoints an
importmap()zu übergeben garantiert die korrekte CSS-Ladereihenfolge:app.css(Original) zuerst, dann deine Overrides.
Tipp: Überschreibe immer CSS Custom Properties (
--accent,--bg, etc.) statt direkter Hex-Werte. Das stellt sicher, dass Hell- und Dunkel-Modus mit deinen Markenfarben korrekt funktionieren. Die vollständige Token-Liste findest du in der Design-Referenz.
Custom PHP-Services
Erstelle ein Service-Interface in src/Service/, implementiere es, und Symfony autowired es automatisch. Für site-spezifische Erweiterungen platziere Implementierungen in local/src/ mit dem NotACms\Local\-Namespace:
// local/src/Service/MyCustomService.php
namespace NotACms\Local\Service;
use NotACms\Service\Content\ContentServiceInterface;
final class MyCustomService
{
public function __construct(
private readonly ContentServiceInterface $content,
) {}
/**
* @return array<\NotACms\Content\ContentItem>
*/
public function recentPosts(string $locale): array
{
return $this->content->getRecentPosts($locale, 5);
}
}
Inject in einen Controller via Constructor Injection — Symfony's DI-Container erledigt den Rest.
Erweitert: Event-Listener, Twig-Filter, Service-Dekoratoren
Neben einfachen Services kannst du auch Event-Listener, Twig-Filter und Service-Dekoratoren in local/src/ erstellen:
// local/src/EventListener/MyListener.php
namespace NotACms\Local\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener]
final class MyListener
{
public function __invoke(MyEvent $event): void { /* ... */ }
}
// local/src/Twig/MyExtension.php
namespace NotACms\Local\Twig;
use Twig\Attribute\AsTwigFilter;
final class MyExtension
{
#[AsTwigFilter('my_filter')]
public function myFilter(string $value): string { /* ... */ }
}
// local/src/Service/MySiteConfigDecorator.php
namespace NotACms\Local\Service;
use NotACms\Service\SiteConfigServiceInterface;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
#[AsDecorator(decorates: SiteConfigServiceInterface::class)]
final class MySiteConfigDecorator implements SiteConfigServiceInterface
{
public function __construct(private readonly SiteConfigServiceInterface $inner) {}
// nur die Methoden überschreiben, die du brauchst
}
Custom Nginx-Config
Für Produktions-Deployments platziere Nginx-Konfigurations-Snippets in local/docker/nginx/. Jede .conf-Datei in diesem Verzeichnis wird automatisch in den server {}-Block eingebunden.
Beim ersten Deploy seedet der Bootstrap drei Dateien aus docs/demo/docker/nginx/:
redirects.conf— SEO-Redirects (www → non-www, Legacy-URLs)error-pages.conf— locale-aware Fehlerseitencsp.conf— erweiterte Content-Security-Policy für externe Origins des Demo-Themes (Phosphor Icons, Google Fonts)
# local/docker/nginx/redirects.conf — Beispiel
location = /old-page/ { return 301 /new-page/; }
location ~ "^\d{4}/\d{2}/\d{2}/([a-z0-9-]+)/$" { return 301 /blog/$1/; }
Konfiguration kann über mehrere Dateien aufgeteilt werden — alle *.conf-Dateien werden eingebunden. Präfixe mit Nummern, wenn die Reihenfolge wichtig ist (z.B. 10-redirects.conf, 20-cache.conf).
Content-Security-Policy-Override
Das Core-Template emittiert eine bare-theme-sichere CSP über eine $csp-Variable, die vor dem local/docker/nginx/*.conf-Include gesetzt wird, sodass jede Override-Datei sie neu definieren kann. Um die Policy für eigene externe Assets zu erweitern, lege eine Datei wie local/docker/nginx/csp.conf mit einer einzigen set-Direktive an:
# local/docker/nginx/csp.conf — erweitert CSP um externe Origins
set $csp "default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' data: cdn.example.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data:; frame-src 'self'; connect-src 'self';";
Füge kein zweites add_header Content-Security-Policy hinzu — Browser schneiden mehrere CSP-Header (restriktiver, nicht weiter).
Übersetzungs-Overrides
Erstelle local/translations/messages.en.yaml und/oder messages.pl.yaml, messages.de.yaml mit nur den Schlüsseln, die du ändern möchtest. Sie werden über die Core-Übersetzungsdateien gemerged, sodass weggelassene Schlüssel ihren Originalwert behalten.
# local/translations/messages.de.yaml
nav:
blog: "Artikel"
footer:
copyright: "Alle Rechte vorbehalten — Meine Seite"
Du kannst jeden Schlüssel aus translations/messages.*.yaml überschreiben. Verschachtelte Schlüssel verwenden die gleiche eingerückte YAML-Struktur wie die Originaldateien.
OG-Standardbild
Das Fallback-og:image, das angezeigt wird wenn eine Seite kein Beitragsbild hat, verwendet standardmäßig einen gebrandeten Platzhalter. Um es zu ersetzen, platziere dein Bild unter local/assets/images/og-default.jpg. Das Template liest automatisch von diesem Pfad.
Beim ersten ./notACMS deploy oder ddev build seedet der Bootstrap diese Datei aus dem Core-Standard, falls sie noch nicht existiert. Ersetze die geseedete Datei mit deiner eigenen.
Für einen komplett anderen Ansatz (anderes Format, andere Dimensionen oder Logik), überschreibe den {% block og_default_image %}-Block in local/templates/base.html.twig:
{# local/templates/base.html.twig #}
{% extends '@base/base.html.twig' %}
{% block og_default_image %}
<meta property="og:image" content="{{ site_base_url ~ asset('images/og-default.jpg') }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/jpeg">
{% endblock %}
Lokale Docs
local/docs/ enthält zwei site-spezifische Referenzdateien, beide gitignored und beim ersten Deploy geseedt:
local/docs/EDITOR_GUIDE.md — der Schreibleitfaden für deine Site: Kategorien, genehmigte Tags, Stimme und Bildgenerierungsstile. Geseedt aus docs/demo/docs/EDITOR_GUIDE.md. System-Level-Content-Mechaniken (Frontmatter-Felder, URL-Struktur, Serien, Entwürfe) bleiben in docs/EDITOR_GUIDE.md.
local/docs/STYLEGUIDE.md — die Design-Referenz für deine Site: Design-Tokens, Farbpalette, Typografie und Komponentenliste. Geseedt aus docs/demo/docs/STYLEGUIDE.md. Styleguide-Mechaniken (die /styleguide/-Dev-Seite, SCSS-Konventionen) bleiben in docs/STYLEGUIDE.md.
Beide Dateien sind nach dem Seeding frei editierbar — der Bootstrap überschreibt sie nie.
Build erweitern
Füge custom Build-Schritte hinzu, indem du einen Symfony Console Command in local/src/Command/ erstellst:
// local/src/Command/GenerateSitemapCommand.php
namespace NotACms\Local\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
#[AsCommand(name: 'local:sitemap')]
class GenerateSitemapCommand extends Command
{
// ...
}
Dann füge ihn deinem ddev build-Alias hinzu oder rufe explizit auf mit ddev exec bin/console local:sitemap.
Praxisbeispiele
Das Verzeichnis docs/customization/ enthält vollständige, kopierfertige Beispiele. Jedes enthält alle benötigten Dateien und ein README mit exakten cp-Befehlen.
| Beispiel | Muster | Was es demonstriert |
|---|---|---|
custom-footer |
Vollständiger Basis-Ersatz | Benutzerdefinierter Footer — zeigt, wie man den einfachen Core-Footer anpasst |
custom-post-card |
Komponenten-Ersatz + SCSS | Horizontales Karten-Layout — zeigt @base-Extend, app-local-Importmap und Komponenten-Override |
self-hosted-fonts |
Vollständiger Basis-Ersatz + SCSS | Eigene Schriftarten laden — zeigt Preload, @font-face und Ersetzen der System-Schriftarten |
php-service-decorator |
PHP #[AsDecorator] |
Turnstile-Validierung überspringen — zeigt NotACms\Local\-Namespace und Dekorator-Muster |
twig-filter |
PHP #[AsTwigFilter] |
` |
Produktions-Anpassung
Umgebungsvariablen
Erstelle .env.local im Projekt-Root (gitignored):
# Required
APP_SECRET=changeme-generate-new
URL=example.com
# Network
NGINX_PORT=8123
CERTRESOLVER=le # oder 'dummy' zum Testen
# Optional: Kontaktformular
RUNTIME_PHP_ENABLED=true
MAILER_DSN=smtp://user:pass@smtp.example.com:587
TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
Docker Compose Overrides
Erstelle docker-compose.override.yaml für lokale Anpassungen:
services:
nginx:
ports:
- "8080:80" # NGINX_PORT für diese Maschine überschreiben
Security-Header
Zufügen zu local/docker/nginx/redirects.conf:
# Security-Header
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Statische Assets cachen
location ~* \.(css|js|webp|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
SSL / HTTPS mit Traefik
Das Docker Compose hat Traefik-Labels für automatisches SSL.
Requirements:
- Externes Netzwerk:
docker network create web - DNS A-Record zeigt auf Server-IP
- Ports 80/443 offen
.env.local:
URL=yourdomain.com
CERTRESOLVER=le # Let's Encrypt
#CERTRESOLVER=dummy # Self-signed zum Testen
Für custom Traefik-Config, Labels zu docker-compose.override.yaml hinzufügen.
Custom Build-Schritte
Post-Build-Commands hinzufügen durch Editieren von scripts/rebuild-content.sh oder Wrapper erstellen:
#!/bin/bash
# custom-deploy.sh
./notACMS deploy --prod
# Custom: Zu S3 sync, CDN purgen, etc.
aws s3 sync public/static/ s3://my-bucket/ --delete
Datei-Berechtigungen
Korrekte Ownership für Produktion sicherstellen:
# Berechtigungen vor dem Deploy fixen
sudo chown -R $USER:$USER .
chmod 755 local/docker/nginx/