EdgeOne 边缘函数 + Hono.js + Fauna 搭建个人博客

一、背景

虽然 “博客” 已经是很多很多年前流行的东西了,但是时至今日,仍然有一部分人在维护自己的博客站点,输出不少高质量的文章。

我使用过几种博客托管平台或静态博客生成框架,前段时间使用Hono.js+Fauna ,基于 EO 边缘函数搭建了一个博客网站样例,写一篇小小文章进行记录。

二、技术栈

2.1 Hono.js

Hono.js 是一个轻量、快速的 Edge Web 框架,适用于多种 JavaScript 运行时:Cloudflare Workers、Fastly Compute、Deno、Bun、Vercel、Netlify、AWS Lambda 等,同样也可以在 EO 边缘函数 Runtime 中运行起来。

在 EO 边缘函数中,最简单的 Hono 应用写法如下:

import { Hono } from 'hono'
const app = new Hono()

app.get('/', (c) => c.text('Hono!'))

app.fire();

2.2 Fauna

Fauna 作为 Cloud API 提供的分布式文档关系数据库。Fauna 使用 FQL 进行数据库查询(FQL: A relational database language with the flexibility of documents)。

因此,我准备将博客文章存放在 Fauna 中,在 Fauna JS Driver 的基础上,包装 RESTful API 供边缘函数调用。

注意:
- 目前 EO 边缘函数还不能完全支持 Fauna JS Driver,因此现阶段还不能直接使用 JS Driver 进行数据查询,需要将 Fauna API 服务搭建在其他 JS 环境中。
- EO 边缘函数后续将支持 KV,同时也会兼容 Fauna JS Driver 的写法,因此这里可以进行优化。
import { Client, fql } from 'fauna';
...

router.get('/', async c => {
  const query = fql`Blogs.all()`;
  const result = await (c.var.faunaClient as Client).query<Blog>(query);
  return c.json(result.data);
});
...

三、搭建博客

3.1 路由

博客网站的路由比较简单,可以直接使用 Hono.js 进行处理:

import { Hono } from "hono";

import { Page } from "./pages/page";
import { Home } from "./pages/home";

const app = new Hono();

...

app.get("/", async (c) => {
  const blogs = await getBlogList();

  return c.html(<Home blogs={blogs} />);
});

app.get("/blog/:id{[0-9]+}", async (c) => {
  const id = c.req.param("id");
  const blog = await getBlogInfo(id);

  if (!blog) return c.notFound();

  return c.html(<Page blog={blog} />);
});

app.fire();

3.2 页面

Hono.js 中,可以直接按照 jsx 的语法定义页面结构和样式:

import { DateTime } from "luxon";

import { Layout } from "./components/layout";

...

const Item = (props: { blog: Blog }) => {
  const { ts, id, title } = props.blog;

  const dt = DateTime.fromISO(ts.isoString);
  const formatDate = dt.toFormat("yyyy-MM-dd");

  return (
    <li>
      <section>
        <p style={{ fontSize: "14px", color: "gray" }}>{formatDate}</p>
        <a href={`/blog/${id}`}>{title}</a>
      </section>
    </li>
  );
};

const List = (props: { blogs: Blog[] }) => (
  <ul>
    {props.blogs.map((blog) => (
      <Item blog={blog} />
    ))}
  </ul>
);

export const Home = (props: { blogs: Blog[] }) => {
  const title = "Tomtomyang's Blog";
  return (
    <Layout title={title}>
      <header>
        <h1>{title}</h1>
      </header>
      <List blogs={props.blogs} />
    </Layout>
  );
};

详情页要比列表页稍微复杂一点,一方面需要将 markdown 文件转换,另一方面还需要计算生成文章目录:

import { parse } from "marked";
import { html, raw } from "hono/html";

import { DateTime } from "luxon";

import type { Blog } from "..";
import { Layout } from "./components/layout";
import { getRenderer } from "../utils/render";

const renderer = getRenderer();

const Toc = () => { ... };

