我们应该了解的前端缓存

我们应该了解的前端缓存

开发人员可以使用多个 headers 来操纵缓存行为。

旧规范与新规范混合在一起:它需要配置许多设置,并且多个用户可能会报告不一致的行为。

在本文中,我将重点解释代理服务器中不同 headers 如何影响浏览器缓存以及它们之间的关系。

对于单页应用程序,它实现无限期地缓存 CSS、字体和图像文件,并防止缓存 HTML 文件和适用的 service worker。该策略作为资源文件是可行的。它们的文件名具有唯一的标识符。

我们可以在 webpack 中对我们的资源文件名中添加hash或者chunkhash来执行相同的缓存配置,这种技术称为长期缓存。

但是,如果我们的网站阻止新的下载,我们该如何更新呢?为了保持站点的更新能力,永远不要缓存 HTML 文件是非常重要。

每次我们访问我的网站时,我们的浏览器都会显示来自服务器的新 HTML 副本,并且仅当有新的 src 脚本或 href 链接时,浏览器才会从服务器下载新资源。

检查缓存

Cache-Control: no-store

如果我们的网站不需要登录,我们的浏览器不应在请求中记录任何内容。我们可以将其用于 HTML 脚本和 Service Worker。

Cache-Control:  public、 no -cache

## 或者

Cache-Control: public, max-age=0, must-revalidate

这两者是等效的,尽管为无缓存,但允许我们提供缓存响应,除非浏览器不检查缓存是否是最新的。

如果正确设置 ETag 或 Last-Modified 标头,浏览器可以检查缓存是否已经是最新的。该版本可帮助我们和我们的用户节省带宽。我们可以将其用于 HTML 和 Service Worker 脚本。

Cache-Control: private, no-cache

## 或者

Cache-Control: private, max-age=0, must-revalidate

同样,这两者也是等价的。公共和私有之间的区别在于共享缓存(例如CDN)可以缓存公共响应,但不能缓存私有响应。

本地缓存(例如浏览器)仍可能缓存私有响应。当我们在服务器上渲染 HTML 并且渲染的 HTML 包含敏感或用户特定信息时,我们可以使用私有。

Cache-Control: public, max-age=31536000, immutable

例如,浏览器将根据 max-age 指令(606024*365)将响应存储一年。

immutable 指令告诉浏览器不得修改此响应(文件)的内容,并且浏览器不得通过发送 If-None-Match(ETag 验证)或 If-Modified-Since(上次修改验证)来验证其缓存。

这用于我们的静态资源以支持长期缓存策略

Pragma和Expires

Pragma: no-cache
Expires: 

Pragma 是一个旧标头,在HTTP/1.0规范中定义为请求标头。

后来变成了HTTP/ 1.1 的指定“Pragma: no-cache”应该被视为“Cache-Control: no-cache”,但这并不是一个可靠的替代品,因为它始终充当请求标头。

对于 HTML 文件,可以禁用 Expires 标头或将其设置为过去的日期。对于静态资源,可以在 Nginx 过期指令中使用 cache-control-max-age 来管理它们。

ETag: W/"5e15153d-120f"

## 或者

ETag: "5e15153d-120f"

ETag 必须唯一地标识资源,并且在大多数情况下,Web 服务器会根据资源的内容生成数字指纹。

如果资源发生变化,ETag 值就会不同。

ETag 有两种类型。低 ETag 相等性表示资源在语义上是等效的。ETag 的良好验证意味着资源逐字节相同。

我们可以通过为弱 ETag 定义的“W/”前缀来区分两者。

弱ETag不适合字节范围查询,但在操作过程中很容易生成。

实际上,我们不会配置 ETag 并将其保留在我们的 Web 服务器上。

curl -I 
curl -I -H "Accept-Encoding: gzip" 

可以看到,当请求静态文件时,Nginx设置了一个强ETag。如果启用了 gzip 压缩但未上传压缩文件,则即时压缩将导致 ETag 较弱。

如果浏览器使用带有缓存资源 ETag 的 If-None-Match 请求标头,它将等待带有新资源的 200 OK 响应或空的 304 Unmodified 响应,表明我们应该使用缓存资源来下载新的一个。

