Next.js 提供了 Fast-Refresh 能力,它可以为您对 React 组件所做的编辑提供即时反馈。

但是,当你通过 Markdown 文件提供网站内容时,由于 Markdown 不是 React 组件,热更新将失效。

怎么做

解决该问题可从以下几方面思考:

  1. 服务器如何监控文件更新
  2. 服务器如何通知浏览器
  3. 浏览器如何更新页面
  4. 如何拿到最新的 Markdown 内容
  5. 如何与 Next.js 开发服务器一起启动

监控文件更新

约定: markdown 文件存放在 Next.js 项目根目录下的 _contents/

通过 node:fs.watch 模块递归的监控 _contents 目录,当文件发生变更,触发 listener 执行。

新建文件 scripts/watch.js 监控 _contents 目录。

const { watch } = require('node:fs');

function main(){
watch(process.cwd() + '/_contents', { recursive: true }, (eventType, filename) => {
console.log(eventType, filename)
});
}

通知浏览器

服务端通过 WebSocket 与浏览器建立连接,当开发服务器发现文件变更后,通过 WS 通知浏览器更新页面。

浏览器需要知道被更新的文件与当前页面所在路由是否有关,因此,服务端发送给浏览器的消息应至少包含当前

更新文件对应的页面路由。

WebSocket

ws 是一个简单易用、速度极快且经过全面测试的 WebSocket 客户端和服务器实现。通过 ws 启动 WebSocket 服务器。

const { watch } = require('node:fs');
const { WebSocketServer } = require('ws') function main() {
const wss = new WebSocketServer({ port: 80 })
wss.on('connection', (ws, req) => {
watch(process.cwd() + '/_contents', { recursive: true }, (eventType, filename) => {
const path = filename.replace(/\.md/, '/')
ws.send(JSON.stringify({ event: 'markdown-changed', path }))
})
})
}

浏览器连接服务器

新建一个 HotLoad 组件,负责监听来自服务端的消息,并热实现页面更新。组件满足以下要求:

  1. 通过单例模式维护一个与 WebSocekt Server 的连接
  2. 监听到服务端消息后,判断当前页面路由是否与变更文件有关,无关则忽略
  3. 服务端消息可能会密集发送,需要在加载新版本内容时做防抖处理
  4. 加载 Markdown 文件并完成更新
  5. 该组件仅在 开发模式 下工作
import { useRouter } from "next/router"
import { useEffect } from "react" interface Instance {
ws: WebSocket
timer: any
} let instance: Instance = {
ws: null as any,
timer: null as any
} function getInstance() {
if (instance.ws === null) {
instance.ws = new WebSocket('ws://localhost')
}
return instance
} function _HotLoad({ setPost, params }: any) {
const { asPath } = useRouter()
useEffect(() => {
const instance = getInstance()
instance.ws.onmessage = async (res: any) => {
const data = JSON.parse(res.data)
if (data.event === 'markdown-changed') {
if (data.path === asPath) {
const post = await getPreviewData(params)
setPost(post)
}
}
}
return () => {
instance.ws.CONNECTING && instance.ws.close(4001, asPath)
}
}, [])
return null
} export function getPreviewData(params: {id:string[]}) {
if (instance.timer) {
clearTimeout(instance.timer)
}
return new Promise((resolve) => {
instance.timer = setTimeout(async () => {
const res = await fetch('http://localhost:3000/api/preview/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
})
resolve(res.json())
}, 200)
})
} let core = ({ setPost, params }: any)=>null if(process.env.NODE_ENV === 'development'){
console.log('development hot load');
core = _HotLoad
} export const HotLoad = core

数据预览 API

创建数据预览 API,读取 Markdown 文件内容,并编译为页面渲染使用的格式。这里的结果

应与 [...id].tsx 页面中 getStaticProps() 方法返回的页面数据结构完全一致,相关

逻辑可直接复用。

新建 API 文件 pages/api/preview.ts

import type { NextApiRequest, NextApiResponse } from 'next'
import { getPostData } from '../../lib/posts' type Data = {
name: string
} export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
if (process.env.NODE_ENV === 'development') {
const params = req.body
const post = await getPostData(['posts', ...params.id])
return res.status(200).json(post)
} else {
return res.status(200)
}
}

更新页面

页面 pages/[...id].tsx 中引入 HotLoad 组件,并传递 setPostData()paramsHotLoad 组件。

...
import { HotLoad } from '../../components/hot-load' const Post = ({ params, post, prev, next }: Params) => {
const [postData, setPostData] = useState(post) useEffect(()=>{
setPostData(post)
},[post]) return (
<Layout>
<Head>
<title>{postData.title} - Gauliang</title>
</Head>
<PostContent post={postData} prev={prev} next={next} />
<BackToTop />
<HotLoad setPost={setPostData} params={params} />
</Layout>
)
} export async function getStaticProps({ params }: Params) {
return {
props: {
params,
post:await getPostData(['posts', ...params.id])
}
}
} export async function getStaticPaths() {
const paths = getAllPostIdByType()
return {
paths,
fallback: false
}
} export default Post

启动脚本

更新 package.jsondev 脚本:

"scripts": {
"dev": "node scripts/watch.js & \n next dev"
},

总结

上述内容,整体概述了大致的实现逻辑。具体项目落地时,还需考虑一些细节信息,

如:文件更新时希望能够在命令行提示更的文件名、针对个性化的路由信息调整文件与路由的匹配逻辑等。