const Info = (props: { author: string; time: string }) => {
  const { author, time } = props;

  const dt = DateTime.fromISO(time);
  const formatDate = dt.toFormat("yyyy-MM-dd hh:mm:ss");

  return (
    <div style={{ paddingBottom: "0.6em" }}>
      <span style={{ color: "gray" }}>{formatDate}</span>
      <span style={{ marginLeft: "10px" }}>{author}</span>
    </div>
  );
};

const Content = (props: { content: string }) => {
  return (
    <article style={{ fontSize: "16px" }}>
      {html`${raw(props.content)}`}
    </article>
  );
};

export const Page = (props: { blog: Blog }) => {
  const { title, author, content, ts } = props.blog;
  const parsedContent = parse(content, { renderer });

  return (
    <Layout title={title}>
      <header>
        <h1>{title}</h1>
      </header>
      <Info author={author} time={ts.isoString}></Info>
      <Content content={parsedContent} />
      <Toc content={parsedContent} />
    </Layout>
  );
};

3.3 缓存

在边缘构建站点的优势之一是对缓存的控制比较灵活,首先,我准备首先缓存 Fauna API 的响应结果:

首页展示所有文章列表,新增文章后,我需要在首页展示出来,因此列表接口我设置不缓存或者缓存时间很短:

export const fetchWithCache = async (url: string) => {
  try {
    return await fetch(url, {
      eo: {
        cacheTtlByStatus: {
          200: 24 * 60 * 60 * 1000,
        },
      },
    });
  } catch (err) {
    return new Response(`FetchWithCache Error: ${err.massage}`, {
      status: 500,
    });
  }
};

文章详情页展示文章的具体内容,我个人的习惯是一篇文章写完后,才进行发布,因此后续文章内容发生变动的概率较低,我选择缓存更长的时间:

export const fetchWithCache = async (url: string) => {
  try {
    return await fetch(url, {
      eo: {
        cacheTtlByStatus: {
          200: 7 * 24 * 60 * 60 * 1000,
        },
      },
    });
  } catch (err) {
    return new Response(`FetchWithCache Error: ${err.massage}`, {
      status: 500,
    });
  }
};

同时,文章详情页还有一个需要注意的点是,通过 API 获取到文章内容后,我还会计算生成 文章目录,文章内容不变,生成的文章目录肯定也不会变,因此这部分重复的计算也可以通过缓存解决掉,方式是使用边缘函数 Runtime 中的 Cache API,将 c.html(<Page blog={blog} />) 生成的 HTML 字符串进行缓存,这样就解决了 Toc 重复计算的问题:

app.get("/blog/:id{[0-9]+}", async (c) => {
  const id = c.req.param("id");

  const cache = caches.default;
  const cacheKey = getCacheKey(id);

  try {
    const cacheResponse = await cache.match(cacheKey);

    if (cacheResponse) {
      return cacheResponse;
    }

    throw new Error(
      `Response not present in cache. Fetching and caching request.`
    );
  } catch {
    const blog = await getBlogInfo(id);

    if (!blog) return c.notFound();

    const html = await c.html(<Page blog={blog} />);
    html.headers.append("Cache-Control", "s-maxage=xxxx");

    c.event.waitUntil(cache.put(cacheKey, html));

    return html;
  }
});

3.4 部署

使用 Tef-CLI 的 publish 命令,直接将开发好的代码部署到 EdgeOne 边缘节点上;或者可以将 dist/edgefunction.js 文件中的代码,粘贴到 EdgeOne 控制台 - 边缘函数 - 新建函数 编辑框中进行部署。

四、总结

经过上面的步骤,我的博客站点就搭建好了:

列表页:

详情页:

整体来看,在边缘节点上搭建一个博客站点,可以更灵活、更高效的利用和操作 CDN 缓存,对于不同类型的页面,我可以设置不同的缓存策略;边缘 Serverless + Cloud API 的部署方式,让我能足够方便的更新博客,后续随着 EO 边缘函数的不断迭代,这种玩法还有很大的升级空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/743253.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

热电发电机越来越受到研发关注

