博客新家:基于 next.js

自从 2017 年初把博客从 WordPress 迁移到 GitHub Pages 已经有 6 年的光景,在这些年里,Web 相关的技术发展日新月异,后端生成 HTML 的模式基本已经被抛弃,PHP、JSP 已经被扫进垃圾堆,一些编程语言已经老去,另一些则异军突起,GitHub Page 背后的 Ruby 不再闪亮,而 Web 平台则依旧长青。

我的前端技术栈从纯 HTML、CSS 和 JavaScript 开始,经历过 Vue 2,后来在 ClojureScript 环境中使用 React.js 的 Wrapper - Reagent,Reagent 是一段传奇,它在 React.js 早期的日子里做到了性能比原生 React.js + JavaScript 还要好,对日后 React.js 采用函数式组件起到了重要的影响。

我编写和维护的 CyberMe 前端项目,基于 ClojureScript 和 React.js,通过 re-frame 进行全局订阅、发布模式的状态管理(类似于 Rx),代码大概有 1w 多行。随着 JS 代码的增多,客户端渲染生成的 JS 文件越来越大,且丧失 SEO 能力意味着 SPA 单页 Web 应用的解决方案并不能覆盖所有场景。我调研了 JavaScript 世界的一些进展,也尝试了 Vite.js 和 Next.js、Tailwind CSS 等全新解决方案,但说实话,这些技术初看非常经验,实际使用也非常舒服,但深入进去就发现,它们往往在解决一部分问题的时候又带来了另外的问题,最后甚至还要考虑退回到最开始的路径是否可行。

Next.js 就是其中之一,它带来了服务端渲染,相比较纯模板模式,充分利用了 NPM 生态,大幅提升了非 SPA 应用场景下网页的开发效率和使用体验。配合基于 Rust 实现的工具链,开发环境稳定并且快速,非常好使。我在搭建新的博客站点的时候,大部分碰到的是 JavaScript 语言表现力不足以灵活操纵数据所带来的,以及对 Next.js 内部运行机制不熟悉导致的碰壁(典型的就是把仅服务端函数在客户端调用 —— 往往表现为报找不到 fs 模块的错误)。

Next.js 提供的其他特性,比如图像延迟加载和优化,路由跳转管理,SSG 数据生成等体验下来都很不错,但也并不是不可缺少。问题在于,它缺乏和多种场景其他工具协作的正交性,而这些功能,基于模板也可以做:缓存、反向路由,并且更透明,更容易适配到各种应用场景。从整套博客迁移下来,我由开始的激动到兴奋,逐步冷静下来,Next.js 的应用领域窄,但在这个领域:需要 SEO 的需要模板生成场景的建站场合,它做的确实很不错,React.js 即便不用来管理大量状态,其生态和 JS、CSS 组件化切实的能够改善大型站点的开发流程,甚至其还包括一些附加服务:从 Github 仓库一键部署到 Vercel,切切实实提升了个人开发者和小团队的效率。

Tailwind CSS 也是如此,它解决了很多问题,但并不完美,其需要配合 IDEA 或者 VSCode 插件的补全使用,加上略微翻一下文档,才能很轻松的修改样式,Tailwind 的原子化 CSS 并不是对 CSS 模块化的背叛,其只是打破了传统意义上的 CSS 模块 —— 通过约定双下划线和双横线的 CSS 模式库,但却很能够适应 CSS Module 或 CSS In JSX 等 JS 模块化方案,从这一点来说,它是成功的。只需要费很少的力气就能打造出还算不错的界面。

总的来说,Next.js + React.js 类似于 SwiftUI,在特定领域表现非常棒,虽然存在着不透明的黑盒,但使用起来很舒服。尽管如此,我大部分时间还是会写 ClojureScript + Reagent(React.js) 的 SPA,甚至在一些情况下直接将 Flutter APP 通过 WASM 部署到 Web,尽管 Clojure 和 Dart 看起来像是两种极端,但这种选择考虑的更多的是适用性,Flutter 远不如 SwiftUI 好用,代码冗余难读,运行效率一般,但 Flutter 程序可以轻松接驳 Rust,嵌套在 Android 和 iOS 设备中,和 Kotlin 与 Swift 基本无缝交互,这种适用性还是非常让人称赞的。至于 Clojure,它还是我通往 JVM 和 JS 平台和生态的入口,足够简单,普适,从日常脚本(SCI)到 Web 平台,再到长期运行的 JVM,它的普适性都很好。

可以说,对于 Flutter 的投资允许我在任何设备基本高效的提供 GUI 界面,对于 Clojure 的投资允许我享受到 JVM 变革(新的 FFI 和协程,以及亚毫秒级别的垃圾回收停顿,JDK 20)和 Web 变革(NPM 生态)的优势,Rust 也是如此。

即便未来出现了新的平台和语言,随手写一个 Clojure 解释器来访问这些新秀并不是困难的事情,考虑到 Clojure 现在已经有稳定的在 Clojure, ClojureCLR, ClojureScript, ClojureDart 实现,扩充一个 ClojureRust 和 ClojureSwift 也未尝不可 —— 总的来说,当使用了大量的编程语言,就会对软件充满戒备,包括那些闭源的,以及废弃的开源语言和工具,被抛弃是每个软件和编程语言生态不得不面临的问题,而充斥着 BUG 和缺陷的生态,总是会加速使用者的离开,最后,可能回想起年长者早年的劝诫:要有属于自己的代码。