Wir haben das Thema Browser, CSS und Barrierefreiheit lange ignoriert und beschlossen, mit der Übersetzung des heutigen Übersichtsmaterials (Original - Februar 2020) darauf zurückzukommen. Ich interessiere mich besonders für Ihre Meinung zur hier erwähnten Server-Rendering-Technologie sowie dazu, wie dringend ein vollwertiges Buch über HTTP / 2 erforderlich ist. Lassen Sie uns jedoch über alles in der richtigen Reihenfolge sprechen.
In diesem Beitrag werden einige Techniken beschrieben, mit denen das Laden von Front-End-Anwendungen beschleunigt und damit die Benutzerfreundlichkeit verbessert werden kann.
Schauen wir uns die allgemeine Architektur des Frontends an. Wie stellen Sie sicher, dass kritische Ressourcen zuerst geladen werden, und maximieren Sie die Wahrscheinlichkeit, dass diese Ressourcen bereits im Cache landen?
Ich werde nicht darauf eingehen, wie das Backend Ressourcen bereitstellen soll, ob Ihre Seite überhaupt als Clientanwendung gerendert werden muss oder wie die Renderzeit Ihrer Anwendung optimiert werden kann.
Überblick
Lassen Sie uns den App-Download-Prozess in drei separate Schritte unterteilen:
- Primäres Rendern - Wie lange dauert es, bis der Benutzer etwas sieht?
- App-Download - Wie lange dauert es, bis der Benutzer die App sieht?
- – ?
Bis zur Phase des primären Renderns (Rendern) kann der Benutzer einfach nichts sehen. Zum Rendern einer Seite benötigen Sie mindestens ein HTML-Dokument. In den meisten Fällen müssen Sie jedoch auch zusätzliche Ressourcen wie CSS- und JavaScript-Dateien laden. Falls verfügbar, kann der Browser mit dem Rendern auf dem Bildschirm beginnen.
In diesem Beitrag werde ich WebPageTest-Wasserfalldiagramme verwenden . Die Kaskade von Anfragen für Ihre Site sieht ungefähr so aus.
Eine Reihe anderer Dateien wird zusammen mit dem HTML-Dokument geladen, und die Seite wird gerendert, nachdem sich alle im Speicher befinden. Bitte beachten Sie, dass CSS-Dateien parallel geladen werden, sodass jede nachfolgende Anforderung die Verzögerung nicht wesentlich erhöht.
Reduzieren der Anzahl der Renderblockierungsanforderungen
In Stylesheets und (standardmäßig) Skriptelementen können keine Inhalte darunter angezeigt werden.
Es gibt verschiedene Möglichkeiten, dies zu beheben:
- Wir setzen Skript-Tags ganz unten in das Tag
body - Laden Sie Skripte asynchron mit
async - Schreiben Sie kleine Teile von JS oder CSS inline, wenn Sie sie synchron laden möchten
Vermeiden Sie das Rendern blockierender Abfrageketten
Es ist nicht nur die Anzahl der Renderblockierungsanforderungen, die Ihre Site verlangsamen können. Wichtiger ist die Größe jeder dieser Ressourcen, die heruntergeladen werden müssen, sowie wann genau der Browser erkennt, dass die Ressource heruntergeladen werden muss.
Wenn der Browser erst nach Abschluss einer anderen Anforderung feststellt, dass die Datei heruntergeladen werden muss, kann es zu einer Kette synchroner Anforderungen kommen. Dies kann verschiedene Gründe haben:
- Regeln
@importin CSS haben - Verwenden von Webschriftarten, auf die in einer CSS-Datei verwiesen wird
- JavaScript-Injection-Link oder Skript-Tags
Betrachten Sie dieses Beispiel:
Eine der CSS-Dateien auf dieser Website enthält eine Regel
@importzum Laden einer Google-Schriftart. Daher muss der Browser die folgenden Anforderungen nacheinander in dieser Reihenfolge ausführen:
- Dokument HTML
- Anwendungs-CSS
- Google Fonts CSS
- Google Font Woff-Datei (nicht in Kaskade angezeigt)
Um dies zu beheben, verschieben wir zuerst die Anforderung für Google Fonts CSS von
@importauf das Link-Tag im HTML-Dokument. Dadurch wird die Kette um ein Glied verkürzt.
Betten Sie die CSS-Datei von Google Fonts direkt in Ihr HTML oder Ihre CSS-Datei ein , um die Geschwindigkeit noch weiter zu steigern .
(Denken Sie daran, dass die CSS-Antwort von Google Fonts vom Benutzeragenten abhängt. Wenn Sie eine Anfrage mit IE8 stellen, verweist das CSS auf eine EOT-Datei (eingebettet in OpenType), IE11 erhält eine Woff-Datei und moderne Browser erhalten eine Woff2. Wenn Sie jedoch damit zufrieden sind Wenn Sie wie bei relativ alten Browsern mit Systemschriftarten arbeiten, können Sie den Inhalt der CSS-Datei einfach kopieren und einfügen.)
Selbst nachdem die Seite gerendert wurde, kann der Benutzer möglicherweise nichts damit anfangen, da kein Text angezeigt wird, bis die Schriftart vollständig geladen ist. Dies kann vermieden werden, indem die Eigenschaft zum Austauschen von Schriftarten verwendet wird , die jetzt in Google Fonts die Standardeinstellung ist.
Manchmal können Sie die Anforderungskette nicht vollständig loswerden. Versuchen Sie in solchen Fällen, das Tag
preloadoder zu verwenden preconnect. Beispielsweise kann die oben gezeigte Site eine Verbindung herstellen, fonts.googleapis.combevor die eigentliche CSS-Anforderung gestellt wird.
Wiederverwenden von Serververbindungen, um Anforderungen zu beschleunigen
Normalerweise erfordert das Herstellen einer neuen Serververbindung drei Roundtrip-Durchgänge zwischen dem Browser und dem Server:
- DNS-Suche
- Herstellen einer TCP-Verbindung
- Herstellen einer SSL-Verbindung
Sobald die Verbindung hergestellt ist, ist mindestens eine weitere Hin- und Rückfahrt erforderlich: Senden Sie eine Anfrage und laden Sie eine Antwort herunter.
Wie in der folgenden Kaskade gezeigt, werden Verbindungen zu vier verschiedenen Servern hergestellt: hostgator.com, optimizely.com, googletagmanager.com und googelapis.com.
Allerdings , nachfolgende Anforderungen auf den betroffenen Server kann die bestehende Verbindung wieder verwenden. Daher
base.cssoder index1.csswerden schnell geladen, da sie sich auch auf hostgator.com befinden.
Reduzierung der Dateigröße und Verwendung von Content Delivery Networks (CDN)
Zwei weitere Faktoren, die Sie steuern, wirken sich neben der Dateigröße auf die Dauer der Anforderung aus: die Größe der Ressource und der Standort Ihrer Server.
Senden Sie dem Benutzer die minimal erforderliche Datenmenge. Achten Sie außerdem auf deren Komprimierung (z. B. mit brotli oder gzip).
Content Delivery Networks (CDNs) stellen Server an einer Vielzahl von Standorten bereit, sodass die Chancen gut stehen, dass sich einer von ihnen in der Nähe Ihrer Benutzer befindet. Sie können sie nicht mit Ihrem zentralen Anwendungsserver verbinden, sondern mit dem nächstgelegenen Server auf dem CDN. Dadurch wird der Datenpfad zum Server und zurück erheblich reduziert. Dies ist besonders nützlich, wenn Sie mit statischen Ressourcen wie CSS, JavaScript und Bildern arbeiten, da diese einfach zu verteilen sind.
Umgehen des Netzwerks mit Servicemitarbeitern
Mit Servicemitarbeitern können Sie Anforderungen abfangen, bevor sie in das Netzwerk eintreten. Somit kann das erste Rendern fast sofort erfolgen !
Dies funktioniert natürlich nur, wenn das Netzwerk einfach eine Antwort senden soll. Diese Antwort sollte bereits zwischengespeichert sein, was Ihren Benutzern das Leben nur erleichtert, wenn sie Ihre Anwendung erneut herunterladen.
Der unten gezeigte Servicemitarbeiter speichert das zum Rendern der Seite erforderliche HTML und CSS zwischen. Beim erneuten Laden versucht die Anwendung, zwischengespeicherte Ressourcen auszugeben. Wenn diese nicht verfügbar sind, wird sie als Fallback an das Netzwerk weitergeleitet.
self.addEventListener("install", async e => {
caches.open("v1").then(function (cache) {
return cache.addAll(["/app", "/app.css"]);
});
});
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
return cachedResponse || fetch(event.request);
})
);
});
Weitere Informationen zum Vorladen und Zwischenspeichern von Ressourcen mithilfe von Servicemitarbeitern finden Sie in diesem Lernprogramm .
Anwendungs-Download
Okay, unser Benutzer hat bereits etwas gesehen. Was braucht er noch, um unsere Anwendung nutzen zu können?
- Laden der App (JS und CSS)
- Laden der wichtigsten Daten für eine Seite
- Laden Sie zusätzliche Daten und Bilder herunter
Bitte beachten Sie, dass nicht nur das Laden von Daten über das Netzwerk das Rendern verlangsamen kann. Wenn der Code geladen wird, muss der Browser ihn analysieren, kompilieren und ausführen.
Aufteilen des Bundles: Laden Sie nur den erforderlichen Code und maximieren Sie die Cache-Treffer.
Durch Aufteilen des Bundles können Sie nur den Code herunterladen, den Sie nur für diese Seite benötigen, und nicht die gesamte Anwendung herunterladen. Wenn Sie ein Bundle aufteilen, kann es in Teilen zwischengespeichert werden, auch wenn andere Teile des Codes geändert wurden und neu geladen werden müssen.
Normalerweise besteht der Code aus drei verschiedenen Dateitypen:
- Code für diese Seite
- Gemeinsamer Anwendungscode
- Module von Drittanbietern, die sich selten ändern (ideal zum Zwischenspeichern!)
Webpack kann den geteilten Code automatisch aufteilen, um das Gesamtgewicht der Downloads zu reduzieren. Dies erfolgt mithilfe von optimierung.splitChunks . Stellen Sie sicher, dass der Laufzeitblock aktiviert ist, damit die Hashes der Blöcke stabil bleiben und das langfristige Caching sinnvoll angewendet werden kann. Ivan Akulov hat einen umfassenden Leitfaden zum Teilen und Zwischenspeichern von Webpack-Code geschrieben.
Das Aufteilen von seitenspezifischem Code kann nicht automatisch erfolgen, daher müssen Sie Snippets identifizieren, die separat geladen werden können. Dies ist häufig eine bestimmte Route oder ein Satz von Seiten. Verwenden Sie dynamische Importe, um solchen Code verzögert zu laden.
Das Aufteilen des Bundles führt dazu, dass mehr Anforderungen gestellt werden, um Ihre Anwendung vollständig zu laden. Wenn die Anforderungen jedoch parallelisiert sind, ist dieses Problem nicht groß, insbesondere auf Websites, die HTTP / 2 verwenden. Beachten Sie die ersten drei Abfragen in dieser Kaskade:
In dieser Kaskade werden jedoch auch zwei Abfragen angezeigt, die nacheinander ausgeführt werden. Diese Fragmente werden nur für diese Seite benötigt und mithilfe eines Aufrufs dynamisch geladen
import().
Dies kann durch Einfügen eines Tags behoben werden,
preload linkwenn Sie wissen, dass Sie diese Fragmente definitiv benötigen.
Wie Sie jedoch sehen können, kann der Geschwindigkeitsgewinn in diesem Fall im Vergleich zur gesamten Ladezeit der Seite gering sein.
Darüber hinaus ist das Vorladen manchmal kontraproduktiv und kann zu Verzögerungen führen, wenn andere, wichtigere Dateien geladen werden. Lesen Sie den Beitrag von Andy Davis zum Vorladen von Schriftarten und zum Blockieren des primären Renderns, indem Sie zuerst die Schriftarten und dann das CSS laden, das das Rendern verhindert.
Laden von Seitendaten
Wahrscheinlich ist Ihre Anwendung so konzipiert, dass sie Daten anzeigt. Hier finden Sie einige Tipps, wie Sie Daten vorab laden und Verzögerungen beim Rendern vermeiden können.
Warten Sie nicht auf Bundles, sondern laden Sie sofort mit dem Laden von Daten.
Es kann einen besonderen Fall der Verkettung sequentieller Anforderungen geben: Sie laden ein Anwendungspaket, und dieser Code fordert bereits Seitendaten an.
Es gibt zwei Möglichkeiten, dies zu vermeiden:
- Betten Sie Seitendaten in ein HTML-Dokument ein
- Fordern Sie Daten über ein Inline-Skript im Dokument an
Durch das Einbetten von Daten in HTML wird sichergestellt, dass Ihre Anwendung nicht auf das Laden warten muss. Es reduziert auch die Gesamtkomplexität der Anwendung, da der Ladezustand nicht verarbeitet werden muss.
Diese Idee ist jedoch nicht so gut, wenn das Abrufen von Daten zu einer erheblichen Verzögerung bei der Antwort Ihres Dokuments führt, da dies auch das anfängliche Rendern verlangsamt.
In diesem Fall oder wenn Sie ein zwischengespeichertes HTML-Dokument mit einem Servicemitarbeiter bereitstellen, können Sie ein Inline-Skript in das HTML einbetten, das diese Daten lädt. Es kann wie folgt als globales Versprechen gegeben werden:
window.userDataPromise = fetch("/me")
Wenn die Daten bereits bereit sind, kann Ihre Anwendung sofort mit dem Rendern beginnen oder warten, bis sie bereit ist.
Wenn Sie beide Methoden verwenden, müssen Sie genau wissen, welche Daten auf der Seite angezeigt werden sollen, und noch bevor die Anwendung mit dem Rendern beginnt. Dies ist normalerweise einfach für benutzerspezifische Daten (Name, Benachrichtigungen ...) bereitzustellen, jedoch nicht einfach für den Umgang mit seitenspezifischen Inhalten. Versuchen Sie, die wichtigsten Seiten selbst hervorzuheben und für jede Seite eine eigene Logik zu schreiben.
Blockieren Sie das Rendern nicht, während Sie auf irrelevante Daten warten
Manchmal erfordert das Generieren von ausgelagerten Daten eine langsame und komplexe Logik, die im Backend implementiert ist. In solchen Fällen ist die Möglichkeit, zuerst eine vereinfachte Version der Daten zu laden, praktisch, wenn dies ausreicht, um Ihre Anwendung funktionsfähig und interaktiv zu machen.
Beispielsweise kann ein Analysetool zuerst alle Diagramme laden und sie dann mit Daten begleiten. Auf diese Weise kann der Benutzer sofort das Diagramm anzeigen, an dem er interessiert ist, und Sie haben Zeit, die Backend-Anforderungen auf verschiedene Server zu verteilen.
Vermeiden Sie Ketten sequentieller Datenabfragen
Dieser Rat scheint meinem vorherigen Punkt zu widersprechen, in dem ich darüber gesprochen habe, das Laden irrelevanter Daten auf eine zweite Anfrage zu verschieben. Vermeiden Sie jedoch die Verkettung sequentieller Anforderungen, wenn eine nachfolgende Anforderung in der Kette dem Benutzer keine neuen Informationen liefert.
Anstatt zuerst zu fragen, was ein Benutzer angemeldet ist, und dann nach einer Liste der Gruppen zu fragen, zu denen der Benutzer gehört, geben Sie die Liste der Gruppen zusammen mit Informationen über den Benutzer zurück. Sie können hierfür GraphQL verwenden , aber ein benutzerdefinierter Endpunkt ist auch in
user?includeTeams=trueOrdnung.
Serverseitiges Rendern
In diesem Fall meinen wir das vorherige Rendern der Anwendung auf dem Server, sodass eine vollwertige HTML-Seite als Antwort auf eine Anforderung aus einem Dokument bereitgestellt wird. So kann der Client die gesamte Seite sehen, ohne auf das Laden von zusätzlichem Code oder zusätzlichen Daten warten zu müssen!
Da der Server nur statisches HTML an den Client sendet, ist Ihre Anwendung zu diesem Zeitpunkt noch nicht interaktiv. Die Anwendung muss geladen werden, sie muss die Renderlogik erneut ausführen und dann die erforderlichen Ereignis-Listener an das DOM anhängen.
Verwenden Sie serverseitiges Rendering, wenn Sie feststellen, dass nicht interaktive Inhalte für sich genommen wertvoll sind. Dieser Ansatz hilft auch dabei, den dort auf dem Server angezeigten HTML-Code zwischenzuspeichern und ihn dann unverzüglich an alle Benutzer zu übertragen, wenn das Dokument zum ersten Mal angefordert wird. Zum Beispiel ist das serverseitige Rendern großartig, wenn Sie ein Blog mit React rendern.
Lesen Sie diesen Artikel von Michal Janaszek; Es wird gut beschrieben, wie Servicemitarbeiter mit serverseitigem Rendering kombiniert werden.
Nächste Seite
Irgendwann muss der Benutzer, der mit Ihrer Anwendung arbeitet, zur nächsten Seite wechseln. Wenn die erste Seite geöffnet ist, haben Sie die Kontrolle über alles, was im Browser geschieht, damit Sie sich auf die nächste Interaktion vorbereiten können.
Vorabrufen von Ressourcen Durch
Vorabrufen des Codes, der zum Anzeigen der nächsten Seite erforderlich ist, können Verzögerungen bei der benutzerdefinierten Navigation vermieden werden. Verwenden Sie Tags
prefetch linkoder webpackPrefetchfür dynamische Importe:
import(
/* webpackPrefetch: true, webpackChunkName: "todo-list" */ "./TodoList"
)
Überlegen Sie, wie viele Benutzerdaten Sie verwenden und wie groß die Bandbreite ist, insbesondere wenn es um mobile Verbindungen geht. In der mobilen Version der Site können Sie nicht eifrig mit dem Vorladen sein, und auch wenn der Datenspeichermodus aktiviert ist.
Wählen Sie strategisch die Daten aus, die Ihre Benutzer am meisten benötigen.
Verwenden Sie die bereits geladenen
Daten erneut. Zwischenspeichern Sie die Ajax-Daten in Ihrer Anwendung lokal, um unnötige Anforderungen später zu vermeiden. Wenn der Benutzer zur Liste der Gruppen auf der Seite Gruppe bearbeiten navigiert, kann der Übergang sofort erfolgen, indem die bereits zuvor ausgewählten Daten wiederverwendet werden.
Bitte beachten Sie, dass dies nicht funktioniert, wenn Ihr Objekt häufig von anderen Benutzern bearbeitet wird und die von Ihnen hochgeladenen Daten schnell veraltet sein können. Versuchen Sie in solchen Fällen, die vorhandenen Daten zuerst im schreibgeschützten Modus anzuzeigen und in der Zwischenzeit die aktualisierten Daten auszuwählen.
Fazit
In diesem Artikel haben wir eine Reihe von Faktoren untersucht, die eine Seite an verschiedenen Stellen des Ladevorgangs verlangsamen können. Verwenden Sie Tools wie Chrome DevTools , WebPageTest und Lighthouse, um festzustellen, welche Tipps für Ihre Anwendung relevant sind.
In der Praxis ist eine umfassende Optimierung selten möglich. Bestimmen Sie, was für Ihre Benutzer am wichtigsten ist, und konzentrieren Sie sich darauf.
Als ich an diesem Artikel arbeitete, stellte ich fest, dass ich die tief verwurzelte Überzeugung teile, dass mehrere Abfragen schlechte Leistungsprobleme darstellen. Dies war in der Vergangenheit der Fall, als für jede Anforderung eine separate Verbindung erforderlich war und die Browser nur wenige Verbindungen pro Domäne zuließen. Dieses Problem verschwand jedoch mit dem Aufkommen von HTTP / 2 und modernen Browsern.
Es gibt starke Argumente für die Aufteilung von Abfragen. Auf diese Weise können Sie unbedingt erforderliche Ressourcen laden und zwischengespeicherte Inhalte besser nutzen, da Sie nur die geänderten Dateien neu laden müssen.