Blog

Jamstack als Architekturstil für Webanwendungen

Jamstack beschreibt einen Architekturstil mit vorgerendertem Frontend und entkoppelten Diensten. Der Beitrag ordnet die ursprünglichen Bausteine JavaScript, APIs und Markup im Stand von 2021 ein.

≈ 9 Min. Lesezeit

Diesen Beitrag anhören (13 Min.)

MP3 herunterladen

Jamstack lässt sich nicht per npm install in ein Projekt holen, und kaufen kann man ihn auch nirgends. Es ist ein Architekturstil für Webanwendungen, der Vorrendering und die Entkopplung von Oberfläche und Diensten betont.

Deshalb fällt die Entscheidung auch eine Ebene höher als die Wahl des nächsten Frameworks. Genau dort setzt Jamstack an.

Der Name wurde ursprünglich aus JavaScript, APIs und Markup gebildet. Geprägt haben ihn Matt Biilmann und Chris Bach bei Netlify; Biilmann machte ihn 2015/2016 öffentlich bekannt. Schon 2021 wurde Jamstack breiter als das Akronym verwendet. Geblieben sind zwei Prinzipien: Vorrendering und Entkopplung. Statt bei jeder Anfrage frisch HTML auf dem Server zu erzeugen, wird Markup vorab gebaut und über ein CDN ausgeliefert; dynamische Teile greifen getrennt auf APIs zu.

Der Paradigmenwechsel: klassisches SSR gegen Jamstack

Um zu verstehen, warum das relevant ist, lohnt sich der Blick auf das klassische Modell. Bei serverseitigem Rendering im traditionellen Sinn passiert bei jedem Seitenaufruf dasselbe: Der Browser schickt einen Request, ein Applikationsserver nimmt ihn entgegen, fragt eine Datenbank ab, baut daraus HTML zusammen und schickt es zurück. Jeder einzelne Besucher löst diese Kette erneut aus. Das ist bewährt, aber es bindet Serverkapazität pro Anfrage und macht den App-Server zum Nadelöhr und zur Angriffsfläche zugleich.

Der Jamstack dreht diese Reihenfolge um. Die Arbeit passiert nicht mehr pro Request, sondern einmalig zur Build-Zeit. Ein Static-Site-Generator zieht Inhalte aus Datenquellen, rendert daraus fertige HTML-Seiten und legt sie als statische Dateien ab. Diese Dateien landen auf einem CDN und werden von dort global, nah am Nutzer, ausgeliefert. Was dynamisch sein muss – ein Warenkorb, ein Login, eine Suche – wird nachträglich clientseitig über APIs nachgeladen.

flowchart LR
  subgraph Classic["Klassisches SSR"]
    B1["Browser"] -->|"Request"| S1["App-Server"]
    S1 --> DB[("Datenbank")]
    DB --> S1
    S1 -->|"HTML pro Request"| B1
  end
  subgraph Jam["Jamstack"]
    B2["Browser"] -->|"statische Seite"| CDN["CDN<br/>(pre-rendered)"]
    B2 -->|"Dynamik zur Laufzeit"| API["APIs /<br/>Serverless"]
  end

Eine Klarstellung, die in Diskussionen regelmäßig untergeht: Der Jamstack verbietet serverseitiges Rendering nicht. Er verlagert den Standardfall. Statt „jede Seite pro Request auf dem Server“ heißt es „jede Seite einmal im Build“ – und dort, wo es wirklich nötig ist, darf ich serverseitiges Rendering weiterhin gezielt einsetzen. Genau darum geht es im nächsten Abschnitt.

Static-Site-Generatoren und Hybrid-Rendering

Der Motor des Jamstack ist der Static-Site-Generator. Im einfachsten Fall ist das ein Prozess, der Inhalte einliest und daraus HTML schreibt. Ein bewusst reduziertes Beispiel macht das Prinzip greifbar – Markdown rein, HTML raus:

import { loadMarkdown, renderToHtml, writeFile } from "./ssg";

for (const post of loadMarkdown("content/**/*.md")) {
  const html = renderToHtml(post);
  writeFile(`dist/${post.slug}/index.html`, html);
}