Next.js 博客版原文:https://gauliang.github.io/blogs/2022/watch-markdown-files-and-hot-load-the-nextjs-page/

监听 Markdown 文件并热更新 Next.js 页面的更多相关文章

  1. ORACLE清理、截断监听日志文件(listener.log)

    在ORACLE数据库中,如果不对监听日志文件(listener.log)进行截断,那么监听日志文件(listener.log)会变得越来越大,想必不少人听说过关于"LISTENER.LOG日 ...

  2. ORACLE 监听日志文件太大停止写监听日志引起数据库连接不上问题

    生产库监听日志文件太大(达到4G多),发现oracle停止写监听日志,检查参数log_file,log_directory,log_status 均正常,数据库运行也正常. 经确认确实为监听日志过大引 ...

  3. 文件下载Controller,文件夹内容监听,文件上传,运行程序通过url实现文件下载

    文件下载Controller @RequestMapping("/fileDownLoad") public ResponseEntity<byte[]> fileDo ...

  4. Oracle数据库运维:要对监听日志文件(listener.log)进行定期清理,如果不定期清理,会遇到下面一些麻烦

    原文链接: http://www.lookdaima.com/WebForms/WebPages/Blanks/Pm/Docs/DocItemDetail.aspx?EmPreviewTypeV=2& ...

  5. 监听Documents文件夹内文件发生改变

    // 当Documents内文件发生改变时,启动计时器,每秒计算一次大小,当大小不发生改变时说明传输完毕,就开始刷新. @property (nonatomic, strong) NSTimer *t ...

  6. Xlua文件在热更新中调用方法

    Xlua文件在热更新中调用方法 public class news : MonoBehaviour { LuaEnv luaEnv;//定义Lua初始变量 void Awake() { luaEnv ...

  7. Vue 事件监听实现导航栏吸顶效果(页面滚动后定位)

    Vue 事件监听实现导航栏吸顶效果(页面滚动后定位) Howie126313 关注 2017.11.19 15:05* 字数 100 阅读 3154评论 0喜欢 0 所说的吸顶效果就是在页面没有滑动之 ...

  8. Webpack多入口文件、热更新等体验

    Webpack现今流行的前端打包工具,今儿本人也来分享下自己学习体验. 一.html-webpack-plugin 实现html模板文件的解析与生成 在plugins加入HtmlWebpackPlug ...

  9. arcgis engine 监听element的添加、更新和删除事件(使用IGraphicsContainerEvents)

    IGraphicsContainerEvents Interface 如何监听 element事件? 如,当我们在Mapcontrol上添加.删除.更新了一个Element后,如何捕捉到这个事件?   ...

随机推荐

  1. Unity—2D边缘检测(描边效果)

    一.ShaderLab 1.Alpha值边缘检测 根据图片的Alpha值边缘判定,向内扩一段距离做边缘,颜色设置未描边颜色: 片元着色阶段,向上下左右四个方向做检测,有一个点的透明度为0,判定为边缘: ...

  2. 2021.11.30 eleveni的水省选题的记录

    2021.11.30 eleveni的水省选题的记录 因为eleveni比较菜,eleveni决定先刷图论,再刷数据结构,同时每天都要刷dp.当然,对于擅长的图论,eleveni决定从蓝题开始刷.当然 ...

  3. 最大数maxnumber - 题解【树状数组】

    原题: 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作.语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值.限制:L不超过当前数列的长度. 2. 插入操作.语法 ...

  4. 我向PostgreSQL社区贡献的功能:空闲会话超时

    经过约八个月的努力,终于完成了 PostgreSQL 空闲会话超时断开的功能. 该功能将在版本 14 中发布. 这是我第一次向 PostgreSQL 提供功能,虽然之前也有向社区提供过补丁,但是这次整 ...

  5. 【mq】从零开始实现 mq-01-生产者、消费者启动

    MQ 是什么? MQ(Message Queue)消息队列,是基础数据结构中"先进先出"的一种数据结构. 指把要传输的数据(消息)放在队列中,用队列机制来实现消息传递--生产者产生 ...

  6. OpenHarmony 3.1 Beta版本关键特性解析——分布式DeviceProfile

    (以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 成翔 OpenAtom OpenHarmony(以下简称"OpenHarmony")作为分布式操作 ...

  7. 超耐心地毯式分析,来试试这道看似简单但暗藏玄机的Promise顺序执行题

    壹 ❀ 引 就在昨天,与朋友聊到JS基础时,她突然想起之前在面试时,遇到了一道难以理解的Promise执行顺序题.由于我之前专门写过手写promise的文章,对于部分原理也还算了解,出于兴趣我便要了这 ...

  8. Java语言的词法分析器的Java实现

    一.实验目的 1. 学会针对DFA转换图实现相应的高级语言源程序. 2. 深刻领会状态转换图的含义,逐步理解有限自动机. 3. 掌握手工生成词法分析器的方法,了解词法分析器的内部工作原理. 二.实验内 ...

  9. 详解六大国产CPU处理器

    一个执着于技术的公众号 CPU作为计算机设备的运算和控制核心,负责指令读取.译码与执行,因研发门槛高.生态构建难,被认为是集成电路产业中的"珠穆朗玛峰". 纵观全球,Intel.A ...

  10. Oracle19c单实例数据库配置OGG单用户数据同步测试

    目录 19c单实例配置GoldenGate 并进行用户数据同步测试 一.数据库操作 1.开启数据库附加日志 2.开启数据库归档模式 3.开启goldengate同步 4.创建goldengate管理用 ...