|

Warum ich Astro für meine nächste Website wählen würde (Teil 1/7)

Also, Folgendes — Hugo läuft auf dieser Seite schon eine Weile, und ehrlich gesagt? Es funktioniert. Schnelle Builds, wenig Aufwand. Aber ich ertappe mich ständig dabei, wie ich mit Go-Templates kämpfe und mir die Flexibilität wünsche, die ich von meiner täglichen TypeScript-Arbeit gewohnt bin.

Ich habe mir Astro angeschaut, und ich glaube, es könnte genau das sein, was ich für einen Neustart brauche. Lass mich dir zeigen, was mich überzeugt hat und wie ich eine Migration von Hugo planen würde.

Was mich an etwas Neuem reizt

Versteh mich nicht falsch — Hugo ist großartig. Aber ein paar Dinge stören mich:

Go-Templates sind… ein Erlebnis. Hast du schon mal {{ with .Params.hero }}{{ if .enable }} sechs Ebenen tief verschachtelt? Das ist nicht schön. Jedes Mal, wenn ich etwas im Layout ändern will, verbringe ich mehr Zeit damit, die Template-Logik zu entschlüsseln, als wirklich etwas zu bauen.

Partials sind keine Komponenten. Hugo hat Partials, klar, aber die fühlen sich nicht wie echte Komponenten an. Keine typisierten Props, keine saubere Komposition. Ich kopiere HTML öfter, als mir lieb ist.

Kein TypeScript. Ich schreibe den ganzen Tag TypeScript auf der Arbeit. Abends zu einer Seite ohne Type Safety zurückzukehren, fühlt sich an wie Autofahren ohne Armaturenbrett.

Warum also Astro?

Drei Dinge haben mich überzeugt:

Standardmäßig kein JavaScript. Astro rendert alles zur Build-Zeit zu HTML. Die Seite lädt ohne Framework-Overhead. Aber wenn du etwas Interaktives brauchst — ein Kontaktformular, einen Theme-Toggle — kannst du nur diese eine Komponente hydrieren:

---
const title = "Hallo vom Server"
---

<h1>{title}</h1>

<!-- nur dieser kleine Kerl liefert JS aus -->
<ContactForm client:load />

Ziemlich cool. Statisch wo möglich, dynamisch nur wo nötig.

Dateibasiertes Routing. Lege eine Datei in src/pages/ ab, fertig, es ist eine Route:

src/pages/
├── index.astro          → /
├── about.astro          → /about
└── blog/
    ├── index.astro      → /blog
    └── [slug].astro     → /blog/:slug

Keine Konfigurationsdatei nötig. Gefällt mir.

Content Collections mit Zod. Astro validiert deine Inhalte mit Schemas. Fehlt ein Titel bei einem Blogbeitrag? Build-Fehler, nicht eine kaputte Seite in Produktion:

// src/content.config.ts
import { defineCollection, z } from 'astro:content'

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    date: z.date(),
    tags: z.array(z.string()),
    draft: z.boolean().default(false)
  })
})

export const collections = { blog }

Type Safety für Inhalte. Ja bitte.

Was ist mit der Build-Geschwindigkeit?

Hugo baut in Millisekunden. Astro wird das nicht schaffen — aber realistisch betrachtet würde eine Seite dieser Größe in vielleicht 3 Sekunden gebaut werden. Bei etwas, das ich ein paar Mal pro Woche deploye, ist mir der Unterschied ehrlich gesagt egal.

Wo Astro tatsächlich gewinnen würde, ist der Output. Keine Framework-Runtime, keine Hydratisierungs-Scripts, nur HTML und CSS. Die Art von PageSpeed-Scores, mit denen man angeben kann.

Wie eine Hugo-Migration aussehen würde

So würde mein aktuelles Hugo-Setup auf Astro abgebildet werden:

HugoAstro
config.toml ParamsConfig + Komponenten-Props
layouts/ Templatessrc/layouts/ + src/components/
content/en/blog/*.mdContentful-Einträge
static/ Assetspublic/
themes/resume/Eigene Komponenten
Go-Template-PartialsAstro-Komponenten mit typisierten Props
languages.en/de ConfigLocale-basierte Routen
docker/DockerfileAktualisiertes Dockerfile (Node statt Hugo)

Das größte Stück sind die Blog-Inhalte — 20+ Markdown-Posts. Die würden nach Contentful wandern, statt als Dateien zu bleiben. Mehr dazu in Teil 2.

Das Hugo-Theme würde komplett wegfallen. Ich würde Komponenten von Grund auf bauen. Mehr Aufwand am Anfang, aber kein Kampf mehr mit den Template-Entscheidungen anderer.

Projekt aufsetzen

Der Einstieg ist denkbar einfach:

npm create astro@latest ./

Wähle “Empty”, baue von Grund auf. Die Struktur:

├── astro.config.mjs
├── package.json
├── public/
│   └── favicon.svg
├── src/
│   ├── layouts/
│   │   └── Base.astro
│   ├── components/
│   ├── pages/
│   │   └── index.astro
│   └── styles/
│       └── global.css
└── tsconfig.json

Und die Konfiguration:

// astro.config.mjs
import { defineConfig } from 'astro/config'

export default defineConfig({
  output: 'static',
  site: 'https://oltionzefi.com'
})

output: 'static' ist eigentlich der Standard, aber ich schreibe Dinge gerne explizit hin.

Der Denkwechsel von Hugo

Wenn du von Hugo kommst, hier der Knackpunkt: Hugo denkt in Templates und Inhaltstypen. Astro denkt in Komponenten und Daten.

Bei Hugo hast du ein Template, das einen Seitenkontext empfängt. Bei Astro holt die Komponente selbst die Daten und rendert HTML — alles in einer Datei:

---
import Layout from '../layouts/Base.astro'
import { getCollection } from 'astro:content'

const posts = await getCollection('blog')
const sorted = posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
---

<Layout title="Blog">
  <ul>
    {sorted.map(post => (
      <li>
        <a href={`/blog/${post.slug}`}>{post.data.title}</a>
        <time>{post.data.date.toLocaleDateString()}</time>
      </li>
    ))}
  </ul>
</Layout>

Keine separaten Template-Dateien, keine Data Cascade zum Debuggen. Einfach TypeScript über dem ----Zaun und Markup darunter.

Was als Nächstes kommt

Das ist eine 7-teilige Serie. Hier der Fahrplan:

  1. Warum Astro (dieser Beitrag)
  2. Contentful einrichten — Content-Modelle, API-Setup, Migration von Markdown
  3. Astro mit Contentful verbinden — Datenabruf, dynamische Routen, Rich-Text-Rendering
  4. Statische Build-Optimierung — Bilder, SEO, Sitemap, RSS
  5. Docker + Nginx Deployment — Multi-Stage Build, Caching, CI/CD
  6. Typsichere Inhalte mit Zod — Contentful-Daten zur Build-Zeit validieren
  7. Secrets absichern — Docker BuildKit Secrets, Tokens aus Image-Layern fernhalten

Wenn du einen ähnlichen Umzug planst, bleib dran. Ich teile jede Entscheidung, jeden Fallstrick und jedes „oh, das ist nice"-Moment.

Weiter voran und genieße jeden Schritt deiner Coding-Reise.