Knowledge

Freezed Tutorial 1 Die erste Website

In diesem Tutorial bauen wir mit Freezed eine kleine Website von Grund auf: eine Startseite, zwei Unterseiten und ein Menü im Header. Als Beispiel dient die fiktive „Rösterei West“, eine Kaffeerösterei in Bochum.

Dabei lernen Sie,

  • wie ein Freezed-Projekt mit dem Skeleton entsteht,
  • wo die wichtigsten Einstellungen in freezed.config.php liegen,
  • wie ein Theme aus Layout, Partials und CSS aufgebaut ist,
  • wie Seiten aus einem Fluid-Template und einer variables.php entstehen,
  • wie Seiten mit freezed:link verlinkt werden.

Das Theme bleibt bewusst klein. Es kommt mit purem CSS aus, ohne JavaScript und ohne Build-Tool. Das Stylesheet liegt schon fertig vor, die Fluid-Templates bauen wir Schritt für Schritt gemeinsam.

Voraussetzungen

  • PHP 8.2 oder neuer
  • Composer
  • Ein Terminal und ein Editor

Zuerst prüfen wir kurz die PHP-Version:

Terminal
php -v

Schritt 1

Projekt anlegen

Den Einstieg macht das freezed-skeleton, ein Starter-Projekt mit gerade mal einer composer.json, einem Docker-Setup und einer .gitignore. Alles Weitere erzeugt Freezed beim Installieren.

Freezed ist noch Beta, deshalb braucht Composer beim Anlegen das Flag --stability=beta:

Terminal
composer create-project --stability=beta neuedaten/freezed-skeleton my-site
cd my-site

Composer lädt die Engine neuedaten/freezed und führt danach automatisch freezed install aus. Dieser Befehl legt die Projektordner an und kopiert ein Standard-Theme samt drei Beispielseiten hinein:

my-site/
  • composer.json
  • freezed.config.php Konfiguration der Website
  • content Inhalte, gruppiert nach Content-Type
    • pages
      • home
      • features
      • about
  • themes Themes (Layouts, Partials, CSS)
    • 00_default
  • static Dateien, die unverändert nach public/ kopiert werden
  • public Build-Ergebnis (wird bei jedem Build neu erzeugt)
  • vendor Composer-Abhängigkeiten, darunter die Freezed-Engine

Wir bauen die Seite einmal, um zu sehen, dass alles funktioniert. Freezed meldet den Build in einer Zeile:

Terminal
./vendor/bin/freezed build
Built 3 pages (3 files, 2 resources) in 14 ms

Das Ergebnis liegt in public/. Zum Anschauen starten wir am besten den Entwicklungsmodus: Er baut die Seite, startet einen lokalen Webserver und baut bei jeder Änderung neu.

Terminal
./vendor/bin/freezed run

Wir öffnen http://localhost:8080 und sehen das Standard-Theme mit den Beispielseiten:

Browserfenster mit dem Standard-Theme von Freezed nach der Installation: Kopfzeile mit Navigation, Startseite mit Überschrift und Beispieltext
Das Standard-Theme nach der Installation, mit den drei Beispielseiten Home, Features und About.

Schritt 2

Aufräumen und Theme-Struktur anlegen

Das Standard-Theme taugt gut zum Abgucken, für dieses Tutorial starten wir aber lieber bei null. Wir löschen die Beispielseiten und das Standard-Theme:

Terminal
rm -rf content/pages/* themes/00_default

Danach legen wir die Ordnerstruktur für das eigene Theme an. Es heißt simple:

Terminal
mkdir -p themes/simple/assets/css
mkdir -p themes/simple/templates/layouts
mkdir -p themes/simple/templates/partials
mkdir -p themes/simple/templates/templates

Ein Theme besteht aus drei Teilen. Freezed sucht sie an festen Pfaden:

themes/simple/
  • assets
    • css
      • style.css Stylesheet, per resource-ViewHelper eingebunden
  • templates
    • layouts Seitengerüst, z. B. page.html
    • partials Wiederverwendbare Bausteine wie Header und Footer
    • templates Fallback-Template für Seiten ohne eigenes index.html

Das Stylesheet

Das CSS ist fertig, wir müssen es im Tutorial nicht mehr anfassen. Wir legen es unter themes/simple/assets/css/style.css ab. Gestaltet werden drei Bereiche: der Header mit Seitenname und Navigation, der Inhaltsbereich und der Footer.

themes/simple/assets/css/style.css
  1. /* =========================================================================
  2. Theme "simple"
  3. Ein bewusst kleines Stylesheet: Header, Inhaltsbereich, Footer.
  4. Kein Build-Tool, kein JavaScript.
  5. ========================================================================= */
  6. /* ----- Grundwerte -------------------------------------------------------- */
  7. :root {
  8. --color-bg: #fbf9f6;
  9. --color-surface: #ffffff;
  10. --color-text: #2b2521;
  11. --color-text-soft: #6f655d;
  12. --color-accent: #b5542d;
  13. --color-accent-dark: #8f3f1f;
  14. --color-line: #e6ded5;
  15. --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
  16. Helvetica, Arial, sans-serif;
  17. --width-content: 44rem;
  18. --width-page: 64rem;
  19. }
  20. *,
  21. *::before,
  22. *::after {
  23. box-sizing: border-box;
  24. }
  25. html {
  26. -webkit-text-size-adjust: 100%;
  27. }
  28. body {
  29. margin: 0;
  30. min-height: 100vh;
  31. display: flex;
  32. flex-direction: column;
  33. font-family: var(--font-sans);
  34. font-size: 1.0625rem;
  35. line-height: 1.65;
  36. color: var(--color-text);
  37. background: var(--color-bg);
  38. }
  39. a {
  40. color: var(--color-accent);
  41. text-decoration: none;
  42. }
  43. a:hover {
  44. color: var(--color-accent-dark);
  45. text-decoration: underline;
  46. }
  47. .container {
  48. width: 100%;
  49. max-width: var(--width-page);
  50. margin-inline: auto;
  51. padding-inline: 1.5rem;
  52. }
  53. /* ----- Header ------------------------------------------------------------ */
  54. .site-header {
  55. background: var(--color-surface);
  56. border-bottom: 1px solid var(--color-line);
  57. }
  58. .site-header .container {
  59. display: flex;
  60. align-items: center;
  61. justify-content: space-between;
  62. gap: 2rem;
  63. min-height: 4.5rem;
  64. }
  65. .site-header__brand {
  66. font-size: 1.25rem;
  67. font-weight: 700;
  68. letter-spacing: -0.01em;
  69. color: var(--color-text);
  70. }
  71. .site-header__brand:hover {
  72. color: var(--color-accent);
  73. text-decoration: none;
  74. }
  75. /* ----- Navigation -------------------------------------------------------- */
  76. .site-nav {
  77. display: flex;
  78. gap: 1.5rem;
  79. }
  80. .site-nav a {
  81. color: var(--color-text-soft);
  82. font-weight: 500;
  83. }
  84. .site-nav a:hover {
  85. color: var(--color-accent);
  86. text-decoration: none;
  87. }
  88. /* ----- Inhalt ------------------------------------------------------------ */
  89. .content {
  90. flex: 1;
  91. padding-block: 3.5rem 5rem;
  92. }
  93. .content .container {
  94. max-width: var(--width-content);
  95. }
  96. .content h1 {
  97. font-size: 2.5rem;
  98. line-height: 1.15;
  99. letter-spacing: -0.02em;
  100. margin: 0 0 1.25rem;
  101. }
  102. .content h2 {
  103. font-size: 1.5rem;
  104. line-height: 1.3;
  105. margin: 2.5rem 0 0.75rem;
  106. }
  107. .content p {
  108. margin: 0 0 1.25rem;
  109. }
  110. .content .lead {
  111. font-size: 1.25rem;
  112. color: var(--color-text-soft);
  113. }
  114. .content ul,
  115. .content ol {
  116. margin: 0 0 1.25rem;
  117. padding-left: 1.4rem;
  118. }
  119. .content li {
  120. margin-bottom: 0.4rem;
  121. }
  122. /* ----- Footer ------------------------------------------------------------ */
  123. .site-footer {
  124. background: var(--color-surface);
  125. border-top: 1px solid var(--color-line);
  126. padding-block: 2rem;
  127. font-size: 0.9375rem;
  128. color: var(--color-text-soft);
  129. }
  130. .site-footer p {
  131. margin: 0;
  132. }
  133. /* ----- Kleine Bildschirme ------------------------------------------------ */
  134. @media (max-width: 640px) {
  135. .site-header .container {
  136. flex-direction: column;
  137. align-items: flex-start;
  138. gap: 0.75rem;
  139. padding-block: 1rem;
  140. }
  141. .site-nav {
  142. flex-wrap: wrap;
  143. gap: 1rem;
  144. }
  145. .content h1 {
  146. font-size: 2rem;
  147. }
  148. }