热电发电机 (TEG) 利用热量&#xff08;或更准确地说&#xff0c;温差&#xff09;和众所周知的塞贝克效应来发电。它们的应用范围从收集可用热能&#xff0c;尤其是在工业和其他情况下“浪费”的热能&#xff0c;到在放射性同位素热发电机 (RTG) 中使用航天器的放射性电源作为…

静电场的基本方程

目录 场积分方程 通量&#xff08;高斯定理&#xff09; 环量 场微分方程 散度 旋度 小结 补充知识 立体角 场积分方程 通量&#xff08;高斯定理&#xff09; 环量 场微分方程 散度 旋度 小结 补充知识 立体角

慢性病防治新策略:诊所管理系统助力健康管理变革

慢性病&#xff0c;如高血压、糖尿病等&#xff0c;正逐渐成为全球健康领域的重要挑战。据尚普咨询的数据显示&#xff0c;全球每年有4100万人死于慢性非传染性疾病&#xff0c;占全球死亡总数的71%。而在中国&#xff0c;随着经济社会发展和卫生健康服务水平的提高&#xff0c…

OpenAI突然宣布停止向中国提供API服务!

标题 &#x1f31f; OpenAI突然宣布停止向中国提供API服务! &#x1f31f;摘要 &#x1f4dc;引言 &#x1f4e2;正文 &#x1f4dd;1. OpenAI API的重要性2. 停止服务的原因分析3. 对中国市场的影响4. 应对措施代码案例 &#x1f4c2;常见问题解答&#xff08;QA&#xff09;❓…

使用AES,前端加密,后端解密,spring工具类了

学习python的时候&#xff0c;看到很多会对参数进行加密&#xff0c;于是好奇心驱使下&#xff0c;让我去了解了下AES加密如何在java中实现。 首先 npm install crypto-js 然后在你的方法中&#xff0c;给你们前端源码看看&#xff0c;因为我用的ruoyi框架做的实验&#xff…

iSCSI driver not found和Failed to start Open-iSCSI的解决方法

案例1&#xff1a;iscsi的配置有问题 方法&#xff1a;一般的处理方法为重装iscsi-initiator-utils。 案例2&#xff1a;linux安装了多个内核&#xff0c;启动所选的内核与iscsi服务不匹配 方法&#xff1a;重启系统&#xff0c;选择对应的内核版本启动系统。 &#xff08;注…

Python 围棋

效果图 完整代码 源码地址&#xff1a;Python 围棋 # 使用Python内置GUI模块tkinter from tkinter import * # ttk覆盖tkinter部分对象&#xff0c;ttk对tkinter进行了优化 from tkinter.ttk import * # 深拷贝时需要用到copy模块 import copy import tkinter.me…

机器学习课程复习——奇异值分解

1. 三种奇异值分解 奇异值分解&#xff08;Singular Value Decomposition, SVD&#xff09;包含了&#xff1a; 完全奇异值分解&#xff08;Complete Singular Value Decomposition, CSVD&#xff09;紧奇异值分解&#xff08;Tight Singular Value Decomposition, TSVD&…

赶快收藏!全网最佳 WebSocket 封装:完美支持断网重连、自动心跳!

文章目录 一、WebSocket 基础WebSocket 的基本使用 二、封装 WebSocket 客户端WebSocketClient 类使用 WebSocketClient 类解释代码实现 三、总结优点未来改进 &#x1f389;欢迎来到SpringBoot框架学习专栏~ ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff…

【复旦邱锡鹏教授《神经网络与深度学习公开课》笔记】卷积

卷积经常用在信号处理中&#xff0c;用于计算信号的延迟累积。假设一个信号发射器每个时刻 t t t产生一个信号 x t x_t xt​&#xff0c;其信息的衰减率为 w k w_k wk​&#xff0c;即在 k − 1 k-1 k−1个时间步长后&#xff0c;信息为原来的 w k w_k wk​倍&#xff0c;时刻 …

电脑图片压缩方法哪个好?这几个压缩方法必看

