Googles Page Experience Update steht vor der Tür. Im kommenden Jahr wird die Ladezeit zum Rankingfaktor und damit zum Optimierungsgegenstand für Suchmaschinenoptimierer. Kai Spriestersbach zeigt in seinem Vortrag einige fortgeschrittene Methoden, mit denen er, über die bekannten Basics hinaus, die Ladezeit moderner Webseiten effektiv verbessert.
2. Research & Development bei der eology GmbH
• B.Sc. E-Commerce
• Mediengestalter IHK
• Lehrbeauftragter an der FHWS
• Inhaber von SEARCH ONE
• Aus- und Weiterbildung von Mitarbeitern
• Speaker auf SMX, SEOkomm, SEO-Day,
OMK, OMKB, etc.
Kai Spriestersbach
3. SEO aus Leidenschaft
• Online seit 1996
• Web-Design seit 2001
• Web-Entwicklung 2004
• SEO seit 2008
• Selbstständig seit 2012
Kai Spriestersbach
Matt Cutts
Head Of Web Spam, Google
Sie könnten mich kennen von
16. Deshalb ist die TTFB so wichtig!
https://www.webpagetest.org/
17. Deshalb ist die TTFB so wichtig!
}
GEFÜHLT
PASSIERT
NIX!
https://www.webpagetest.org/
18. Was passiert vor dem 1. Byte?
• Domain wird via DNS aufgelöst
• TCP-Verbindung wird aufgebaut
SYN, SYN-ACK, ACK
• TLS-Handshake für SSL-Verbindung
• Request wird an den Server gesendet
TTFB
https://www.mozilla.org/de/firefox/browsers/compare/
https://www.cloudflare.com/learning/dns/what-is-dns/
https://www.cloudflare.com/de-de/learning/ssl/what-happens-in-a-tls-handshake/
TCP
~50ms
TLS
~110ms
DNS
~50ms
19. Puh… wir warten immer noch auf das erste Byte!
• HTTP Request
• CGI-Request
• PHP-Script laden
• PHP ausführen
• Datenbank abfragen
• HTML rendern
• HTTP Response mit HTML senden
TTFB
GET / HTTP/1.1
Host: www.example.com Request /
index.php
SELECT column1, colum
FROM table_name;
Built HTML
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2020 12:28:53 GMT
Server: NGINX
Content-Length: 88
Content-Type: text/html
Connection: Closed
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
20. Puh… wir warten immer noch auf das erste Byte!
• HTTP Request
• CGI-Request
• PHP-Script laden
• PHP ausführen
• Datenbank abfragen
• HTML rendern
• HTTP Response mit HTML senden
TTFB
GET / HTTP/1.1
Host: www.example.com Request /
index.php
SELECT column1, colum
FROM table_name;
Built HTML
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2020 12:28:53 GMT
Server: NGINX
Content-Length: 88
Content-Type: text/html
Connection: Closed
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
21. 1. Full Page Cache aktivieren
• Fertig generiertes HTML wird im RAM vorgehalten
• Alle unbekannten Nutzer erhalten statisches HTML
• Cookie-/Pfadbasierte Ausnahmen
• PHP, HDD & DB werden nicht benötigt!
TTFB Optimierung
Request /
Cached HTML
..zZz
RAM
22. 2. Opcode, Object In-Memory Cache + NVMe nutzen
• Schnellste SSDs (NVMe) statt drehender Platten
• Bereits vorprozessierter PHP-Code wird gespeichert
• Häufig benutze Objekte/Fragmente werden persistent gespeichert
• Häufige Datenbankabfragen werden im RAM gehalten
• Beschleunigt selbst dynamische Webseiten!
TTFB Optimierung
23. 3. CDN verwenden
• Schnellster DNS + AnyCast IP
• Hosting in weltweiten Datencentern = Nah am Kunden
• Argo Routing = Schnellster Weg
• Seiten liegen bereits fertig gerendert im CDN
• On-The-Fly Content Optimierung möglich (Bilder, Kompression, etc.)
TTFB Optimierung
49. So bitte nicht!
CSS-Datei herunterladen
CSS prozessieren
DNS Auflösung
TCP-Verbindungsaufbau
SSL-Handshake
WebFont herunterladen
WebFont rendern
DNS Auflösung
TCP-Verbindungsaufbau
SSL-Handshake
Text anzeigen
HTML geladen
50. Das können wir uns sparen
CSS-Datei herunterladen
CSS prozessieren
DNS Auflösung
TCP-Verbindungsaufbau
SSL-Handshake
WebFont herunterladen
WebFont rendern
DNS Auflösung
TCP-Verbindungsaufbau
SSL-Handshake
Text anzeigen
HTML geladen
51. Also besser so…
CSS-Datei herunterladen
CSS prozessieren
WebFont herunterladen
WebFont rendern
Text anzeigen
HTML geladen
52. Also besser so…
CSS-Datei herunterladen
CSS prozessieren
WebFont herunterladen
WebFont rendern
Text anzeigen
HTML geladen
https://web.dev/font-display/
53. Also besser so…
CSS-Datei herunterladen
CSS prozessieren
WebFont herunterladen
WebFont rendern
Text anzeigen
HTML geladen
54. Also besser so…
CSS-Datei herunterladen
CSS prozessieren
WebFont herunterladen
WebFont rendern
Text anzeigen
HTML geladen
60. Was bringt Smart Font Reducing?
4 DNS Requests
30 HTTP-Requests
300 KB Daten
via Google Fonts Selfhosted reduced Font
0 DNS Requests
1 HTTP-Requests
20 KB Daten
70. Universal Hover-Preloader
/**
* Copyright (c) 2020 Google Inc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
const exposed = {};
if (location.search) {
var a = document.createElement("a");
a.href = location.href;
a.search = "";
history.replaceState(null, null, a.href);
}
function tweet_(url) {
open(
"https://twitter.com/intent/tweet?url=" + encodeURIComponent(url),
"_blank"
);
}
function tweet(anchor) {
tweet_(anchor.getAttribute("href"));
}
expose("tweet", tweet);
function share(anchor) {
var url = anchor.getAttribute("href");
event.preventDefault();
if (navigator.share) {
navigator.share({
url: url,
});
} else if (navigator.clipboard) {
navigator.clipboard.writeText(url);
message("Article URL copied to clipboard.");
} else {
tweet_(url);
}
}
expose("share", share);
function message(msg) {
var dialog = document.getElementById("message");
dialog.textContent = msg;
dialog.setAttribute("open", "");
setTimeout(function () {
dialog.removeAttribute("open");
}, 3000);
}
function prefetch(e) {
if (e.target.tagName != "A") {
return;
}
if (e.target.origin != location.origin) {
return;
}
var l = document.createElement("link");
l.rel = "prefetch";
l.href = e.target.href;
document.head.appendChild(l);
}
document.documentElement.addEventListener("mouseover", prefetch, {
capture: true,
passive: true,
});
document.documentElement.addEventListener("touchstart", prefetch, {
capture: true,
passive: true,
});
const GA_ID = document.documentElement.getAttribute("ga-id");
window.ga =
window.ga ||
function () {
if (!GA_ID) {
return;
}
(ga.q = ga.q || []).push(arguments);
};
ga.l = +new Date();
ga("create", GA_ID, "auto");
ga("set", "transport", "beacon");
var timeout = setTimeout(
(onload = function () {
clearTimeout(timeout);
ga("send", "pageview");
}),
1000
);
var ref = +new Date();
function ping(event) {
var now = +new Date();
if (now - ref < 1000) {
return;
}
ga("send", {
hitType: "event",
eventCategory: "page",
eventAction: event.type,
eventLabel: Math.round((now - ref) / 1000),
});
ref = now;
}
addEventListener("pagehide", ping);
addEventListener("visibilitychange", ping);
addEventListener(
"click",
function (e) {
var button = e.target.closest("button");
if (!button) {
return;
}
ga("send", {
hitType: "event",
eventCategory: "button",
eventAction: button.getAttribute("aria-label") || button.textContent,
});
},
true
);
var selectionTimeout;
addEventListener(
"selectionchange",
function () {
clearTimeout(selectionTimeout);
var text = String(document.getSelection()).trim();
if (text.split(/[snr]+/).length < 3) {
return;
}
selectionTimeout = setTimeout(function () {
ga("send", {
hitType: "event",
eventCategory: "selection",
eventAction: text,
});
}, 2000);
},
true
);
if (window.ResizeObserver && document.querySelector("header nav #nav")) {
var progress = document.getElementById("reading-progress");
var timeOfLastScroll = 0;
var requestedAniFrame = false;
function scroll() {
if (!requestedAniFrame) {
requestAnimationFrame(updateProgress);
requestedAniFrame = true;
}
timeOfLastScroll = Date.now();
}
addEventListener("scroll", scroll);
var winHeight = 1000;
var bottom = 10000;
function updateProgress() {
requestedAniFrame = false;
var percent = Math.min(
(document.scrollingElement.scrollTop / (bottom - winHeight)) * 100,
100
);
progress.style.transform = `translate(-${100 - percent}vw, 0)`;
if (Date.now() - timeOfLastScroll < 3000) {
requestAnimationFrame(updateProgress);
requestedAniFrame = true;
}
}
new ResizeObserver(() => {
bottom =
document.scrollingElement.scrollTop +
document.querySelector("#comments,footer").getBoundingClientRect().top;
winHeight = window.innerHeight;
scroll();
}).observe(document.body);
}
function expose(name, fn) {
exposed[name] = fn;
}
addEventListener("click", (e) => {
const handler = e.target.closest("[on-click]");
if (!handler) {
return;
}
e.preventDefault();
const name = handler.getAttribute("on-click");
const fn = exposed[name];
if (!fn) {
throw new Error("Unknown handler" + name);
}
fn(handler);
});
// There is a race condition here if an image loads faster than this JS file. But
// - that is unlikely
// - it only means potentially more costly layouts for that image.
// - And so it isn't worth the querySelectorAll it would cost to synchronously check
// load state.
document.body.addEventListener(
"load",
(e) => {
if (e.target.tagName != "IMG") {
return;
}
// Ensure the browser doesn't try to draw the placeholder when the real image is present.
e.target.style.backgroundImage = "none";
},
/* capture */ "true"
);
https://kai.im/preloader Danke an Daniel Abromeit @der_abro
71. Noch nicht genug gehört?
Lerne einfach alles über
PageSpeed & Web Vitals
Melde Dich jetzt unverbindlich
für mein Online Seminar an!
https://kai.im/pagespeed