Sie sehen vielleicht, dass der Begriff JAMstack immer häufiger vorkommt. Ich bin schon seit einiger Zeit ein Fan dieses Ansatzes.
Eines der Prinzipien von JAMstack ist das Pre-Rendering. Mit anderen Worten, es generiert Ihre Website im Voraus in eine Sammlung statischer Assets, sodass sie Ihren Besuchern mit maximaler Geschwindigkeit und minimalem Overhead von einem CDN oder einer anderen optimierten statischen Hosting-Umgebung bereitgestellt werden kann.
Aber wenn wir unsere Websites im Voraus generieren, wie lassen wir sie dann dynamisch wirken? Wie erstellen wir Websites, die sich oft ändern müssen? Wie arbeiten wir mit benutzergenerierten Inhalten?
Wie sich herausstellt, können Serverless Functions hierfür ein großartiger Anwendungsfall sein. JAMstack und Serverless sind beste Freunde. Sie ergänzen sich wunderbar.
In diesem Artikel werden wir ein Muster zur Verwendung von Serverless Functions als Fallback für vorgefertigte Seiten einer Website betrachten, die fast ausschließlich aus benutzergenerierten Inhalten besteht. Wir werden eine Technik des optimistischen URL-Routings verwenden, bei der die 404-Seite eine Serverless Function ist, um dynamisch Serverless Rendering hinzuzufügen.
Buzzwordy? Vielleicht. Effektiv? Auf jeden Fall!
Sie können sich die Demo-Website ansehen, um sich diesen Anwendungsfall vorzustellen. Aber nur, wenn Sie versprechen, zurückzukommen.

Sind Sie das? Sie sind zurückgekommen? Großartig. Lassen Sie uns eintauchen.
Die Idee hinter dieser kleinen Beispiel-Website ist, dass sie es Ihnen ermöglicht, eine nette, fröhliche Nachricht und ein virtuelles Aufmunterungsgeschenk für einen Freund zu erstellen. Sie können eine Nachricht schreiben, einen Lutscher (oder ein Eis, für meine amerikanischen Freunde) anpassen und eine URL erhalten, um sie mit dem beabsichtigten Empfänger zu teilen. Und schon haben Sie seinen Tag aufgehellt. Was gibt es da nicht zu lieben?
Traditionell würden wir diese Website mit serverseitigem Skripting erstellen, um Formularübermittlungen zu verarbeiten, neue Lutschies (unsere benutzergenerierten Inhalte) zu einer Datenbank hinzuzufügen und eine eindeutige URL zu generieren. Dann würden wir mehr serverseitige Logik verwenden, um Anfragen für diese Seiten zu parsen, die Datenbank abzufragen, um die Daten zu erhalten, die zur Befüllung einer Seitenansicht benötigt werden, sie mit einer geeigneten Vorlage zu rendern und sie an den Benutzer zurückzugeben.
Das alles erscheint logisch.
Aber wie viel kostet die Skalierung?
Technische Architekten und Tech-Leads erhalten diese Frage oft bei der Projektplanung. Sie müssen genügend Leistung planen, bezahlen und bereitstellen, falls der Erfolg eintritt.
Diese virtuelle Lolly-Website ist kein bloßes Beiwerk. Dieses Ding wird mich zum Multimillionär machen, wegen all der positiven Botschaften, die wir uns gegenseitig schicken wollen! Die Traffic-Zahlen werden sprunghaft ansteigen, wenn sich die Nachricht verbreitet. Ich sollte eine gute Strategie haben, um sicherzustellen, dass die Server die hohe Last bewältigen können. Ich könnte einige Caching-Layer, einige Load Balancer hinzufügen und meine Datenbank und Datenbankserver so gestalten, dass sie die Last ohne Stöhnen unter der Nachfrage nach der Erstellung und Bereitstellung all dieser Lollies teilen können.
Außer... Ich weiß nicht, wie man das macht.
Und ich weiß nicht, wie viel es kosten würde, diese Infrastruktur hinzuzufügen und alles am Laufen zu halten. Es ist kompliziert.
Deshalb vereinfache ich mein Hosting gerne, indem ich so viel wie möglich voraussichtlich rendere.
Das Ausliefern statischer Seiten ist erheblich einfacher und günstiger als das dynamische Ausliefern von Seiten von einem Webserver, der Logik ausführen muss, um Ansichten bei jeder Anfrage für jeden Besucher zu generieren.
Da wir mit vielen benutzergenerierten Inhalten arbeiten, macht es immer noch Sinn, eine Datenbank zu verwenden, aber diese werde ich nicht selbst verwalten. Stattdessen wähle ich eine der vielen verfügbaren Datenbankoptionen als Service aus. Und ich werde über ihre APIs mit ihr sprechen.
Ich könnte Firebase oder MongoDB oder eine beliebige andere wählen. Chris hat einige davon auf einer hervorragenden Website über Serverless-Ressourcen zusammengestellt, die es wert ist, erkundet zu werden.
In diesem Fall habe ich Fauna als meinen Datenspeicher gewählt. Fauna verfügt über eine nette API zum Speichern und Abfragen von Daten. Es ist ein No-SQL-Daten speicher und gibt mir genau das, was ich brauche.