Schritt 3

Die Website konfigurieren

Alle Einstellungen liegen in freezed.config.php im Projektstamm. Die Datei gibt ein PHP-Array zurück. Wir ersetzen den Inhalt durch diese Fassung:

freezed.config.php
  1. <?php
  2. return [
  3. // Öffentliche Basis-URL der Website, ohne Slash am Ende.
  4. // Wird für absolute URLs in der Sitemap benötigt.
  5. 'siteUrl' => 'https://roesterei-west.example',
  6. // Bei jedem Build eine public/sitemap.xml erzeugen.
  7. 'sitemap' => [
  8. 'enabled' => true,
  9. 'lastmod' => null,
  10. ],
  11. // Variablen, die in jedem Template zur Verfügung stehen.
  12. // Eine Seite kann sie in ihrer variables.php überschreiben.
  13. 'variables' => [
  14. 'siteName' => 'Rösterei West',
  15. 'siteLanguage' => 'de',
  16. 'currentYear' => date('Y'),
  17. 'pageTitle' => 'Rösterei West',
  18. 'pageDescription' => 'Kleine Kaffeerösterei in Bochum. Handgeröstet, direkt gehandelt.',
  19. ],
  20. 'contentTypes' => [
  21. 'pages' => [
  22. 'targetDirectory' => '',
  23. 'targetFileExtension' => 'html',
  24. ],
  25. ],
  26. ];

Die drei Blöcke im Überblick:

  • siteUrl und sitemap: Die Basis-URL braucht Freezed nur für absolute Links, zum Beispiel in der Sitemap. Mit enabled => true schreibt jeder Build eine public/sitemap.xml.
  • variables: Diese Werte stehen in jedem Template als {siteName}, {pageTitle} und so weiter zur Verfügung. pageTitle und pageDescription sind hier Standardwerte, die jede Seite in ihrer eigenen variables.php überschreibt.
  • contentTypes: Jeder Ordner direkt unter content/ ist ein Content-Type und braucht hier einen Eintrag. pages wird in den Stamm von public/ gebaut, mit der Endung .html.

Schritt 4

Seitentemplate mit Header und Footer

Jetzt entsteht das Gerüst, in dem später jede Seite steckt. Es besteht aus einem Layout und zwei Partials.

Das Layout

Das Layout ist das HTML-Grundgerüst. Wir legen themes/simple/templates/layouts/page.html an:

themes/simple/templates/layouts/page.html
  1. <!doctype html>
  2. <html lang="{siteLanguage}">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>{pageTitle} · {siteName}</title>
  7. <meta name="description" content="{pageDescription}">
  8. <link rel="stylesheet" href="{freezed:resource(path: 'assets/css/style.css', context: 'theme')}">
  9. </head>
  10. <body>
  11. <f:render partial="header" arguments="{_all}" />
  12. <main class="content">
  13. <div class="container">
  14. <f:render section="content" />
  15. </div>
  16. </main>
  17. <f:render partial="footer" arguments="{_all}" />
  18. </body>
  19. </html>

