Die JAMstack-Architektur: eine webbasierte Top-Level-Architektur
JAMstack steht für JavaScript, APIs und Markup – eine technologieoffene Architektur, die im Build vorrendert, über ein CDN ausliefert und das Backend in unabhängige APIs und Serverless-Funktionen entkoppelt.
In meinen Schulungen und Beratungen kommt früher oder später immer dieselbe Frage: Wie strukturiere ich eine Webanwendung so, dass sie schnell ist, sich gut skalieren lässt und trotzdem wartbar bleibt? Über die letzten Jahre habe ich gemerkt, dass viele Teams intuitiv nach dem nächsten Framework greifen, obwohl die eigentliche Entscheidung eine Ebene höher fällt – bei der Top-Level-Architektur. Genau hier setzt der JAMstack an. Er ist kein Framework, das ich installiere, sondern eine Denkweise, wie ich eine moderne Webplattform überhaupt aufbaue.
Das Akronym steht für JavaScript, APIs und Markup. Geprägt hat den Begriff Mathias (Matt) Biilmann Christensen, Mitgründer und CEO von Netlify, der ihn rund um 2015/2016 öffentlich gemacht hat – prominent auf der Smashing Conference 2016. Die zentrale Idee klingt zunächst unspektakulär und ist trotzdem ein echter Paradigmenwechsel: Statt bei jeder Anfrage frisch HTML auf dem Server zu rendern, wird das Markup einmal im Build vorgerendert und anschließend über ein CDN ausgeliefert. Dynamik entsteht erst danach – im Browser, über JavaScript und API-Aufrufe.
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
Wichtig ist mir an dieser Stelle eine Klarstellung, weil sie in Diskussionen oft untergeht: Der JAMstack verbietet serverseitiges Rendering nicht. Er verlagert nur 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
getStaticPropsundgetStaticPathsrendert die Seite im Build; danach ist sie reines Markup vom CDN. - SSR (Server-Side Rendering) über
getServerSidePropserzeugt 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 spürbar schneller beim Nutzer ankommen. Statt einer Rechenkette aus Server und Datenbank liefert das nächstgelegene Edge einfach eine fertige Datei aus. Das schlägt sich in zwei Metriken nieder, die ich in Reviews regelmäßig heranziehe: die Time to First Byte (TTFB), also wie schnell überhaupt das erste Byte eintrifft, und die Time to Interactive (TTI), also ab wann der Nutzer wirklich mit der Seite arbeiten kann. Beide profitieren, wenn das Markup schon fertig vorliegt und nicht erst erzeugt werden muss.
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 Frontend und Backend sauber getrennt. Das Backend ist kein Monolith mehr, der alles kann, sondern eine Sammlung unabhängiger APIs und Serverless-Funktionen. Authentifizierung übernimmt ein Dienst wie Auth0 mit JWT, Inhalte kommen aus einem Headless-CMS wie Contentful oder GraphCMS, die Suche liefert Algolia, Kommentare vielleicht Disqus – und die eigene Geschäftslogik steckt in einzelnen Funktionen, etwa auf AWS Lambda oder in Netlify Functions.
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 ist das Abrechnungsmodell: pay-per-execution. Ich zahle nur für die tatsächlichen Aufrufe und die verbrauchte Rechenzeit, nicht für einen dauerhaft laufenden Server. Die Skalierung passiert on-demand, ohne dass ich Kapazität vorhalten muss. Wer sich tiefer einlesen will, dem lege ich meinen Beitrag Serverless: Grundlagen und Grenzen sowie den AWS-Serverless-Lernpfad ans Herz.
Build und CI: der Git-Push als Auslöser
Die Vorrenderei muss natürlich irgendwann passieren, und im JAMstack ist der Auslöser dafür in aller Regel ein Git-Push. Ein Push auf das Repository startet eine CI-Pipeline – etwa über GitHub Actions – die den Static-Site-Generator ausführt und das Ergebnis als atomares Deployment auf das CDN bringt. Atomar heißt: Die neue Version wird komplett und auf einmal aktiv, ein halb aufgespielter Zustand kann gar nicht erst entstehen. Spezialisierte Hoster wie Netlify und Vercel haben genau diesen Ablauf zu ihrem Kernprodukt gemacht, inklusive Edge-CDN und versionierten, jederzeit zurückrollbaren Deploys.
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, den ich in der Beratung gern betone, betrifft die Sicherheit. Wenn zur Laufzeit gar kein origin-Applikationsserver pro Request angesprochen wird, schrumpft die Angriffsfläche erheblich. Es gibt keinen monolithischen 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 Performance steigt, weil vorgerendertes Markup direkt vom Edge kommt – das drückt TTFB und verbessert die TTI.
- Die Angriffsfläche schrumpft, ergänzt um atomare, versionierte Deployments.
- Die Skalierung übernehmen das CDN und die on-demand skalierenden Serverless-Funktionen.
- Die Kosten sinken durch pay-per-execution statt dauerhaft vorgehaltener Serverkapazität.
- 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 pur aus: Inhalte werden im Build gerendert, es gibt kaum Dynamik, das CDN liefert alles blitzschnell, und die Betriebskosten sind minimal. 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. Das statische Gerüst trägt weiterhin zur schnellen Erstauslieferung bei, aber der Schwerpunkt verschiebt sich klar auf serverseitiges Rendering für geschützte Bereiche und auf eine dichte API-Orchestrierung – gern über eine GraphQL-Frontend-API, die viele Backend-Dienste bündelt.
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 die Arbeit vom Request in den Build, entkoppelt Frontend und Backend und ersetzt den Monolithen durch ein Geflecht unabhängiger APIs und Serverless-Funktionen. Was daraus folgt – schnelle Auslieferung über das CDN, eine kleinere Angriffsfläche, günstige Skalierung und eine angenehme Entwicklererfahrung – ist kein Selbstzweck, sondern die Konsequenz aus einer klaren Grundentscheidung.
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. Wer diese Abstufung verinnerlicht, trifft die Architekturentscheidung dort, wo sie hingehört – eine Ebene über dem Framework.
Weiterführende Quellen
- Mike Bild: „JAMstack-Architektur" – der ausführliche Originalartikel: https://www.developer-world.de/dwx-insights/architecture-patterns/jamstack-architektur
- Offizielle JAMstack-Ressource: https://jamstack.org/
- Next.js 12 (Release vom 26.10.2021): https://nextjs.org/blog/next-12
- SvelteKit Public Beta: https://svelte.dev/blog/sveltekit-beta
- Azure Static Web Apps GA (Mai 2021): https://www.infoq.com/news/2021/05/azure-static-web-apps-ga/
Kommentare