nextjs:如何将静态资源发布到 CDN
nextjs 是基于 react 的服务端同构指出框架,在使用的过程中也多多少少遇到过几个问题,其中最大的问题就是静态资源的发布了。
1. 如何基于文件内容进行 hash 命名
Next.js uses a constant generated at build time to identify which version of your application is being served. This can cause problems in multi-server deployments when next build is ran on every server. In order to keep a static build id between builds you can provide the generateBuildId function:
按照官网上的说法,每次发布都会生成新的 hash 路径,即使当前没有任何的变动。例如某次发布的路径是/_next/static/tZonUgEY-GPCEExGbFapL/pages/index.js
,那么下次的 hash 必然不是这个值。这样导致的一个问题是:如果在多台机器上发布并 build 时,会导致每次 build 产生的值不同。如果想固定某个值或者使用某个值,一个是可以先 build 完成后后再分发,或者,可以在next.config.js
中自定义generateBuildId
:
// 来自官网上的例子
// next.config.js
module.exports = {
generateBuildId: async () => {
return 'my-build-id';
}
};
npm 上也有提供相应的安装包,可以使用当前 git 提交的 hash 值作为 buildId:next-build-id。
可是这种存在的一个问题就是:即使文件没有发生变动,或者我只修改了首页的代码,发布完成后,pages 下所有的资源都需要重新加载,有用户建议使用内容的 hash 值作为每个资源的路径,但官方好像好像不太情愿,说实现起来比较困难,详情可以看这个 issue: use content hash in pages chunk name。在这条 issue 中,有用户自己实现一个插件,不过我还没用过,有兴趣的同学可以尝试下。
2. 路径的拼接规则
静态资源上传到 CDN,这是存在目前存在的最大的问题,虽然在next.config.js
中可以配置assetPrefix
字段,但实际使用起来还是非常困难。
打包后的 js 和 css,引用路由均为/_next/static
开头:
如图片中所示,带有 data-next-page 属性的,实际上访问的是.next/server/static/[hash]/pages/_app.js;不带这个属性的,访问的路径是.next/static/runtime/webpack-[hash].js
我们以 2019/09/16 提交的 nextjs 源码为例:pages_document,里面有全局脱水数据的注入,页面相关的 js 和静态资源的 js 的拼接:
// 页面相关的js
// assetPrefix为我们在next.config.js中配置的前缀
// ${buildId}即为每次打包生成的hash值,在本地环境下值为development
// _devOnlyInvalidateCacheQueryString: 变动的时间戳,正式环境中为空, _devOnlyInvalidateCacheQueryString: process.env.NODE_ENV !== 'production' ? '?ts=' + Date.now() : ''
src={assetPrefix + encodeURI(`/_next/static/${buildId}/pages${getPageFile(page)}`) + _devOnlyInvalidateCacheQueryString}
// 静态资源的js
src={`${assetPrefix}/_next/${file}${_devOnlyInvalidateCacheQueryString}`}
全局脱水数据的注入
<script
id="__NEXT_DATA__"
type="application/json"
nonce={this.props.nonce}
crossOrigin={this.props.crossOrigin || process.crossOrigin}
dangerouslySetInnerHTML={{
__html: NextScript.getInlineScriptSource(this.context._documentProps)
}}
data-ampdevmode
/>
上面的页面编译后的路径是.next/server/static/{hash}/pages/_document.js
,这些 js 读取的路径是分别由 2 个 json 文件控制的。
sever/pages-manifest.json
:加载页面相关的 js,nextjs 是服务端渲染+客户端渲染两种方式,刷新页面时使用的服务端渲染(使用server/static/{hash}/pages/
中的文件),切换路由时使用的是客户端渲染(使用static/{hash}/pages/
中的文件),这里加载的 js,是用于在路由切换时使用客户端渲染的方式;static/build-manifest.json
:加载静态资源的 js,使用static/
里除 hash 路径外的资源;
我们了解这些,主要是为了理解 js 的路径是怎样拼接完成的。
3. 如何发布静态资源到 CDN
静态资源发布到 CDN 其实很简单,只要把.next/static
下目录的资源上传上去即可。最困难的是如何替换代码中的路径。把这个目录下的静态文件上传到 CDN 后,生成的地址会变成:
<!-- 假设我们的CDN地址是 http://static.qq.com -->
<script src="http://static.qq.com/runtime/webpack-4b444dab214c6491079c.js"></script>
从第 2 部分中能看到,代码中使用assetPrefix
作为静态资源的前缀时,只是单纯的拼接到了最前面而已,拼接后的地址是:
<script src="http://static.qq.com/_next/static/runtime/webpack-4b444dab214c6491079c.js"></script>
中间多出了/_next/static
的路径,最后的结果是页面需要加载的资源和上传的资源路径不一致,就会各种 404。这里我的解决方案很简单粗暴,读取编译后的文件,然后执行 node 程序,将里面的字符替换掉:
const fs = require('fs');
// 获取文件夹中所有的文件
function readDirAll(path) {
// 获取字符串的最后一个字符
var getLastCode = function(str) {
return str.substr(str.length - 1, 1);
};
var result = []; // 存储获取到的文件
var stats = fs.statSync(path); // 获取当前文件的状态
if (stats.isFile()) {
result.push(path);
} else if (stats.isDirectory()) {
// 若当前路径是文件夹,则获取路径下所有的信息,并循环
var files = fs.readdirSync(path);
for (var i = 0, len = files.length; i < len; i++) {
var item = files[i],
itempath =
getLastCode(path) == '/' ? path + item : path + '/' + item; // 拼接路径
var st = fs.statSync(itempath);
if (st.isFile()) {
result.push(itempath);
} else if (st.isDirectory() && item !== 'cache') {
// 当前是文件夹,则递归检索,将递归获取到的文件列表与当前result进行拼接
var s = readDirAll(itempath);
result = result.concat(s);
}
}
}
return result;
}
const list = readDirAll('.next');
list.forEach(file => {
let data = fs.readFileSync(file, 'utf8');
if (file.indexOf('_document.js') > -1) {
data = data
.replace(/\/_next\//g, '/')
.replace(/static\/" \+ buildId/g, '" + buildId');
fs.writeFileSync(file, data);
console.log(file, 'success');
} else if (file.indexOf('build-manifest.json') > -1) {
data = data.replace(/static\//g, '');
fs.writeFileSync(file, data);
console.log(file, 'success');
} else if (data.indexOf('/_next/static') > -1) {
data = data.replace(/\/_next\/static\//g, '/');
fs.writeFileSync(file, data);
console.log(file, 'success');
}
});
这样就能就可以保证项目的 CDN 地址和真正上传的地址是一致的了。
欢迎访问蚊子的前端博客: https://www.xiabingbao.com
欢迎关注蚊子的公众号:
nextjs:如何将静态资源发布到 CDN的更多相关文章
- nginx实践(一)之静态资源web服务
静态资源服务场景CDN 配置语法-文件读取(nginx优势之一sendfile) 配置语法-tcp_nopush 简单的说就是把多个包合并,一次传输给客户端 配置语法-tap_nodelay 配置语法 ...
- Nginx详解十:Nginx场景实践篇之Nginx静态资源场景配置
一.静态资源WEB服务 1.静态资源类型:非服务器动态运行生成的文件 2.静态资源服务场景-CDN 假设静态资源存储中心在云南,用户在北京去请求一个文件,那么就会造成一个传输的延时,而如果Nginx同 ...
- Nginx之静态资源WEB服务
本篇主要记录学习Nginx的静态资源WEB服务的几种常见的功能记录学习 Nginx开发常用的命令 nginx -tc /etc/nginx/nginx.conf vim /etc/nginx/conf ...
- nginx静态资源web服务
静态资源:非服务器动态运行生成的文件 浏览器端渲染:html ,css,js 图片:jpeg,gif,png 视频:flv ,mpeg 文件:txt,等任意下载文件 静态资源服务场景:CDN 文件读取 ...
- nginx 作为静态资源web服务
Nginx作为静态资源web服务 静态资源web服务-CDN场景 Nginx资源存储中心会把静态资源分发给“北京Nginx”,“湖南Nginx”,“山东Nginx”. 然后北京User发送静态资源请求 ...
- nginx 静态资源WEB服务
1.静态资源类型 非服务器动态运行生成的文件 类型种类 浏览器端渲染 HTML.CSS.JS 图片 JPEG.GIF.PNG 视频 FLV.MPEG ...
- 学习nginx从入门到实践(五) 场景实践之静态资源web服务
一.静态资源web服务 1.1 静态资源 静态资源定义:非服务器动态生成的文件. 1.2 静态资源服务场景-CDN 1.3 文件读取配置 1.3.1 sendfile 配置语法: syntax: se ...
- 前后端分离+本地服务实时刷新+缓存管理+接口proxy+静态资源增量更新+各种性能优化+上线运维发布——gulp工作流搭建
技巧集:http://www.gulpjs.com.cn/docs/recipes/ 其实无非就是利用各种gulp插件.node脚本对项目文件做各种IO操作,只是备忘,需要的话,还是自己重新写最合适. ...
- 国内站点经常使用的一些 CDN 静态资源公共库加速服务
web开发人员们的福利来了..旨在为大家提供更快很多其它更好的静态资源库的CDN载入库方案! CDN公共库是指将经常使用的JS库存放在CDN节点,以方便广大开发人员直接调用. 与将JS库存放在serv ...
随机推荐
- 【LeetCode】215-数组中的第K个最大元素
题目描述 在未排序的数组中找到第 k 个最大的元素.请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素. 示例 1: 输入: [3,2,1,5,6,4] 和 k = 2 ...
- Appium+python自动化(三十七)- 士兵突击许三多 - 多个appium服务启动,多个设备启动,多进程并发启动设备-并发测试 - 下(超详解)
简介 接着上一篇继续看一下如何并发测试以及并发测试的过程中,可能遇到的问题,在这里宏哥把宏哥遇到的和小伙伴或者童鞋们,一起分享一下. Appium端口检测 问题思考 经过前面学习,我们已经能够使用py ...
- Tomcat运行机制
Tomcat其实就是一个servlet的容器,因此,它在运行过程中,首先要做以下事情: 1.实现servlet api规范.如request.response.cookie.session等,容器对其 ...
- 装逼手册之 python中的内存分配的小秘密
装逼手册之 python中的内存分配的小秘密 虽然我们现在得益于时代和技术的发展,不用再担心内存的问题:但是遥想当年,都是恨不得一个钢镚掰成俩份用,所以我就想深入了解一下,在python中内存分配的一 ...
- 大数据平台搭建 - cdh5.11.1 - hadoop集群安装
一.前言 由于线下测试的需要,需要在公司线下(测试)环境搭建大数据集群. 那么CDH是什么? hadoop是一个开源项目,所以很多公司再这个基础上进行商业化,不收费的hadoop版本主要有三个,分别是 ...
- Web之-----弹出确认框控件应用
引用文件!-------- <link rel="stylesheet" type="text/css" href="@Url.FrontUrl ...
- OPC—— KepServer.ServerState返回值为3和OPCConfig.exe配置文件的根目录
做开发没有对电脑的绝对管理员权限的问题,会出现很多意外,调试OPC是总是连接状态有时莫明返回3,提示 not configuration,问题在于: 没有以管理员权限运行OPCConfig.exe,导 ...
- 松软科技课堂:SQL--RIGHTJOIN关键字
发布时间:2019/3/15 9:27:31 SQL RIGHT JOIN 关键字 RIGHT JOIN 关键字会右表 (table_name2) 那里返回所有的行,即使在左表 (table_name ...
- 多线程——Thread类
进程(Process):“正在执行的程序”,程序进入内存运行就变成了一个进程.一个进程会产生多个线程. 多线程(Multithread):一个进程中同时存在几个执行体.单线程是按照函数的顺序执行,多线 ...
- ckeditor中 config.js等通过ckeditor.js引入文件手动修改方法
因为除了ckeditor.js之外的引用文件是通过ckeditor.js自动添加<script>或<link>标签实现文件的引入,引入的根目录是展示页面的地址.有时需要手动修改 ...