Drei Dinge passieren hier:

1{freezed:resource(...)} bindet das Stylesheet ein. Der ViewHelper kopiert die Datei beim Build nach public/ und gibt den öffentlichen Pfad zurück. Mit context: 'theme' sucht er die Datei relativ zum Theme-Ordner.

2<f:render partial="header" arguments="{_all}" /> rendert das Partial partials/header.html und reicht ihm alle Variablen weiter, also auch siteName.

3<f:render section="content" /> ist der Platzhalter für den Seiteninhalt. Jede Seite füllt diese Section in ihrem eigenen Template.

Header und Footer als Partials

Header und Footer sind auf jeder Seite gleich, deshalb landen sie in eigenen Partials. Der Header zeigt vorerst nur den Seitennamen als Link zur Startseite. Wir legen themes/simple/templates/partials/header.html an:

themes/simple/templates/partials/header.html
  1. <header class="site-header">
  2. <div class="container">
  3. <a class="site-header__brand" href="/">{siteName}</a>
  4. </div>
  5. </header>

Der Footer nutzt die Variablen currentYear und siteName aus der Konfiguration. Wir legen themes/simple/templates/partials/footer.html an:

themes/simple/templates/partials/footer.html
  1. <footer class="site-footer">
  2. <div class="container">
  3. <p>&copy; {currentYear} {siteName}</p>
  4. </div>
  5. </footer>

Das Fallback-Template

Zum Schluss bekommt das Theme ein Fallback-Template unter themes/simple/templates/templates/index.html. Freezed nutzt es für jede Seite, die kein eigenes index.html mitbringt:

themes/simple/templates/templates/index.html
  1. <f:layout name="page" />
  2. <f:section name="content">
  3. <h1>{pageTitle}</h1>
  4. <p class="lead">{pageDescription}</p>
  5. </f:section>

<f:layout name="page" /> wählt das Layout layouts/page.html. Die Section content liefert den Inhalt, den das Layout an der Stelle von <f:render section="content" /> einsetzt.

Erste Seite zum Testen

Um das Gerüst zu sehen, braucht es mindestens eine Seite. Wir legen content/pages/home/variables.php an:

content/pages/home/variables.php
  1. <?php
  2. return [
  3. 'pageTitle' => 'Startseite',
  4. 'targetFileName' => 'index.html',
  5. ];

Der Ordnername bestimmt normalerweise den Dateinamen der gebauten Seite, aus home/ würde home.html. Mit targetFileName legen wir stattdessen index.html fest, damit die Seite unter / erreichbar ist.

Das Theme sieht jetzt so aus:

themes/simple/
  • assets
    • css
      • style.css
  • templates
    • layouts
      • page.html
    • partials
      • header.html
      • footer.html
    • templates
      • index.html

Wir bauen die Seite:

Terminal
./vendor/bin/freezed build
Built 1 page (1 file, 1 resource, sitemap) in 18 ms

In public/ liegen jetzt die Startseite, das Stylesheet und die Sitemap:

public/
  • index.html
  • sitemap.xml
  • simple
    • assets
      • css
        • style.css

Im Browser sehen wir jetzt Header, Footer und dazwischen den Inhalt aus dem Fallback-Template. Viel ist das noch nicht, aber das Gerüst steht. Überschrift und Beschreibung kommen aus variables.php und freezed.config.php:

Browserfenster mit dem neuen Theme: Header mit dem Seitennamen Rösterei West, darunter die Überschrift Startseite und die Beschreibung aus der Konfiguration, unten der Footer mit Copyright
Das Seitengerüst mit Header und Footer. Der Inhalt kommt noch aus dem Fallback-Template.

Schritt 5

Inhalt auf der Startseite

Eine Seite ist ein Ordner mit zwei Dateien: variables.php für die Daten und index.html für das Fluid-Template. Zuerst ergänzen wir content/pages/home/variables.php um zwei eigene Variablen:

content/pages/home/variables.php
  1. <?php
  2. return [
  3. 'pageTitle' => 'Startseite',
  4. 'targetFileName' => 'index.html',
  5. 'headline' => 'Kaffee, der nach Bochum schmeckt.',
  6. 'intro' => 'Wir rösten in kleinen Chargen, mitten im Ruhrgebiet. Jede Bohne kennen wir beim Namen, jeden Bauern per Handschlag.',
  7. ];

Danach legen wir das Template content/pages/home/index.html an. Es wählt das Layout page und füllt die Section content:

content/pages/home/index.html
  1. <f:layout name="page" />
  2. <f:section name="content">
  3. <h1>{headline}</h1>
  4. <p class="lead">{intro}</p>
  5. <p>
  6. Seit 2019 rösten wir in einer alten Werkstatt in Bochum-Ehrenfeld.
  7. Unsere Bohnen kaufen wir direkt bei Kooperativen in Kolumbien, Äthiopien
  8. und Guatemala, ohne Zwischenhändler.
  9. </p>
  10. <h2>Was uns wichtig ist</h2>
  11. <ul>
  12. <li>Kleine Chargen von maximal zwölf Kilo pro Röstung</li>
  13. <li>Faire Preise, die wir direkt mit den Bauern aushandeln</li>
  14. <li>Frisch verschickt, spätestens drei Tage nach der Röstung</li>
  15. </ul>
  16. </f:section>

Sobald ein index.html neben der variables.php liegt, hat es Vorrang vor dem Fallback-Template des Themes. Überschrift und Einleitung kommen aus den Variablen, der restliche Text steht direkt im Template.

content/pages/
  • home
    • index.html
    • variables.php

Wir bauen neu und öffnen die Startseite:

Terminal
./vendor/bin/freezed build
Browserfenster mit der Startseite der Rösterei West: Überschrift Kaffee, der nach Bochum schmeckt, Einleitung, ein Absatz und die Liste Was uns wichtig ist
Die Startseite mit Inhalt. Überschrift und Einleitung kommen aus der variables.php, der Rest aus dem Template.

Schritt 6

Zwei Unterseiten

Unterseiten funktionieren genauso wie die Startseite: ein Ordner, eine variables.php, ein index.html. Der Ordnername wird zum Dateinamen, aus ueber-uns/ wird ueber-uns.html.

Über uns

Wir legen content/pages/ueber-uns/variables.php an:

content/pages/ueber-uns/variables.php
  1. <?php
  2. return [
  3. 'pageTitle' => 'Über uns',
  4. 'pageDescription' => 'Wer hinter der Rösterei West steckt und wie wir arbeiten.',
  5. ];

Und das Template content/pages/ueber-uns/index.html:

content/pages/ueber-uns/index.html
  1. <f:layout name="page" />
  2. <f:section name="content">
  3. <h1>{pageTitle}</h1>
  4. <p class="lead">
  5. Zwei Leute, ein Trommelröster und eine Werkstatt in Bochum-Ehrenfeld.
  6. </p>
  7. <p>
  8. Angefangen hat alles mit einem gebrauchten Röster aus den Niederlanden
  9. und einer Garage. Heute rösten wir dreimal pro Woche und beliefern
  10. Cafés, Büros und Privatkunden im ganzen Ruhrgebiet.
  11. </p>
  12. <h2>So arbeiten wir</h2>
  13. <p>
  14. Jede Sorte bekommt ihr eigenes Röstprofil. Wir rösten eher hell, damit
  15. die Herkunft der Bohne im Vordergrund steht und nicht der Röstgrad.
  16. </p>
  17. <h2>Besuch in der Rösterei</h2>
  18. <p>
  19. Freitags von 14 bis 18 Uhr ist die Werkstatt geöffnet. Kommt vorbei,
  20. probiert die aktuellen Röstungen und schaut uns über die Schulter.
  21. </p>
  22. </f:section>