In der Praxis übernehmen das ausgereifte Werkzeuge. Gatsby etwa ist ein React-basierter Generator mit einem GraphQL-Data-Layer und einem großen Plugin-Ökosystem. Next.js – aktuell in Version 12, erschienen Ende Oktober 2021 mit einem neuen, deutlich schnelleren Rust-Compiler auf SWC-Basis – bietet mehrere Rendering-Modi nebeneinander an. Und SvelteKit, das sich seit dem Frühjahr 2021 in einer öffentlichen Beta befindet, geht denselben Weg über ein Adapter-Konzept. SvelteKit ist bewusst noch als jung einzuordnen; es ist pre-1.0 und im Beta-Stadium, aber die Richtung ist bereits klar erkennbar.

Der entscheidende Punkt moderner Generatoren ist, dass ich das Rendering nicht global festlege, sondern pro Seite oder Route entscheide. Am Beispiel von Next.js lässt sich das gut sortieren:

  • SSG (Static Site Generation) über getStaticProps und getStaticPaths rendert die Seite im Build; danach ist sie reines Markup vom CDN.
  • SSR (Server-Side Rendering) über getServerSideProps erzeugt die Seite pro Request auf dem Server, für echt dynamische Inhalte.
  • ISR (Incremental Static Regeneration), seit Version 9.5 aus dem Jahr 2020 verfügbar, baut eine statische Seite zeit- oder ereignisgesteuert im Hintergrund neu.
  • CSR (Client-Side Rendering) über Hooks und Datenbibliotheken wie SWR lädt die Daten erst im Browser nach.

Ein typisches SSG-Muster mit einem Hauch ISR sieht so aus:

export async function getStaticProps() {
  const posts = await fetchFromCMS();

  return {
    props: { posts },
    revalidate: 3600, // ISR: regenerate at most once per hour
  };
}

Das revalidate ist der Übergang vom rein Statischen ins Hybride: Die Seite bleibt statisch und CDN-schnell, altert aber nicht ein, weil sie sich nach der angegebenen Zeit selbst erneuert. Diese Mischung – statisch, wo möglich, serverseitig oder clientseitig, wo nötig – ist in meiner Erfahrung der realistische Normalfall, nicht die reine Lehre.

CDN und Performance

Warum der ganze Aufwand? Weil vorgerenderte Assets vom CDN oft schneller beim Nutzer ankommen. Statt einer Rechenkette aus Server und Datenbank liefert ein naher Edge-Standort eine fertige Datei aus. Das kann die Time to First Byte (TTFB) deutlich verbessern. Für die Interaktivität reicht fertiges Markup allein jedoch nicht: Viel JavaScript und teure Hydration können den Vorteil wieder aufzehren. Deshalb messe ich Auslieferung und Interaktivität getrennt, statt beides dem Pre-Rendering gutzuschreiben.

Hinzu kommt der Effekt auf SEO. Suchmaschinen bekommen bei vorgerendertem Markup sofort vollständigen Inhalt zu sehen, ohne auf clientseitiges Nachladen angewiesen zu sein. Für inhaltsgetriebene Seiten – Blogs, Dokumentationen, Marketing – ist das ein handfester Vorteil, den ich nicht der reinen Ladegeschwindigkeit unterordnen würde.

Ein häufiges Missverständnis will ich hier ausräumen: Das CDN liefert die statischen Assets aus, nicht die APIs. Die dynamischen Daten kommen weiterhin über API-Requests – entweder direkt aus dem Frontend oder über eine vorgelagerte Frontend-API. Das CDN beschleunigt also die Auslieferung des Gerüsts, während die dynamischen Teile getrennt davon bedient werden.

Entkopplung und API-Orchestrierung

Damit sind wir beim zweiten großen Prinzip: der Entkopplung. Im Jamstack sind Auslieferung des Frontends und dynamische Datenzugriffe getrennt. Das Backend kann weiterhin eine zusammenhängende Anwendung sein oder aus mehreren Diensten bestehen; entscheidend ist, dass das Frontend während Build oder Laufzeit über APIs darauf zugreift. Authentifizierung kann ein externer Dienst übernehmen, Inhalte können aus einem Headless-CMS kommen, und eigene Geschäftslogik kann in Serverless-Funktionen liegen. Das sind verbreitete Bausteine, keine Definition der Architektur.

Die spannende Frage ist dann, wo diese vielen APIs zusammengeführt werden. Zwei Wege haben sich etabliert. Entweder orchestriere ich direkt im Frontend, indem die Komponenten die benötigten Endpunkte selbst abfragen:

import useSWR from "swr";

const fetcher = (url) => fetch(url).then((res) => res.json());

function Cart() {
  const { data, error } = useSWR("/api/cart", fetcher);
  if (error) return <p>Failed to load</p>;
  if (!data) return <p>Loading…</p>;
  return <CartView items={data.items} />;
}

Oder ich lege eine Frontend-API davor – häufig über GraphQL, etwa mit AWS AppSync oder dem GraphQL-Endpunkt eines Headless-CMS. Das Frontend stellt dann eine einzige, konsolidierte Anfrage, und die Orchestrierung der dahinterliegenden Dienste passiert eine Schicht tiefer. Für größere Anwendungen mit vielen Datenquellen bevorzuge ich diesen Weg, weil er das Frontend von der Detailkenntnis über jedes einzelne Backend entlastet.

Die Serverless-Funktionen selbst sind bewusst schlank und zustandslos. Ein Handler im Netlify- beziehungsweise Lambda-Stil sieht typischerweise so aus:

exports.handler = async (event, context) => {
  const { name } = JSON.parse(event.body);

  return {
    statusCode: 200,
    body: JSON.stringify({ message: `Hello ${name}` }),
  };
};

Der wirtschaftliche Reiz dahinter kann das Abrechnungsmodell sein: pay-per-execution. Je nach Plattform, Grundgebühr und Lastprofil zahlt man stärker für tatsächliche Aufrufe und Rechenzeit als für dauerhaft vorgehaltene Server. Auch die Skalierung übernimmt der Anbieter innerhalb seiner Limits weitgehend. Laufzeitgrenzen, verteilte Fehler, Quoten und die Kosten einzelner Aufrufe bleiben trotzdem Architekturfragen.

Build und CI: der Git-Push als Auslöser

Die Vorrenderei muss natürlich irgendwann passieren. Häufig startet ein Git-Push eine CI-Pipeline – etwa über GitHub Actions –, die den Static-Site-Generator ausführt und das Ergebnis auf ein CDN bringt. Viele spezialisierte Plattformen veröffentlichen diese Artefakte atomar und versioniert, sodass eine neue Version geschlossen aktiv wird und sich zurückrollen lässt. Das ist eine Eigenschaft des gewählten Deployment-Systems, nicht automatisch jedes Jamstack-Builds.

flowchart LR
  Dev["git push"] --> CI["GitHub Actions<br/>(CI)"]
  CI --> SSG["Static Site Generator<br/>(Build)"]
  SSG --> Deploy["atomic deploy"]
  Deploy --> CDN["CDN<br/>(global edge)"]
  CDN --> User["User"]
  SSG -.->|"großer Katalog"| Shard["Sharding /<br/>parallele Builds"]
  Shard --> Deploy

Ein einfacher Workflow, der genau das tut, ist schnell aufgeschrieben:

name: build-and-deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: "16"
      - run: npm ci
      - run: npm run build
      - run: npx netlify deploy --prod --dir=dist

Bei kleinen Seiten ist der Build in Sekunden durch. Bei großen Katalogen mit vielen tausend Seiten wird die Build-Zeit selbst zum Thema – hier hilft Sharding, also das Aufteilen des Builds in parallele Teilprozesse. Das ist gleichzeitig ein guter Übergang zu den Grenzen, die ich weiter unten anspreche.

Sicherheit und Betriebsvorteile

Ein Nebeneffekt der Architektur, der leicht übersehen wird, betrifft die Sicherheit. Wenn zur Laufzeit gar kein Applikationsserver pro Request angesprochen wird, schrumpft die Angriffsfläche erheblich. Kein monolithischer Server, der bei jeder Anfrage Datenbankzugriffe fährt und dabei angreifbar wird. Ausgeliefert werden statische Dateien, und die dynamischen Teile stecken in klar abgegrenzten, einzeln absicherbaren APIs. In Summe ergibt sich daraus eine handliche Liste an Vorteilen:

  • Die TTFB kann sinken, weil vorgerendertes Markup direkt vom Edge kommt; die Interaktivität hängt zusätzlich am ausgelieferten JavaScript.
  • Für statische Seiten entfällt ein Applikationsserver pro Lesezugriff; die APIs bleiben eine eigene Angriffsfläche.
  • CDN und Plattform können einen großen Teil der Skalierung übernehmen, jeweils innerhalb ihrer Quoten und Laufzeitgrenzen.
  • Pay-per-execution kann bei passendem Lastprofil Kosten sparen, ist aber nicht grundsätzlich billiger.
  • Die Entwicklererfahrung profitiert von klarer Trennung, Git-basierten Deploys und schnellen Rollbacks.