Entscheidend ist, dass Fauna ein ganzes Unternehmen aus der Bereitstellung von Datenbankdiensten gemacht hat. Sie verfügen über das tiefe Fachwissen, das ich niemals haben werde. Durch die Verwendung eines Database-as-a-Service-Anbieters habe ich ein Expertenteam für Datendienste für mein Projekt geerbt, komplett mit Hochverfügbarkeitsinfrastruktur, Kapazitäts- und Compliance-Sicherheit, erfahrenen Support-Ingenieuren und umfangreicher Dokumentation.
Dies sind die Vorteile der Verwendung eines Drittanbieters wie diesem im Vergleich zur Eigenentwicklung.
Architektur TL;DR
Beim Arbeiten an einem Proof of Concept zeichne ich oft den logischen Ablauf der Dinge auf. Hier ist meine Skizze für diese Website.

Und eine kleine Erklärung
- Ein Benutzer erstellt einen neuen Lutscher, indem er ein ganz normales HTML-Formular ausfüllt.
- Der neue Inhalt wird in einer Datenbank gespeichert, und seine Übermittlung löst eine neue Website-Generierung und Bereitstellung aus.
- Sobald die Website-Bereitstellung abgeschlossen ist, wird der neue Lutscher unter einer eindeutigen URL verfügbar sein. Es wird sich um eine statische Seite handeln, die sehr schnell vom CDN geliefert wird, ohne Abhängigkeit von einer Datenbankabfrage oder einem Server.
- Bis die Website-Generierung abgeschlossen ist, sind neue Lutscher nicht als statische Seiten verfügbar. Erfolgreiche Anfragen nach Lolly-Seiten fallen auf eine Seite zurück, die die Lolly-Seite dynamisch generiert, indem sie die Datenbank-API bei Bedarf abfragt.
Diese Art von Ansatz, die zuerst statische/vorgefertigte Assets annimmt und erst dann auf ein dynamisches Rendering zurückgreift, wenn keine statische Ansicht verfügbar ist, wurde von Markus Schork von Unilever treffend als „Static First“ beschrieben, was mir sehr gut gefällt.
Im Detail
Sie könnten direkt in den Code für diese Website eintauchen, der Open Source ist und Ihnen zur Verfügung steht, oder wir können noch etwas weiter sprechen.
Sie möchten tiefer eintauchen und die Implementierung dieses Beispiels erkunden? Okay, ich werde es im Detail erklären.
- Daten aus der Datenbank abrufen, um jede Seite zu generieren
- Daten per Serverless Function an eine Datenbank-API senden
- Auslösen einer vollständigen Seitengenerierung
- Rendern nach Bedarf, wenn Seiten noch nicht generiert wurden
Seiten aus einer Datenbank generieren
Gleich werden wir darüber sprechen, wie Daten in die Datenbank geschrieben werden, aber lassen Sie uns zunächst annehmen, dass bereits einige Einträge in der Datenbank vorhanden sind. Wir möchten eine Website generieren, die für jeden dieser Einträge eine Seite enthält.
Statische Seitengeneratoren sind dafür großartig. Sie verarbeiten Daten, wenden sie auf Vorlagen an und geben HTML-Dateien aus, die bereit zum Ausliefern sind. Wir könnten für dieses Beispiel jeden Generator verwenden. Ich habe mich für Eleventy entschieden, aufgrund seiner relativen Einfachheit und der Geschwindigkeit seiner Seitengenerierung.
Um Eleventy Daten zu füttern, haben wir verschiedene Möglichkeiten. Eine besteht darin, ihm JavaScript zu geben, das strukturierte Daten zurückgibt. Dies ist perfekt für die Abfrage einer Datenbank-API.
Unsere Eleventy-Datendatei wird ungefähr so aussehen.
// Set up a connection with the Fauna database.
// Use an environment variable to authenticate
// and get access to the database.
const faunadb = require('faunadb');
const q = faunadb.query;
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET
});
module.exports = () => {
return new Promise((resolve, reject) => {
// get the most recent 100,000 entries (for the sake of our example)
client.query(
q.Paginate(q.Match(q.Ref("indexes/all_lollies")),{size:100000})
).then((response) => {
// get all data for each entry
const lollies = response.data;
const getAllDataQuery = lollies.map((ref) => {
return q.Get(ref);
});
return client.query(getAllDataQuery).then((ret) => {
// send the data back to Eleventy for use in the site build
resolve(ret);
});
}).catch((error) => {
console.log("error", error);
reject(error);
});
})
}
Ich habe diese Datei lollies.js genannt, wodurch alle von ihr zurückgegebenen Daten Eleventy in einer Sammlung namens lollies zur Verfügung gestellt werden.
Wir können diese Daten nun in unseren Vorlagen verwenden. Wenn Sie den Code sehen möchten, der dies nimmt und für jeden Artikel eine Seite generiert, können Sie ihn im Code-Repository sehen.
Daten ohne Server übermitteln und speichern
Wenn wir eine neue Lolly-Seite erstellen, müssen wir Benutzerinhalte in der Datenbank erfassen, damit sie in Zukunft zur Befüllung einer Seite unter einer bestimmten URL verwendet werden können. Hierfür verwenden wir ein traditionelles HTML-Formular, das Daten an einen geeigneten Formularhandler sendet.
Das Formular sieht ungefähr so aus (oder sehen Sie den vollständigen Code im Repo).
<form name="new-lolly" action="/new" method="POST">
<!-- Default "flavors": 3 bands of colors with color pickers -->
<input type="color" id="flavourTop" name="flavourTop" value="#d52358" />
<input type="color" id="flavourMiddle" name="flavourMiddle" value="#e95946" />
<input type="color" id="flavourBottom" name="flavourBottom" value="#deaa43" />
<!-- Message fields -->
<label for="recipientName">To</label>
<input type="text" id="recipientName" name="recipientName" />
<label for="message">Say something nice</label>
<textarea name="message" id="message" cols="30" rows="10"></textarea>
<label for="sendersName">From</label>
<input type="text" id="sendersName" name="sendersName" />
<!-- A descriptive submit button -->
<input type="submit" value="Freeze this lolly and get a link">
</form>
Wir haben in unserem Hosting-Szenario keine Webserver, daher müssen wir einen Ort finden, um die von diesem Formular übermittelten HTTP-POST-Anfragen zu bearbeiten. Dies ist ein perfekter Anwendungsfall für eine Serverless Function. Ich verwende hierfür Netlify Functions. Sie können auch AWS Lambda, Google Cloud oder Azure Functions verwenden, aber ich mag die Einfachheit des Workflows mit Netlify Functions und die Tatsache, dass meine Serverless API und meine UI in einem einzigen Code-Repository zusammengehalten werden.
Es ist gute Praxis, Backend-Implementierungsdetails nicht in Ihr Frontend einzubringen. Eine klare Trennung hilft, Dinge portabler und aufgeräumter zu halten. Schauen Sie sich das Attribut action des Formularelements oben an. Es sendet Daten an einen Pfad auf meiner Website namens /new, was nicht wirklich darauf hindeutet, mit welchem Dienst dies kommunizieren wird.
Wir können Umleitungen verwenden, um dies zu jedem beliebigen Dienst weiterzuleiten. Ich werde es an eine Serverless Function senden, die ich im Rahmen dieses Projekts bereitstellen werde, aber es könnte leicht angepasst werden, die Daten woanders hin zu senden, wenn wir möchten. Netlify bietet uns eine einfache und hoch optimierte Umleitungs-Engine, die unseren Traffic auf der CDN-Ebene umleitet, sodass Benutzer sehr schnell an den richtigen Ort geleitet werden.
Die folgende Umleitungsregel (die sich in der Datei netlify.toml meines Projekts befindet) leitet Anfragen an /new an eine Serverless Function weiter, die von Netlify Functions gehostet wird und namens newLolly.js heißt.
# resolve the "new" URL to a function
[[redirects]]
from = "/new"
to = "/.netlify/functions/newLolly"
status = 200
Schauen wir uns diese Serverless Function an, die
- die neuen Daten in der Datenbank speichert,
- eine neue URL für die neue Seite erstellt und
- den Benutzer zur neu erstellten Seite umleitet, damit er das Ergebnis sehen kann.
Zuerst benötigen wir die verschiedenen Hilfsprogramme, die wir zum Parsen der Formulardaten, zum Herstellen einer Verbindung zur Fauna-Datenbank und zum Erstellen lesbar kurzer eindeutiger IDs für neue Lollies benötigen.
const faunadb = require('faunadb'); // For accessing FaunaDB
const shortid = require('shortid'); // Generate short unique URLs
const querystring = require('querystring'); // Help us parse the form data
// First we set up a new connection with our database.
// An environment variable helps us connect securely
// to the correct database.
const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET
})
Jetzt fügen wir etwas Code hinzu, um Anfragen an die Serverless Function zu bearbeiten. Die Handler-Funktion parst die Anfrage, um die benötigten Daten aus der Formularübermittlung zu erhalten, generiert dann eine eindeutige ID für den neuen Lolly und erstellt ihn dann als neuen Datensatz in der Datenbank.
// Handle requests to our serverless function
exports.handler = (event, context, callback) => {
// get the form data
const data = querystring.parse(event.body);
// add a unique path id. And make a note of it - we'll send the user to it later
const uniquePath = shortid.generate();
data.lollyPath = uniquePath;
// assemble the data ready to send to our database
const lolly = {
data: data
};
// Create the lolly entry in the fauna db
client.query(q.Create(q.Ref('classes/lollies'), lolly))
.then((response) => {
// Success! Redirect the user to the unique URL for this new lolly page
return callback(null, {
statusCode: 302,
headers: {
Location: `/lolly/${uniquePath}`,
}
});
}).catch((error) => {
console.log('error', error);
// Error! Return the error with statusCode 400
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
});
});
}
Überprüfen wir unseren Fortschritt. Wir haben eine Möglichkeit, neue Lolly-Seiten in der Datenbank zu erstellen. Und wir haben eine automatisierte Build, die für jeden unserer Lollies eine Seite generiert.
Um sicherzustellen, dass ein vollständiger Satz vorgefertigter Seiten für jeden Lolly vorhanden ist, sollten wir jedes Mal einen neuen Build auslösen, wenn ein neuer Eintrag erfolgreich zur Datenbank hinzugefügt wird. Das ist sehr einfach zu tun. Unser Build ist dank unseres statischen Seitengenerators bereits automatisiert. Wir brauchen nur eine Möglichkeit, ihn auszulösen. Mit Netlify können wir so viele Build-Hooks definieren, wie wir möchten. Das sind Webhooks, die unseren Build und unsere Bereitstellung neu starten, wenn sie eine HTTP-POST-Anfrage erhalten. Hier ist der, den ich in der Admin-Konsole von Netlify erstellt habe.