Kontakt

Die Kontaktseite zeigt, dass Variablen auch verschachtelt sein können. Wir legen content/pages/kontakt/variables.php an:

content/pages/kontakt/variables.php
  1. <?php
  2. return [
  3. 'pageTitle' => 'Kontakt',
  4. 'pageDescription' => 'Adresse, Öffnungszeiten und Kontaktdaten der Rösterei West.',
  5. 'address' => [
  6. 'street' => 'Alte Werkstatt 12',
  7. 'zip' => '44789',
  8. 'city' => 'Bochum',
  9. ],
  10. 'email' => 'hallo@roesterei-west.example',
  11. 'phone' => '0234 123456',
  12. ];

Im Template content/pages/kontakt/index.html greifen wir mit Punktnotation auf die verschachtelten Werte zu, zum Beispiel {address.city}:

content/pages/kontakt/index.html
  1. <f:layout name="page" />
  2. <f:section name="content">
  3. <h1>{pageTitle}</h1>
  4. <p class="lead">
  5. Fragen zu einer Röstung, eine Bestellung für das Büro oder einfach
  6. Lust auf einen Besuch? Schreibt uns oder kommt vorbei.
  7. </p>
  8. <h2>Adresse</h2>
  9. <p>
  10. {siteName}<br>
  11. {address.street}<br>
  12. {address.zip} {address.city}
  13. </p>
  14. <h2>Erreichbarkeit</h2>
  15. <p>
  16. E-Mail: <a href="mailto:{email}">{email}</a><br>
  17. Telefon: {phone}
  18. </p>
  19. <h2>Öffnungszeiten</h2>
  20. <p>Freitags von 14 bis 18 Uhr, oder nach Absprache.</p>
  21. </f:section>

Der Content-Ordner enthält jetzt drei Seiten:

content/pages/
  • home
    • index.html
    • variables.php
  • ueber-uns
    • index.html
    • variables.php
  • kontakt
    • index.html
    • variables.php

Wir bauen neu:

Terminal
./vendor/bin/freezed build
Built 3 pages (3 files, 1 resource, sitemap) in 8 ms

Die Unterseiten liegen als public/ueber-uns.html und public/kontakt.html und sind unter http://localhost:8080/ueber-uns.html und http://localhost:8080/kontakt.html erreichbar:

Browserfenster mit der Unterseite Über uns: Überschrift, Einleitung und die Abschnitte So arbeiten wir und Besuch in der Rösterei
Die Unterseite „Über uns“.
Browserfenster mit der Unterseite Kontakt: Adresse, E-Mail und Telefon aus den verschachtelten Variablen sowie die Öffnungszeiten
Die Unterseite „Kontakt“ mit den verschachtelten Variablen für die Adresse.

Von einer Seite zur anderen kommen wir bisher nur über die Adresszeile. Das ändert der letzte Schritt.

Schritt 7

Navigation im Header

Die Menüpunkte definieren wir als Variable in der Konfiguration, gerendert werden sie im Header-Partial. So stehen sie auf jeder Seite zur Verfügung, ohne dass ein Seitentemplate davon wissen muss.

Menüpunkte in der Konfiguration

Wir ergänzen in freezed.config.php innerhalb von variables den Eintrag navigation:

freezed.config.php
  1. 'variables' => [
  2. 'siteName' => 'Rösterei West',
  3. 'siteLanguage' => 'de',
  4. 'currentYear' => date('Y'),
  5. 'pageTitle' => 'Rösterei West',
  6. 'pageDescription' => 'Kleine Kaffeerösterei in Bochum. Handgeröstet, direkt gehandelt.',
  7. // Hauptnavigation. CONTENT:<contentType>/<ordner> wird beim Build
  8. // in die öffentliche URL der jeweiligen Seite aufgelöst.
  9. 'navigation' => [
  10. ['label' => 'Start', 'href' => 'CONTENT:pages/home'],
  11. ['label' => 'Über uns', 'href' => 'CONTENT:pages/ueber-uns'],
  12. ['label' => 'Kontakt', 'href' => 'CONTENT:pages/kontakt'],
  13. ],
  14. ],