Drei Fallstudien aus der Praxis

Damit das nicht theoretisch bleibt, drei Szenarien, die ich in dieser Reihenfolge fast als Reifegrad lese.

Das erste ist der statische Blog oder das Portfolio. Hier spielt der Jamstack seine Stärken aus: Inhalte werden im Build gerendert, es gibt kaum Dynamik, und das CDN kann sie ohne Anwendungsserver ausliefern. Für diese Klasse von Projekten ist reines SSG oft schon die vollständige Antwort.

Das zweite Szenario ist B2C-E-Commerce. Sobald Sessions, Warenkörbe und Transaktionen ins Spiel kommen, reicht das rein Statische nicht mehr. Produktseiten lassen sich hervorragend statisch vorrendern und über das CDN ausliefern, während Warenkorb, Checkout und Nutzerkonto clientseitig über APIs bedient werden. Das ist der klassische Hybrid-Fall, bei dem ISR zusätzlich hilft, Produktdaten aktuell zu halten, ohne jede Seite pro Request neu zu bauen.

Das dritte Szenario sind B2B-, SaaS- und IoT-Anwendungen. Hier dominieren Dashboards, personalisierte Ansichten und Echtzeitdaten. Ein statisches Gerüst kann zur schnellen Erstauslieferung beitragen; geschützte Bereiche lassen sich je nach Produkt clientseitig, serverseitig oder hybrid rendern. Auch eine GraphQL-Frontend-API kann sinnvoll sein, wenn tatsächlich mehrere Datenquellen für die Oberfläche gebündelt werden müssen.

Wo der Jamstack an Grenzen stößt

So überzeugend das Modell ist, ehrlich bleibe ich bei den Rändern. Reines SSG skaliert nicht beliebig. Bei einigen tausend Seiten wird die Build-Zeit langsam relevant, bei Millionen von Seiten wird das vollständige Vorrendern schlicht unpraktikabel. Genau an diesem Punkt kommen die bereits genannten Hilfsmittel ins Spiel: ISR rendert nur das nach, was sich wirklich ändert; Hybrid-Rendering erlaubt serverseitiges Erzeugen für die selten besuchten oder hochdynamischen Routen; und Sharding verteilt große Builds auf parallele Prozesse.

Auch bei der Werkzeugauswahl lohnt Nüchternheit. SvelteKit ist Ende 2021 vielversprechend, aber noch Beta. Azure Static Web Apps ist erst seit Mai 2021 allgemein verfügbar und damit ebenfalls recht jung, integriert dafür Azure Functions als Backend direkt mit. Schnelle Build-Bausteine wie esbuild von Evan Wallace beschleunigen die Pipeline enorm, sind aber selbst noch vor der 1.0. Wer hier baut, wählt bewusst zwischen etabliert und aufstrebend.

Fazit

Der Jamstack ist für mich weniger eine Technologie als eine Haltung zur Architektur. Er verschiebt geeignete Arbeit vom Request in den Build und trennt die Auslieferung des Frontends von dynamischen Datenzugriffen. Ob dahinter eine Enterprise-Anwendung, mehrere Dienste oder Serverless-Funktionen stehen, ist eine eigene Entscheidung. Schnelle Auslieferung über das CDN, eine kleinere Laufzeitfläche und einfache Deployments können daraus folgen, wenn Anwendung und Plattform dazu passen.

Entscheidend ist, dass der Jamstack technologieoffen bleibt. Er schreibt mir kein Framework vor, sondern eine Denkweise, innerhalb derer ich Next.js, Gatsby oder SvelteKit ebenso einsetzen kann wie Netlify, Vercel oder Azure. Und er ist kein Dogma: Reines SSG für den Blog, Hybrid für den Shop, serverseitig plus API-Orchestrierung für die SaaS-Plattform. Mit dieser Abstufung fällt die Architekturentscheidung dort, wo sie hingehört – eine Ebene über dem Framework.

Weiterführende Quellen

Wie fandest du diesen Beitrag?

Kommentare