Um die Website neu zu generieren, einschließlich einer Seite für jeden Lolly, der in der Datenbank erfasst ist, können wir eine HTTP-POST-Anfrage an diesen Build-Hook senden, sobald wir unsere neuen Daten in der Datenbank gespeichert haben.
Dies ist der Code dafür.
const axios = require('axios'); // Simplify making HTTP POST requests
// Trigger a new build to freeze this lolly forever
axios.post('https://api.netlify.com/build_hooks/5d46fa20da4a1b70XXXXXXXXX')
.then(function (response) {
// Report back in the serverless function's logs
console.log(response);
})
.catch(function (error) {
// Describe any errors in the serverless function's logs
console.log(error);
});
Sie können ihn im Kontext sehen, hinzugefügt zum Erfolgs-Handler für die Datenbankeinfügung im vollständigen Code.
Das ist alles großartig, wenn wir damit zufrieden sind, auf den Abschluss des Builds und der Bereitstellung zu warten, bevor wir die URL unseres neuen Lollies mit dem beabsichtigten Empfänger teilen. Aber wir sind keine geduldigen Leute, und wenn wir diese schöne neue URL für den gerade erstellten Lolly erhalten, wollen wir sie sofort teilen.
Leider erhalten wir, wenn wir diese URL aufrufen, bevor die Website mit der neuen Seite neu generiert wurde, eine 404. Aber zum Glück können wir diese 404 zu unserem Vorteil nutzen.
Optimistisches URL-Routing und Serverless-Fallbacks
Mit benutzerdefinierten 404-Weiterleitungen können wir jede fehlgeschlagene Anfrage nach einer Lolly-Seite an eine Seite weiterleiten, die die Lolly-Daten direkt in der Datenbank sucht. Das könnten wir mit clientseitigem JavaScript tun, wenn wir wollten, aber noch besser wäre es, eine fertige Seite dynamisch aus einer Serverless Function zu generieren.
Hier ist wie:
Zuerst müssen wir all jenen hoffnungsvollen Anfragen nach einer Lolly-Seite, die leer zurückkommen, mitteilen, stattdessen zu unserer Serverless Function zu gehen. Das machen wir mit einer weiteren Regel in unserer Netlify-Weiterleitungskonfiguration.
# unfound lollies should proxy to the API directly
[[redirects]]
from = "/lolly/*"
to = "/.netlify/functions/showLolly?id=:splat"
status = 302
Diese Regel wird nur angewendet, wenn die Anfrage nach einer Lolly-Seite keine statische Seite gefunden hat, die zum Ausliefern bereit ist. Sie erstellt eine temporäre Umleitung (HTTP 302) zu unserer Serverless Function, die ungefähr so aussieht.
const faunadb = require('faunadb'); // For accessing FaunaDB
const pageTemplate = require('./lollyTemplate.js'); // A JS template litereal
// setup and auth the Fauna DB client
const q = faunadb.query;
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET
});
exports.handler = (event, context, callback) => {
// get the lolly ID from the request
const path = event.queryStringParameters.id.replace("/", "");
// find the lolly data in the DB
client.query(
q.Get(q.Match(q.Index("lolly_by_path"), path))
).then((response) => {
// if found return a view
return callback(null, {
statusCode: 200,
body: pageTemplate(response.data)
});
}).catch((error) => {
// not found or an error, send the sad user to the generic error page
console.log('Error:', error);
return callback(null, {
body: JSON.stringify(error),
statusCode: 301,
headers: {
Location: `/melted/index.html`,
}
});
});
}
Wenn eine Anfrage nach einer anderen Seite (nicht im Pfad /lolly/ der Website) zu einem 404 führt, leiten wir diese Anfrage nicht an unsere Serverless Function weiter, um nach einem Lolly zu suchen. Wir können den Benutzer einfach direkt zu einer 404-Seite weiterleiten. Unsere netlify.toml Konfiguration lässt uns beliebig viele Ebenen von 404-Weiterleitungen definieren, indem wir weiter unten in der Datei Fallback-Regeln hinzufügen. Der erste erfolgreiche Treffer in der Datei wird berücksichtigt.
# unfound lollies should proxy to the API directly
[[redirects]]
from = "/lolly/*"
to = "/.netlify/functions/showLolly?id=:splat"
status = 302
# Real 404s can just go directly here:
[[redirects]]
from = "/*"
to = "/melted/index.html"
status = 404
Und wir sind fertig! Wir haben nun eine Website, die „Static First“ ist und versucht, Inhalte dynamisch mit einer Serverless Function zu rendern, wenn eine URL noch nicht als statische Datei generiert wurde.
Ziemlich flott!
Unterstützung für größere Skalierung
Unsere Technik, einen Build auszulösen, um die Lolly-Seiten jedes Mal neu zu generieren, wenn ein neuer Eintrag erstellt wird, ist möglicherweise nicht für immer optimal. Obwohl es stimmt, dass die Automatisierung des Builds die Neuverteilung der Website trivial macht, möchten wir Dinge drosseln und optimieren, wenn wir sehr beliebt werden. (Was nur eine Frage der Zeit sein kann, richtig?)
Das ist in Ordnung. Hier sind ein paar Dinge, die Sie berücksichtigen sollten, wenn wir sehr viele Seiten erstellen müssen und häufigere Ergänzungen zur Datenbank vornehmen.
- Anstatt einen Build für jeden neuen Eintrag auszulösen, könnten wir die Website als geplanten Job neu generieren. Vielleicht passiert das einmal pro Stunde oder einmal pro Tag.
- Wenn wir einmal täglich bauen, könnten wir uns entscheiden, nur die Seiten für neue Lollies zu generieren, die in den letzten Tagen eingereicht wurden, und die jeden Tag generierten Seiten für die zukünftige Verwendung zu cachen. Diese Art von Logik im Build würde uns helfen, eine massive Anzahl von Lolly-Seiten zu unterstützen, ohne dass der Build unerschwinglich lang wird. Aber ich werde hier nicht auf Intra-Build-Caching eingehen. Wenn Sie neugierig sind, können Sie im Netlify Community Forum danach fragen.
Durch die Kombination von statischen, vorgefertigten Assets mit Serverless-Fallbacks, die dynamisches Rendering ermöglichen, können wir eine überraschend breite Palette von Anwendungsfällen abdecken – und das alles, ohne viele dynamische Infrastrukturen bereitstellen und warten zu müssen.
Welche anderen Anwendungsfälle könnten Sie mit diesem „Static First“-Ansatz abdecken?
Hallo,
Super interessanter Artikel! Ich habe über eine ähnliche Lösung nachgedacht, aber Probleme mit Updates -> veraltete Inhalte. Haben Sie einen Vorschlag, wenn jemand in Ihrem Fall die Farben des Lolly ändern könnte? Da die Website für diesen Lolly vorgefertigt ist, würde sie bis zum nächsten Build mit der alten Version ausgeliefert. Gibt es eine Möglichkeit, die Cloud Function bereitzustellen, wenn ein neuerer DB-Eintrag verfügbar ist?
Großartige Lektüre!
Ich frage mich, gibt es einen statischen Website-Builder, der nur geänderte Daten erstellen könnte? Hier nur eine neue Lollipop-Seite?