Browser opening process:
Let’s see what each part does, as shown above:
—- navigationStart: start time to process the requested page — redirect: start url — unload: Continue uninstalling old pages — Appcache: Check if there is an offline cache
All of the above is done locally, and the next step is to connect to the network
— DNS —- domainLookupStart: start domain lookup —- domainLookupEnd: —- responseEnd: enables a TCP connection to be used for serial downloading of js, CSS, etc. – Processing: the browser starts to work —- domLoading: the DOM is loaded into memory —- domInteractive: —- domInteractive ~ domContentLoaded: DomContentLoaded —- domContentLoaded ~ domComplete: This process does some other things, such as extracting page information, setting cache parameters, etc., and dom processing ends when domComplete — onLoad: the onLoad in the HTML tag, starts executing scripts, processing functions, etc
What we need to know is that prompt for Unload all the way up to the Processing page is always white. We can consider several points that need to be optimized according to the figure above:
- The cache
- DNS
- TCP connection time, increase bandwidth, server response speed
- Request, response speed, the smaller the amount of data, the faster the response
- Processing, optimize dom structure
Potential optimization points during browser requests
- Can the same static resource be cached?
- Can DNS cache reduce DNS query time?
- Network request process goes nearest network environment, CDN?
- Can HTTP resource request sizes be reduced?
- Reduce the number of HTTP requests
- Server side rendering
Recommend the article
From optimization to Interview Style guide – web series
1. HTTP resource caching
Objective: To improve the speed of resource loading in case of repeated access
The cache stores a copy of the output on request, such as an HTML page, image, or text, when the next request comes in: if it is the same URL, the cache directly responds to the request with a copy, rather than sending the request to the source server again.
Let’s take a look at what happens when the browser has no cache, or when the first request is made, as follows:
Here is the case with caching, as follows:
Let’s first explain what the caches in the figure above mean:
- last-modified / if-modified-since
This is a set of request/response headers
* Last-modified: Wed, 16 May 2018 02:57:16 GMT 01 Request header: if-modified-since: Wed, 16 May 2018 05:55:38 GMT
Last-modified indicates the last time the response resource was modified. If the server returns a resource with last-Modified in the header, the next time the resource is requested, the value is added to the if-Modified-since header. The server can compare this value. Determines whether the resource has changed, and if not, returns 304.
When a resource expires (using a cache-control max-age) and is found to have a last-Modified declaration, a request to the Web server is made with if-Modified-since, indicating the request time. When the Web server receives the request, it finds the if-modified-since header and compares it with the last modification time of the requested resource. If the last modification time is relatively new, it indicates that the resource has been changed again, then the whole resource content is responded (written in the response message package), HTTP 200; If the last modification time is old, it indicates that the resource has not been modified. In this case, HTTP 304 is used to inform the browser to continue using the saved cache.
- etag / if-none-match
This is also a set of request/response headers
Response headers: etag: “D5FC8B85A045FF720547BC36FC872550” request header: the if – none – match: “D5FC8B85A045FF720547BC36FC872550” etag is browser current resources in the server’s unique identifier (generate rules is determined by the server), the server returns the resource, if the head onto the etag and The next time the resource is requested, the value will be added to the if-none-match header. The server can compare this value to determine whether the resource has changed. If not, 304 will be returned.
When a resource expires (using cache-control max-age) and the resource is found to have an Etage declaration, the web server requests the resource again with if-none-match (Etag value). When the Web server receives the request, it finds if-none-match and compares it with the corresponding checksum string of the requested resource to return 200 or 304.
- expires
* expires: Thu, 16 May 2019 03:05:59 GMT
Set an expiration time in the HTTP header before which browser requests will not be issued, but will automatically read files from the cache unless the cache is emptied or forced to refresh. The drawback is that there can be inconsistencies between server time and client time, so HTTP/1.1 adds a cache-control header to fix this.
- cache-control
Set the expiration time (in seconds) within which browser requests are read directly from the cache. Cache-control takes precedence when both Expires and Cache-control are present.
These caches have the following priorities: cache-Control > Expires > etag > last-Modified
HTML files are not cached, but JS, CSS, images, and video files are cached. Previously, in webpack, we set the hash in JS and CSS, so only the HTML can be updated. When the resource file changes, the HTML will load the new resource file
// Last-modified // Defect: The timestamp of the file is changed but the content is not necessarily changed. Timestamps are only accurate to the second level, and frequently updated content will not take effect. var handle = function (req, res) { fs.stat(filename, function (err, stat) { var lastModified = stat.mtime.toUTCString(); if (lastModified === req.headers['if-modified-since']) { res.writeHead(304, "Not Modified"); res.end(); } else { fs.readFile(filename, function(err, file) { var lastModified = stat.mtime.toUTCString(); res.setHeader("Last-Modified", lastModified); res.writeHead(200, "Ok"); res.end(file); }); }}); };Copy the code
// etags var getHash = function (str) { var shasum = crypto.createHash('sha1'); return shasum.update(str).digest('base64'); }; var handle = function (req, res) { fs.readFile(filename, function(err, file) { var hash = getHash(file); var noneMatch = req.headers['if-none-match']; if (hash === noneMatch) { res.writeHead(304, "Not Modified"); res.end(); } else { res.setHeader("ETag", hash); res.writeHead(200, "Ok"); res.end(file); }}); };Copy the code
// Expires // As long as the file exists locally, requests will not be made until the expiration date. // Bug: If the user's local time is inconsistent with the server time, there is a problem with this caching mechanism. var handle = function (req, res) { fs.readFile(filename, function(err, file) { var expires = new Date(); expires.setTime(expires.getTime() + 10 * 365 * 24 * 60 * 60 * 1000); res.setHeader("Expires", expires.toUTCString()); res.writeHead(200, "Ok"); res.end(file); }); };Copy the code
// Cache-Control
var handle = function (req, res) {
fs.readFile(filename, function(err, file) {
res.setHeader("Cache-Control", "max-age=" + 10 * 365 * 24 * 60 * 60 * 1000);
res.writeHead(200, "Ok");
res.end(file);
});
};
Copy the code
Recommend the article
HTTP cache
2. DNS preloading
Reduces DNS resolution time
<link rel=" prefetch" href="//yuchengkai. Cn" <link rel="prerender" href="http://example.com"> // Lazy execution is delaying some logic until it is used. This technique can be used for first screen optimizations, and lazy execution can be used for some time-consuming logic that does not need to be used on the first screen. Lazy execution needs to be woken up, usually by calling a timer or an event.Copy the code
Recommend the article
DNS recursive query and iterative query
3. CDN
The network request process goes to the nearest network environment
4. Reduce the request packet size
4.1 Enabling Gzip Compression
Enable gzip gzip on; Gzip_min_length 1k; Gzip_comp_level 6 is recommended. Gzip_types text/plain Application /javascript application/ X-javascript text/ CSS application/ XML text/javascript application/x-httpd-php image/bmp application/x-bmp image/x-ms-bmp application/vnd.ms-fontobject font/ttf font/opentype font/x-woff; # use gzip_static on directly for resources that have already been gzip. Vary: accept-encoding (gzip gzip_vary on); Vary: accept-encoding (gzip gzip_vary on) Gzip_buffers 4 16k; Gzip_http_version 1.1; Gzip gzip_disable "MSIE [1-6]\." # Reverse proxy enable gzip_proxied expired no-cache no-store private auth;Copy the code
Recommend the article
Nginx is fully configured
4.2 Resource Optimization
To properly compress and merge resources, split the code, see the following article “Make your pages fly” 2- Resource optimization
5. Reduce the number of HTTP requests
1. Enable Keep Alive
Multiple requests reuse a TCP connection
- A persistent TCP connection saves connection creation time
- Nginx enables Keep Alive by default
Keepalive_timeout 65; Keepalive_requests 100; keepalive_requests 100;Copy the code
5.2 Performance improvements for HTTP 2
Http2 can only work with HTTPS:
Advantage:
- Binary transmission
- Request response multiplexing
- Server push
Open http2:
Generate SSL certificate:
openssl genrsa -des3 -passout pass:x -out server.pass.key 2048 openssl rsa -passin pass:x -in server.pass.key -out server.key openssl req -new -key server.key -out server.csr openssl x509 -req -sha256 -days 3650 -in server.csr -signkey server.key -out server.crtCopy the code
Push to the client in advance
Recommend the article
HTTP1.0, HTTP1.1, and HTTP2.0
6. Service workers
- Accelerated repeat access
- Offline support
Matters needing attention:
- Extended the first screen time, but reduced the total page load time
- compatibility
- This parameter can only be used with localhost or HTTPS
A Service Worker is a script that runs in the background independently of the current web page by the browser, opening the door for features that do not depend on the page or user interaction. In the future these will include push messages, background synchronization and geofencing, but the first major feature it will offer is the ability to intercept and process web requests, including programmatically managing cached responses.
See the document: segmentfault.com/a/119000001…
Chrome ://serviceworker-internals/ install Service Worker details chrome://inspect/#service-workers
Service Workers are like an interceptor between a server and a web page, intercepting HTTP requests as they come in and out, giving you complete control over your site. The main features:
- After the page is registered and installed successfully, it runs in the background of the browser and is not affected by page refresh. It can monitor and block HTTP requests of all pages within the scope.
- Websites must use HTTPS. Except when debugging with the local development environment (such as using localhost for domain names)
Run in the browser background, you can control all page requests under the scope of the open 3. Separate scope, separate runtime environment and execution thread 4. Cannot manipulate page DOM. However, event mechanisms can be used to handle 5. Event-driven service threads
If a service worker is installed on an HTTP site, it will be vulnerable to attack
For details about browser support, see caniuse.com/#feat=servi…
The Service Worker life cycle is shown below:
When the user navigates to the URL for the first time, the server returns the responding web page.
- When you call the register() function, the Service Worker starts downloading.
- During registration, the browser downloads, parses, and executes the Service Worker (). If anything goes wrong in this step, the promise returned by register() executes reject, and the Service Worker is deprecated.
- Once the Service Worker has successfully executed, the Install event is activated
- Once the installation is complete, the Service Worker is activated and controls everything within its scope. If all events in the life cycle succeed, the Service Worker is ready and ready to use.
Offline cache index.html
<! DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello Caching World! </title> </head> <body> <! -- Image --> <img src="/images/hello.png" /> <! <script async SRC ="/js/script.js"></script> <script> // Register service worker if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js', {scope: '/'}). Then (function (registration) {console.log('ServiceWorker registration successful with scope: ', registration.scope); }). Catch (function (err) {console.log('ServiceWorker registration failed: ', err); }); } </script> </body> </html>Copy the code
Note: The registered path of a Service Worker determines the scope of the default page used by its scope. If service-worker.js is in the /sw/ page path, this causes the service worker to receive only fetch events in the /sw/ page path by default. If stored at the root of a website, all fetch events for that website will be received. If you want to change its scope, set the scope in the second argument. The example changes it to the root directory, which is valid for the entire site.
service-worker.js
var cacheName = 'helloWorld'; // Install event, This occurs when the browser installs and registers the Service Worker self.addeventListener ('install', Event => {/* event.waitUtil is used to perform some pre-installed logic until the installation is successful, but it is recommended to cache only lightweight and very important resources, After the installation is successful, the ServiceWorker status changes from Installing to Installed */ Event.waituntil (caches. Open (cacheName). Then (cache => '/js/script.js', '/images/hello.png'])); cache.addall ([// If all files are successfully cached, the installation is complete. If any files fail to download, the installation process fails. }); /** Add an event listener for the FETCH event. Next, the caches.match() function is used to check that the incoming request URL matches anything currently in the cache. Returns the cached resource if it exists. If the resource does not exist in the cache, obtain the resource over the network and add the obtained resource to the cache. */ self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request) .then(function (response) { if (response) { return response; } var requestToCache = event.request.clone(); // return fetch(requestToCache).then( function (response) { if (! response || response.status ! == 200) { return response; } var responseToCache = response.clone(); caches.open(cacheName) .then(function (cache) { cache.put(requestToCache, responseToCache); }); return response; })); });Copy the code
Note: The reason for using request.clone() and Response.clone () is because request and response are a stream that can only be consumed once. Because we consumed the request once through the cache and consumed it again when making the HTTP request, we need to Clone the request — a request is a stream and can only be consumed once.
Serice Worker implements message push
- Prompt the user and get their subscription details
- Keep these details on the server
- Send any messages as needed
Step 1 and step 2 index.html
<! DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Progressive Times</title> <link rel="manifest" href="/manifest.json"> </head> <body> <script> var endpoint; var key; var authSecret; var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY'; // The method is very complicated, but we can not look at it in detail, Function urlBase64ToUint8Array(base64String) {const padding =' ='. Repeat ((4-base64String.length) 4) % % 4); const base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').then(function (registration) { return registration.pushManager.getSubscription() .then(function (subscription) { if (subscription) { return; } return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }) .then(function (subscription) { var rawKey = subscription.getKey ? subscription.getKey('p256dh') : ''; key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : ''; var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : ''; authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : ''; endpoint = subscription.endpoint; return fetch('./register', { method: 'post', headers: new Headers({ 'content-type': 'application/json' }), body: JSON.stringify({ endpoint: subscription.endpoint, key: key, authSecret: authSecret, }), }); }); }); }). Catch (function (err) {console.log('ServiceWorker registration failed: ', err); }); } </script> </body> </html>Copy the code
Step 3 The server sends a message to the service worker app.js
const webpush = require('web-push'); const express = require('express'); var bodyParser = require('body-parser'); const app = express(); webpush.setVapidDetails( 'mailto:[email protected]', 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY', 'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0' ); app.post('/register', function (req, res) { var endpoint = req.body.endpoint; saveRegistrationDetails(endpoint, key, authSecret); const pushSubscription = { endpoint: req.body.endpoint, keys: { auth: req.body.authSecret, p256dh: req.body.key } }; var body = 'Thank you for registering'; var iconUrl = 'https://example.com/images/homescreen.png'; SendNotification (pushSubscription, json.stringify ({MSG: body, url: 'http://localhost:3111/', icon: iconUrl })) .then(result => res.sendStatus(201)) .catch(err => { console.log(err); }); }); app.listen(3111, function () { console.log('Web push app listening on port 3111! ')});Copy the code
The service worker listens for push events and pushes notification details to the user service-worker.js
Self.addeventlistener ('push', function (event) {var payload = event.data? JSON.parse(event.data.text()) : 'no payload'; var title = 'Progressive Times'; Event. WaitUntil (/ / using the provided information to display Web push notification. Self registration. ShowNotification (title, {body: payload. MSG, url: payload.url, icon: payload.icon }) ); });Copy the code
7. Processing, dom structure optimization
7.1 Pre-rendered Pages
Pre-rendered page
react-snap
- Performance bottlenecks for large single-page applications: JS download + parsing + execution
- Main problems with SSR: Sacrificing TTFB to remedy First Paint; complex
- Pre-rendering renders pages before packaging, without server involvement
7.2 Using skeleton Components to reduce Layout Shift
react-placeholder
7.3 Optimize layout using Flexbox
Advantage:
- Higher performance implementation scheme
- Containers have the ability to determine the size, order, alignment, spacing, and so on of child elements
- Two-way layout
7.4 Progressive Bootstrapping
- Visible non-interactive VS minimum set of interactive resources (e.g. first screen only)
7.5 Rendering Optimization
Reference: [Make your page fly] 1- Render optimization