2023 了,不会还要做官网吧!
随着 AIGC
的兴起,移动互联网仿佛一瞬间也不香了,吃到红利的头部应用还在稳坐泰山,后起者在各种 xx
已死的声浪中,不知所措,信心尽失。流量的走势左右了太多人的思绪,太多的决策,所以当你在 2023 年,PC
互联网已经凉到不能再凉的时候,接到一个官网的需求,你会怎么去做呢,耐心看完,希望对你有所帮助。
官网开发作为前端的传统艺能已经诞生很久了,与现在主流的 react/vue
等 csr
框架开发不同,官网对 seo
有一定要求,所以咱们的网页不再是开局一个空 html
,渲染全靠 js
了。传统的官网开发,一般采用 WordPress
建站,很好的技术栈 php + html/css/js
前后台一把梭,方便快捷,架构成熟。不过当遇到公司的前后端技术栈是 java + react
时,这个方案显然对开发人员并不友好,作为一名思维跳脱,勇于承担的前端,灵光乍现,我们是不是可以自己独立的完成整个官网呢。
在掘金上搜了搜,居然还有现成的课程,看看时间还挺新的,不如就从它入手吧。
作为网站尊贵的会员,我用兑换券换到了这门课程,开始了如饥似渴的学习。文章内容还是比较浅显易懂的,跟着操作下一个网站的雏形就出现了。课程中的技术栈主要采用 next.js + strapi
完成对官网的开发,相信各位看官并不陌生,业界知名的 react ssr
框架和 headless cms
系统,基础的介绍就不多说了,来分享点实操中得到经验教训吧。
next.js 相关
收获到的知识
1. 如何调试 next.js 服务端渲染的逻辑
之前,我只会使用 console.log
在启动的命令行里去判断代码的问题,现在我知道了使用 cross-env NODE_OPTIONS='--inspect' next dev
命令,他会开启一个 nodejs
的 inspect
调试窗口,因为 next.js
的系统对象都很复杂,层级深,如果 console.log
的话很觉得很烦人,通过如上的方式就能便捷。
2. 生成有参数的静态页面的方法
其实像官网流量挺小的,没有必要走静态导出的 ssg
,使用 ssr
也完全没有问题,但是鉴于对 k8s
扩容,服务器费用之类的知识不太了解,官网的迭代频率也很低,所以本次采用了 ssg
的方案,要求所有子页面都要生成, 这就不得不提到内置的 api
名为 getStaticPaths
了。他会在打包阶段,先从一个列表接口拿到所有的id
,再执行 getStaticProps
中的逻辑,拿到所有详情数据。比如我们的故事详情页,共有两条数据,id为1,2,执行完后,就会在对应目录下生成两个 html
得到的经验教训
1. 移动端适配
一个简易的判断移动端的代码很容易写,先不纠结完备性,大致如下:
/mobile|android|iphone|ipad|phone/i.test(window.navigator.userAgent.toLowerCase())
然而在 next.js
的 ssg
方案中你却很难拿到,因为 render
里不能有对 window
的直接使用。我想了很多取巧的方法,但是都不行,如果是 ssr
还可以用请求头的 user-agent
来判断,作为属性透传到组件中,如课程一样。但 ssg
你只能使用响应式,同时渲染2套布局,才是最简单的做法。仔细想想,因为 html
字符串的构建是在 build
的时候就已经决定了,所以不能动态的拼接成不同的 dom
也就很好理解了。
PS: 感谢 @wonderL17 指出,也可以使用nginx配置的方式,将对应路径转发到为移动端生成的页面。
如果页面复用逻辑多,且能很好支持响应式,可以使用响应式。如果完全是另一套风格,则可以使用配置ng方式去处理,更为优秀,代码会更好维护且生成的页面更小。
2. api目录的处理
next.js
中的 api
目录可以承担接口的功能,课程中在 api
层进行了一次中转,我对此呈保留意见,想法如下:
如果 strapi 不能直接支持一定的并发量,那么使用 api 来代理请求一样不能达到。
中间层越多,可能出现的协作编码问题就越多,比如 strapi 返回的数据不符合预期,有些人会去修改 strapi,有些人会去改 next.js 的 api,这样会造成项目维护的不统一。
其实简单的对 strapi
加个跨域的配置,就能很好的直接在每个组件内使用,这对于一般的官网是完全可以满足的,而且代码清晰整洁。
strapi 相关
strapi
暂时没有给我带来新的知识,因为没有对他的原理进行研究,最核心的功能,编辑类型,即可生成对应的增删改查界面是最值得学习的部分,可是也是他最成熟的部分,开发嘛,能用就行,所以只有使用上的踩坑记录。但不得不说,strapi
的模块设计的挺不错的(除开dashboard ui
定制外),有兴趣的可以了解下代码原理和设计思路。
得到的经验教训
1. 数据库(!!!!!)
请一定不要用 sqlite
,不然你会哭的。因为项目肯定是需要多人协作的,强大如你 merge
代码对你来说轻而易举,但是你会 merge db
文件吗?相信我,你不会。
所以不要等项目已成,你才想起来去换个数据库,那么你即将面临,如果将 sqlite
数据导出成 postgresql
或者 mysql
数据的问题,听上去并不难吧,我花了 1~2
天时间才把这个无意义的工作做完。虽然能查到很多方案,但开源的不好用,付费的不想试,还是老老实实导出 sql
,再导入是最快的。(不详述了,都是辛酸,你懂的,装了一堆东西,要么环境问题,要么功能问题,迟迟无法完成迁移的痛)
所以一开始就选好数据库,很关键,这样既方便了协作开发,又方便了后续使用。因为 4.x?
具体是几不清楚,relations
排序不生效(postgresql | mysql
),sqlite
没有这个问题,所以替换数据库后,升级下 strapi
,我升级的是4.10.6
,顺便可以把依赖里的 sharp
删掉,有时候要装很久。
2. OSS(!!!!)
oss
一样至关重要,如果你使用了 strapi
的媒体库功能,请一定先配置好自定义的 oss
,使用这个库就好了 strapi-provider-upload-oss
,配置起来很方便,如果你头铁,一开始就是不配,你将面临,将上传好的图片删除,再重新上传一遍,没错他没有批量替换的功能,只能手动操作,我也没有花很久,1个小时,重新换了70~80张图片(关键素材上次用完还删了)。
配置如下:
// config/plugins.js
module.exports = ({ env }) => ({
upload: {
config: {
provider: "strapi-provider-upload-oss", // full package name is required
providerOptions: {
accessKeyId: "xxxx", // 用你的 oss 配置把 xxx 换掉,但千万别上传到开源库里(如github, gitee)哦
accessKeySecret: "xxx",
region: "xxx",
bucket: "xxx",
uploadPath: "/strapi/static",
baseUrl: "xxx",
timeout: 3000,
secure: true,
},
},
},
});
3. 数据的格式化(!!!)
课程中自己定义 removeTime, removeAttrsAndId
等方法,对数据进行处理,可能写的比较早,还没有成熟的转换库,这里介绍下 strapi-plugin-transformer
,可以快速的去掉没必要的层级结构和一些属性,相当好用。配置如下:
// config/plugins.js
module.exports = ({ env }) => ({
transformer: {
enabled: true,
config: {
responseTransforms: {
removeAttributesKey: true,
removeDataKey: true,
},
},
},
});
这个操作可以避免对数据进行过多的处理,也就意味着 src/api/xx
里的代码,你基本不需要手动修改了,大大提升了后台配置的开发效率。C
端对数据的处理也更简单了
4. 一些小的注意点
config 目录下添加插件,需要创建 plugins.js 文件,少写了 s 会导致插件不生效。(PS:嗯,就是粗心的我建了 plugin.js 还怪人家插件不好使~)
.cache 还挺有用的,一些修改不生效,可以试试 build 后再重启试试。
一些拓展能力
strapi
的初始状态很难满足直接交付给运营配置,最大的坑点在于类型定义是英文的,还没有层级结构,这里参考了这篇文章提到的方案:juejin.cn/post/721922…,来对 dashboard
进行定制。功能主要分为3个步骤,patch-package
使用参考原文章即可。
类型汉化
只需要修改 admin/app.js
即可
类型层级&排序:.cache/admin/src/content-manger/pages/App/LeftMenu 文件
// 目录排序,1,1.1,2,2.1
function compareDirectories(formatter, dir1, dir2) {
// 提取目录中的数字和点号
const regex = /(\d+|\.)+/g;
const arr1 = dir1.match(regex);
const arr2 = dir2.match(regex);
if (!arr1 || !arr2) return formatter.compare(dir1, dir2);
// 比较每个部分的数字
for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
const num1 = parseFloat(arr1[i]) || 0; // 如果无法解析为数字,默认为0
const num2 = parseFloat(arr2[i]) || 0;
if (num1 < num2) {
return -1;
} else if (num1 > num2) {
return 1;
}
}
// 如果所有部分都相同,则按照长度进行比较
if (arr1.length < arr2.length) {
return -1;
} else if (arr1.length > arr2.length) {
return 1;
}
return 0; // 目录完全相同
}
// Sort correctly using the language
// 这条注释下,用目录排序的方法替代原有 sort
// SubNavLink 内,使用 dangerouslySetInnerHTML 体现目录层级
<span dangerouslySetInnerHTML={{__html: link.title.replace(/([0-9]+.)/g, (a, b, index) => {
if (index <= 1) return '';
return ' ';
})}}></span>
这样你就能得到一个这样的配置目录
项目部署
因为我这边项目是 ssg
,修改完内容是需要触发流水线重新部署的,如果像想避免这个工作,可以增加一些按钮,触发 webhook
。这样只要排版是够用的,就不需要开发介入了。
如下示例,修改的是 .cache/admin/src/pages/HomePage/index.js
以上改动想要生效,记得用 patch-package
,这也是我说他的定制模块不友好的原因~
总结
对 next.js
和 stapi
的学习,踩坑,使用经验就是这么多了,起初发起这个项目,是因为我们官网的框架有些老了,用的 fis3
,有时候法务、市场同学来找替换素材,都是没什么工作难度的事儿,浪费时间,也整的挺烦的,所以借着机会升级了一下,以后就事半功倍了。
至于在当今这个时代,官网的 seo
是否还有意义,是否还能够为公司带来不俗的自然增量,这个我最感兴趣的事儿,迟迟没能发起。
因为在公司,这是涉及很多部门(品牌,公关,法务,市场,产品,设计)的事儿,普通开发并不能调动资源,我也很期待,如果有幸能发起一个这样的项目,并不断通过技术上的优化为业务带来新的增量,那里可能会用到更多的贴合业务的 ssr
技术,到时候有机会再和大家分享下~
但我也想对技术感兴趣的朋友说,底层的技术重构也是很有魅力的一件事儿,尽管没有业务方的支持,如果你持之以恒的来做,复刻原产品,也能让这个产品在技术层面上焕然一新。
谨以此文,与君共勉!
链接:https://juejin.cn/post/7242519176853733433
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。