小窝装修记
前言
小窝装修其实早在 23 年 8 月就已经开始有想法了,到了 23 年 9 月底开始动工翻新了。
最开始装修时想的是依旧使用 Typecho 作为 CMS,通过 Typecho 的 Restful API 插件提供 API 接口进行前后端交互,而前端则使用 Nuxt 3 以及 Element UI 前端 UI 框架来实现,并且总体布局还是与先前主题 Cuckoo 一致保持双栏样式,但总体风格偏向于简单、纯白。但在 23 年底因为期末哇还有一些别的事情哇导致一度停工,到了今年一看发现装修部件 Nuxt 3 的版本都落后很多了(悲)。
于是到了今年 2 月花了些时间又完全重新规划和装修,但这次与初版装修有所不同,这次使用的是 Nuxt Content v3 作为 CMS,前端框架选用 Shadcn-vue,并且改成了个人主页和文章整合在一起,虽然装修后还有很多地方有些欠缺,比如暗色模式、标签、灯箱、表情包啥的,就像是装修后还缺少点家具似的 hhh,待后续慢慢丰富起来吧。
(起初想着原来在 Typecho 的评论很多难迁移,但是看到 Artalk 支持 Typecho 数据迁移所以就没啥顾虑了,直接开工力!)
Nuxt Content
选用 Nuxt Content 作为 CMS 也是因为看到了许多用它来重构 Blog 的文章,满足我所想要的功能,就是在折腾的时候有点难顶。因为最开始看到重构的文章都用的 v2 版本,而我用的是新出的 v3 版本。
新版本中,与旧版相比区别挺大。
- 使用
queryCollection()代替了queryContent()。 <ContentDoc>,<ContentList>,<ContentNavigation>和<ContentQuery>均被移除。
在 v3 版本中,当需要获取文章数据时只需要使用:
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
return queryCollection('post').path(route.path).first()
})
想要根据时间排序且筛选进行部分输出可以使用 order() :
const { data: page } = await useAsyncData(() => {
return queryCollection('post')
.select('path', 'title', 'description', 'date', 'category')
.order('date', 'DESC')
.all()
})
想要实现分页功能可以使用 skip() 和 limit() :
const { data: page } = await useAsyncData(() => {
return queryCollection('post')
.select('path', 'title', 'description', 'date', 'category')
.order('date', 'DESC')
.skip(5)
.limit(5)
.all()
})
对于想修改默认 HTML 排版的可以在 components/content/ 创建对应标签组件进行覆盖,详细可参考 官网文档 。若想实现特殊样式,比如警示框之类的,则同样可以在 components/content/ 创建自定义的组件,以 components/content/Callout.vue 为例,编写 Markdown 则可以通过这种方式来调用:
::callout
This is a callout.
::
当然,你可以通过这种方式开实现表情包等功能(后续看看有什么好用的方案装修上去)。
文章源建议可以自行多开一个 Github 私有库,从整体中分离出来,这样可以避免 Github Action 进行多余的部署操作(当然,也可以通过 if 来避免多余部署操作)。其中,CONTENT_REPO_TOKEN 是申请的 Github 密钥。
export default defineContentConfig({
collections: {
post: defineCollection({
type: 'page',
source: {
include: 'post/*.md',
repository: 'https://github.com/bhaoo/xxxxxxx',
authToken: process.env.CONTENT_REPO_TOKEN
},
schema: z.object({
date: z.string(),
category: z.string(),
})
}),
}
})
Sitemap
@nuxtjs/sitemap 对于 Nuxt Content v3 做了兼容,通过以下方法即可实现将文章内容加入站点地图中:
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
import { asSitemapCollection } from '@nuxtjs/sitemap/content'
export default defineContentConfig({
collections: {
post: defineCollection(
asSitemapCollection({
type: 'page',
source: {
include: 'post/*.md',
repository: 'https://github.com/bhaoo/xxxxxxx',
authToken: process.env.CONTENT_REPO_TOKEN
},
schema: z.object({
date: z.string(),
category: z.string(),
})
}),
),
}
})
Umami
(于 2025-04-03 更新)
之前还在用着百度统计但随着去年还是前年改版后就基本换到 Umami 了,在用 Cuckoo 的时候,是不通过代理的形式直接请求 Umami 的 API 接口的,此时的请求是这样的:
client → Cloudflare(Umami) → Umami
这次换到 Nuxt 后则用的是 Nuxt Umami 来实现,并且使用了简单的代理来实现。起初,发现能正常使用就没管,但这段时间发现地理位置信息有误,到了 Nginx 日志一看,请求的 IP 地址都是服务器 IP 地址,跑到 Umami 的仓库看到了这些:
- umami/src/lib/constants.ts L321-L333
export const IP_ADDRESS_HEADERS = [
'cf-connecting-ip',
'x-client-ip',
'x-forwarded-for',
'do-connecting-ip',
'fastly-client-ip',
'true-client-ip',
'x-real-ip',
'x-cluster-client-ip',
'x-forwarded',
'forwarded',
'x-appengine-user-ip',
];
- umami/src/lib/detect.ts L17-L43
export function getIpAddress(headers: Headers) {
const customHeader = process.env.CLIENT_IP_HEADER;
if (customHeader && headers.get(customHeader)) {
return headers.get(customHeader);
}
const header = IP_ADDRESS_HEADERS.find(name => {
return headers.get(name);
});
const ip = headers.get(header);
if (header === 'x-forwarded-for') {
return ip?.split(',')?.[0]?.trim();
}
if (header === 'forwarded') {
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);
if (match) {
return match[1];
}
}
return ip;
}
由于 cf-connecting-ip 放在了 IP_ADDRESS_HEADERS 首位,并且因为开启简单代理后的请求路径如下:
client → Cloudflare(Blog) → Nitro → Cloudflare(Umami) → Umami
当请求客户端到 Nitro 时,cf-connecting-ip 还是准确的客户端 IP 地址,但因简单代理的套娃后,此时的 cf-connecting-ip 则变成了 Nitro 所在服务器的 IP 地址。
因此解决的办法有两种:
第一种则是将 Nuxt Umami 的代理模式设置为 false ,即跟之前一样,直连至 Umami ,配置如下:
export default defineNuxtConfig({
umami: {
...others,
proxy: 'false',
},
});
第二种则是将 Nuxt Umami 的代理模式设置为 direct ,然后通过内网方式请求至 Umami ,配置如下:
(以 Blog 和 Umami 均在同个 Docker 网络中为例)
export default defineNuxtConfig({
umami: {
...others,
host: 'http://umami-1:3000/',
proxy: 'direct',
},
});
此时请求路径则变成:
client → Cloudflare(Blog) → Nitro → Umami
Artalk
(于 2025-04-03 更新)
Artalk 同理,如果使用的是 nitro 代理的话需要前往服务端设置 - 服务器 - 代理标头名,填写 X-Forwarded-For 以来获取用户真实 IP,Nuxt 的配置如下:
export default defineNuxtConfig({
nitro: {
devProxy: {
"/artalk": {
target: "http://artalk:3000",
changeOrigin: true,
},
},
routeRules: {
"/artalk/**": {
proxy: "http://artalk:3000/**",
},
},
},
});
部署
部署这一块已经可以看看我之前的这篇文章哦 -> Github Actions 实现 Nuxt 3 自动构建并部署
后言
总的来说装修还没完工,还在逐渐更新中。
Cuckoo 的话也不会就此停更,如果是想要看 Cuckoo Demo 的话可以点这里哦。
本文采用 CC BY-NC-SA 4.0 许可协议,允许您分享和改编,但仅限于非商业用途。转载时,请务必附上原文出处链接及上述许可声明。