大家是否经常因为图片文件过大而无法轻松分享或上传而感到困扰&#xff1f;这说明你需要一款高效的图片压缩工具了。 无论是为了节省存储空间还是快速分享图片&#xff0c;拥有一款功能强大的图片压缩软件将极大地助你保存和分享图片。 今天&#xff0c;本文将介绍几款图片压…

QT自定义信号和槽函数

在QT中最重要也是必须要掌握的机制&#xff0c;就是信号与槽机制&#xff0c;在MFC上也就是类型的机制就是消息与响应函数机制 在QT中我们不仅要学会如何使用信号与槽机制&#xff0c;还要会自定义信号与槽函数&#xff0c;要自定义的原因是系统提供的信号&#xff0c;在一些情…

基于Python的求职招聘管理系统【附源码】

摘 要 随着互联网技术的不断发展&#xff0c;人类的生活已经逐渐离不开网络了&#xff0c;在未来的社会中&#xff0c;人类的生活与工作都离不开数字化、网络化、电子化与虚拟化的数字技术。从互联网的发展历史、当前的应用现状和发展趋势来看&#xff0c;我们完全可以肯定&…

前后端交互的弯弯绕绕

前后端交互&#xff1a; &#x1f197;&#xff0c;收拾一下心情让我们来聊一聊AJax吧&#xff0c;随着前端的飞速发展&#xff0c;前后的交互也发生了天翻地覆的变化&#xff1a; 前后端交互的方式有很多&#xff1a; AJAX、表单提交、WebSocket、RESTful API、... 这对新入…

九、(正点原子)Linux定时器

一、Linux中断简介 1、中断号 每个中断都有一个中断号&#xff0c;通过中断号即可区分不同的中断&#xff0c;有的资料也把中断号叫做中断线。在 Linux 内核中使用一个 int 变量表示中断号。在Linux中&#xff0c;我们可以使用已经编写好的API函数来申请中断号&#xff0c;定义…

微服务中不同服务使用openfeign 相互调用

首先 我们上文 已经知道了 nacos 的注册服务&#xff0c;现在 我们 在不同服务中相互调用就可以使用openfeign 直接调用&#xff0c;而不是 再写冗余的调用代码啦 首先 我们的微服务组件如下 因为我这个微服务是我在 员工登录demo 中 拆出来的&#xff0c;在userlogin模块中…

基于4G工业路由器的连锁品牌店铺组网监测

基于4G工业路由器的连锁品牌店铺组网监测是智慧城市建设中至关重要的任务&#xff0c;它涉及到营运管理等多方面&#xff0c;应用物联网技术可确保店铺运营的高效、安全和可靠。 连锁品牌店铺遍布城市各领域&#xff0c;甚至跨城市部署&#xff0c;分布广泛。这对集团总部的管…

HTTP/2 头部压缩 Header Compress(HPACK)详解

文章目录 1. HPACK 的工作原理1.1 静态表1.2 动态表 2. 压缩过程2.1 编码过程2.2 解码过程 3. HPACK 的优势 在HTTP1.0中&#xff0c;我们使用文本的形式传输header&#xff0c;在header中携带cookie的话&#xff0c;每次都需要重复传输几百到几千的字节&#xff0c;这着实是一…

JavaWeb——MySQL:navicate客户端工具简单使用

目录 1. 连接 2. 新建数据库 3. 使用数据库 4. 新建表 5.使用表 6. 导出数据库 我这里是英文版&#xff0c;没有进行汉化。 1. 连接 点击左上角Connection&#xff0c;选择MySQL&#xff0c;&#xff08;我连接的是自己计算机上的数据库&#xff09;连接名输入&#x…

F5《企业DNS建设白皮书》中的DNS解析服务器最佳实践

在这个数字化转型加速的时代&#xff0c;DNS&#xff08;域名系统&#xff09;的重要性不言而喻。每一次重大事件都凸显了DNS的可靠性和安全性问题。对企业而言&#xff0c;它不仅关系到业务连续性&#xff0c;更是提供永续数字服务的关键。本文根据F5公司发布的《企业DNS建设白…