同样的优化可以应用于GET API响应,并且不限于静态文件。

如果我们的应用程序接收大量JSON有效负载,我们可以将后端配置为根据有效负载的内容计算和配置 ETag。

在发送给客户端之前,将其与 If-None-Match 请求标头进行比较。

如果匹配,则发送 304 Unmodified 而不是有效负载,以节省带宽并提高 Web 应用程序性能。

Last-Modified

Last-Modified: Tue, 07 Jan 2023 23:33:17 GMT

最后修改的响应标头是另一种缓存控制机制,并使用最后修改的日期。Last Modified 标头是更准确 ETag 的替代机制。

当我们发送带有缓存资源的上次修改日期的 If-Modified-Since 请求标头时,浏览器期望带有更新资源的 200 OK 响应或指示使用缓存资源的 304 响应 Empty Unmodified,而不是下载新的一个。

Nginx 配置

现在我们已经了解了不同类型的缓存头的作用,是时候专注于将我们的知识付诸实践了。

以下 Nginx 配置适用于旨在支持长期缓存的单页面应用程序。

gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

首先,我对最能从单页应用程序中受益的内容类型启用了 GZIP 压缩。有关每个可用 gzip 配置的更多详细信息,请参阅 Nginx gzip 模块文档。

location ~* (\.html|\/sw\.js)$ {
  expires -1y;
  add_header Pragma "no-cache";
  add_header Cache-Control "public";
}

将所有 HTML 文件与服务工作脚本 /sw.js 进行比较,两者都不应该被缓存。

location ~* \.(js|css|png|jpg|jpeg|gif|ico|json)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

最大化缓存所有静态元素(即 JavaScript 文件)、CSS 文件、图像和静态 JSON 文件。如果我们托管自己的字体文件,也可以添加它们。

location / {
  try_files $uri $uri/ =404;
}


if ($host ~* ^www\.(.*)) {
  set $host_without_www $1;
  rewrite ^(.*) https://$host_without_www$1 permanent;
}

这两个与缓存无关,但却是 Nginx 配置的重要组成部分。

由于现代单一源有漂亮的 URL — 它支持路由,而我们的静态服务器不知道它。需要为每个非静态文件的路径提供默认的index.html 文件。

对于不带 www 的 URL。如果我们将应用程序托管在服务提供商已经为我们提供服务的地方,则可能不需要后者。

express配置

有时我们无法通过 Nginx 等反向代理服务器提供静态文件。

在这种情况下,我们可能需要使用像 Expresse 这样的服务器来提供静态文件。

import express, { Response } from "express";
import compression from "compression";
import path from "path";

const PORT = process.env.PORT || 3000;
const BUILD_PATH = "public";

const app = express();

function setNoCache(res: Response) {
  const date = new Date();
  date.setFullYear(date.getFullYear() - 1);
  res.setHeader("Expires", date.toUTCString());
  res.setHeader("Pragma", "no-cache");
  res.setHeader("Cache-Control", "public, no-cache");
}

function setLongTermCache(res: Response) {
  const date = new Date();
  date.setFullYear(date.getFullYear() + 1);
  res.setHeader("Expires", date.toUTCString());
  res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
}

app.use(compression());
app.use(
  express.static(BUILD_PATH, {
    extensions: ["html"],
    setHeaders(res, path) {
      if (path.match(/(\.html|\/sw\.js)$/)) {
        setNoCache(res);
        return;
      }

      if (path.match(/\.(js|css|png|jpg|jpeg|gif|ico|json)$/)) {
        setLongTermCache(res);
      }
    },
  }),
);

app.get("*", (req, res) => {
  setNoCache(res);
  res.sendFile(path.resolve(BUILD_PATH, "index.html"));
});

app.listen(PORT, () => {
  console.log(`Server is running http://localhost:${PORT}`);
});

该脚本模仿了我们的 Nginx 设置的功能。使用压缩中间件启用 gzip。

Express Static 中间件为我们设置 ETag 和 Last-Modified 标头。如果请求与任何已知的静态文件都不匹配,我们应该自己发送index.html。

你可能感兴趣的:(http,前端,缓存)