Statt einer festen URL steht in href eine Content-Referenz: CONTENT:pages/ueber-uns meint den Ordner ueber-uns im Content-Type pages. Freezed löst die Referenz beim Build in die tatsächliche URL auf. Benennen wir später eine Datei um oder machen aus einer Seite ein Verzeichnis, bleibt der Link intakt.

Menü im Header-Partial

Wir erweitern themes/simple/templates/partials/header.html um eine <nav>. Eine f:for-Schleife läuft über die Menüpunkte, der ViewHelper freezed:link erzeugt daraus die Links:

themes/simple/templates/partials/header.html
  1. <header class="site-header">
  2. <div class="container">
  3. <a class="site-header__brand" href="/">{siteName}</a>
  4. <nav class="site-nav" aria-label="Hauptnavigation">
  5. <f:for each="{navigation}" as="item">
  6. <freezed:link href="{item.href}">{item.label}</freezed:link>
  7. </f:for>
  8. </nav>
  9. </div>
  10. </header>

freezed:link rendert ein normales <a>-Tag. Zeigt eine Referenz auf eine Seite, die es nicht gibt, erzeugt Freezed statt des Links ein <span class="dead-link"> und gibt beim Build eine Warnung aus. So fallen Tippfehler sofort auf.

Wir bauen ein letztes Mal:

Terminal
./vendor/bin/freezed build

Im gebauten HTML sind die Referenzen zu echten Pfaden geworden:

Gebautes HTML
<nav class="site-nav" aria-label="Hauptnavigation">
    <a href="/">Start</a>
    <a href="/ueber-uns.html">Über uns</a>
    <a href="/kontakt.html">Kontakt</a>
</nav>

Das Menü erscheint rechts im Header, auf der Startseite wie auf jeder Unterseite:

Browserfenster mit der Startseite der Rösterei West, im Header rechts die Navigation mit Start, Über uns und Kontakt
Die Startseite mit Navigation im Header.
Browserfenster mit der Unterseite Über uns, im Header rechts dieselbe Navigation mit Start, Über uns und Kontakt
Dieselbe Navigation auf der Unterseite „Über uns“.

Ergebnis

Die fertige Website besteht aus wenigen Dateien. Alles, was wir selbst angelegt haben, liegt in content/, themes/ und freezed.config.php:

my-site/
  • freezed.config.php
  • content
    • pages
      • home
        • index.html
        • variables.php
      • ueber-uns
        • index.html
        • variables.php
      • kontakt
        • index.html
        • variables.php
  • themes
    • simple
      • assets
        • css
          • style.css
      • templates
        • layouts
          • page.html
        • partials
          • header.html
          • footer.html
        • templates
          • index.html
  • public Build-Ergebnis
    • index.html
    • ueber-uns.html
    • kontakt.html
    • sitemap.xml
    • simple/assets/css/style.css

Zum Veröffentlichen kopieren wir den Inhalt von public/ auf einen beliebigen Webserver oder Static-Host. Mehr braucht es nicht, die Seite besteht nur aus HTML und CSS.

Wie es weitergeht

  • Themes stapeln: Mehrere Themes in themes/ überlagern sich. Ein zweites Theme kann einzelne Partials des ersten überschreiben, ohne es zu kopieren.
  • Partials mit Argumenten: Bausteine wie ein Hero-Bereich oder eine Kartenliste lassen sich mit <f:render partial="..." arguments="{...}" /> aus Variablen befüllen.
  • Weitere Content-Types: Ein Ordner content/posts/ plus ein Eintrag in contentTypes ergibt zum Beispiel einen Blog. Mit freezed:contentTypeCollection lassen sich dessen Einträge auf einer Übersichtsseite.
  • Bilder: Der ViewHelper freezed:image skaliert und konvertiert Bilder beim Build.