chore: 初始化项目仓库

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 21:27:39 +08:00
commit b7addb5c7a
50 changed files with 15964 additions and 0 deletions
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1067" height="600" viewBox="0 0 1067 600" style="max-width: 100%; height: auto; cursor: default;"><defs><pattern id="checkerboard" width="20" height="20" patternUnits="userSpaceOnUse"><rect width="10" height="10" fill="#e0e0e0"/><rect x="10" y="0" width="10" height="10" fill="#ffffff"/><rect x="0" y="10" width="10" height="10" fill="#ffffff"/><rect x="10" y="10" width="10" height="10" fill="#e0e0e0"/></pattern></defs><rect width="100%" height="100%" fill="url(#checkerboard)"/><rect width="100%" height="100%" fill="rgba(255, 255, 255, 1)"/><!----><foreignObject x="0" y="0" width="100%" height="100%" style="pointer-events: none;"><div xmlns="http://www.w3.org/1999/xhtml" style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; gap: 22px; font-family: sans-serif; font-weight: 600;"><div style="order: 1; width: 123px; height: 123px; display: flex; align-items: center; justify-content: center; background-color: transparent; backdrop-filter: none; border-radius: 0px;"><div style="max-width: 103px; max-height: 103px; flex-shrink: 0; color: rgb(0, 0, 0); filter: drop-shadow(rgba(0, 0, 0, 0) 0px 0px 0px); display: flex; align-items: center; justify-content: center; border-radius: 6%; overflow: hidden;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 185" width="100%" height="100%" preserveAspectRatio="xMidYMid meet"><path fill="#2396ed" d="M250.716 70.497c-5.765-4-18.976-5.5-29.304-3.5c-1.2-10-6.725-18.749-16.333-26.499l-5.524-4l-3.844 5.75c-4.803 7.5-7.205 18-6.485 28c.24 3.499 1.441 9.749 5.044 15.249c-3.362 2-10.328 4.5-19.455 4.5H1.155l-.48 2c-1.682 9.999-1.682 41.248 18.014 65.247c14.892 18.249 36.99 27.499 66.053 27.499c62.93 0 109.528-30.25 131.386-84.997c8.647.25 27.142 0 36.51-18.75c.24-.5.72-1.5 2.401-5.249l.961-2zM139.986 0h-26.42v24.999h26.42zm0 29.999h-26.42v24.999h26.42zm-31.225 0h-26.42v24.999h26.42zm-31.225 0H51.115v24.999h26.421zM46.311 59.998H19.89v24.999h26.42zm31.225 0H51.115v24.999h26.421zm31.225 0h-26.42v24.999h26.42zm31.226 0h-26.422v24.999h26.422zm31.225 0H144.79v24.999h26.422z"/></svg></div></div><span style="order: 0; font-size: 86px; color: rgb(0, 0, 0); text-shadow: rgba(0, 0, 0, 0.09) 0px 0px 0px; line-height: 1; white-space: nowrap;">编译 musl</span><span style="order: 2; font-size: 86px; color: rgb(0, 0, 0); text-shadow: rgba(0, 0, 0, 0.09) 0px 0px 0px; line-height: 1; white-space: nowrap;">可执行文件</span></div></foreignObject><g class="ratio-guide" style="display: none;"><rect x="0.16666666666674246" y="0" width="1066.6666666666665" height="600" fill="none" stroke="rgba(255,0,0,0.5)" stroke-width="2" stroke-dasharray="10 5"/><text x="10.166666666666742" y="30" fill="rgba(255,0,0,0.5)" font-size="20">16:9</text></g><rect x="0" y="0" width="100%" height="100%" fill="none" stroke="rgba(255,0,0,0.8)" stroke-width="2" class="canvas-border" style="display: none;"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+132
View File
@@ -0,0 +1,132 @@
---
title: Docker 编译 musl 可执行文件
published: 2025-03-27
image: /posts/docker-musl-build/img/cover.svg
description: 解决 Go 语言跨平台编译问题,使用 Docker + musl 编译静态链接的可执行文件。
tags: ['Docker', 'Golang', '运维']
draft: false
---
## 解决问题
用 Go 进行跨平台编译时往往步骤复杂,用 Docker 可以轻松解决。但 Docker 的当前最小镜像 Alpine 其动态链接库采用 musl,与主流的 glibc 不同,往往不能方便执行。
## 查阅资料
```bash
# 启动容器并挂载当前目录到 `/app`
docker run -it --rm -v "$PWD":/app -w /app ubuntu:20.04 bash
# 在容器内执行以下操作:
apt-get update && apt-get install -y musl-tools golang-go # 安装 musl 和 Go
export CC=musl-gcc # 指定 musl 编译器
export CGO_ENABLED=1 # 启用 CGO
# 设置代理
go env -w GOPROXY=https://goproxy.cn,direct
go build -ldflags '-linkmode external -extldflags "-static"' -o myapp # 静态编译
exit # 退出容器
# 验证结果(在宿主机执行)
file myapp # 应显示 "statically linked"
ldd myapp # 应显示 "not a dynamic executable"
wget https://go.dev/dl/go1.21.linux-amd64.tar.gz
ENV PATH="/data/go/bin:$PATH"
```
### 遇到问题
1. `go mod tidy` 时报错 `tls: failed to verify certificate: x509: certificate`
解决:`RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*`
基础镜像的证书未更新,导致 https 的请求出现类似错误。
![image-20250327100813512](/posts/docker-musl-build/img/image-20250327100813512.png)
2. 直接安装 `golang-go` 其版本过低
解决: `PATH="/data/go/bin:$PATH"` 手动指定环境
## 最终方案
1. 下载 `go1.24.1.linux-amd64.tar.gz` 解压
2. 编写 Dockerfile
3. 执行 `docker build -t go-build:v1 .`
Dockerfile 内容:
```dockerfile
FROM ubuntu:20.04
LABEL org.opencontainers.image.authors="sutong"
COPY go/ /go/
ENV PATH="/go/bin:$PATH"
RUN apt-get update && apt-get install -y musl-tools
## go mod tidy 时报错 tls: failed to verify certificate: x509: certificate
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
# 指定 musl 编译器
ENV CC=musl-gcc
# 启用 CGO
ENV CGO_ENABLED=1
VOLUME /data
WORKDIR /data
CMD ["/bin/bash"]
```
### 构建
```bash
# docker build -t go-build:v1 .
# 直接执行编译 编译后删除
docker run --rm -v .:/data go-build:v1 go build -o app_musl
# 构建编译环境
docker run -it -d -v .:/data --name <容器名称> go-build:v1 /bin/bash
# egdocker run -it -d -v .:/data --name logbus-build go-build:v1 /bin/bash
docker exec -it <容器名称> go build -o <myapp>
# egdocker exec -it logbus-build go build -o logbus_musl_1
```
### 额外注意
若没科学上网需要设置代理:
```bash
docker exec -it <容器名称> /bin/bash
go env -w GOPROXY=https://goproxy.cn,direct
go build -ldflags '-linkmode external -extldflags "-static"' -o myapp # 静态编译
go build # musl 库编译
```
### 运行环境示例
```dockerfile
FROM alpine:latest
LABEL org.opencontainers.image.authors="sutong"
COPY glog_musl /app/
COPY conf.ini /data/
COPY ipname.ini /data/
WORKDIR /app
RUN chmod 777 /app/glog_musl
CMD ["./glog_musl", "logsweb", "/data/conf.ini"]
# docker build -t glog:v1 .
# docker run -p 6801:6801 glog:v1
```
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1067" height="600" viewBox="0 0 1067 600" style="max-width: 100%; height: auto; cursor: default;"><defs><pattern id="checkerboard" width="20" height="20" patternUnits="userSpaceOnUse"><rect width="10" height="10" fill="#e0e0e0"/><rect x="10" y="0" width="10" height="10" fill="#ffffff"/><rect x="0" y="10" width="10" height="10" fill="#ffffff"/><rect x="10" y="10" width="10" height="10" fill="#e0e0e0"/></pattern></defs><rect width="100%" height="100%" fill="url(#checkerboard)"/><rect width="100%" height="100%" fill="rgba(255, 255, 255, 1)"/><!----><foreignObject x="0" y="0" width="100%" height="100%" style="pointer-events: none;"><div xmlns="http://www.w3.org/1999/xhtml" style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; gap: 22px; font-family: sans-serif; font-weight: 600;"><div style="order: 1; width: 123px; height: 123px; display: flex; align-items: center; justify-content: center; background-color: transparent; backdrop-filter: none; border-radius: 0px;"><div style="max-width: 103px; max-height: 103px; flex-shrink: 0; color: rgb(0, 0, 0); filter: drop-shadow(rgba(0, 0, 0, 0) 0px 0px 0px); display: flex; align-items: center; justify-content: center; border-radius: 6%; overflow: hidden;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 185" width="100%" height="100%" preserveAspectRatio="xMidYMid meet"><path fill="#2396ed" d="M250.716 70.497c-5.765-4-18.976-5.5-29.304-3.5c-1.2-10-6.725-18.749-16.333-26.499l-5.524-4l-3.844 5.75c-4.803 7.5-7.205 18-6.485 28c.24 3.499 1.441 9.749 5.044 15.249c-3.362 2-10.328 4.5-19.455 4.5H1.155l-.48 2c-1.682 9.999-1.682 41.248 18.014 65.247c14.892 18.249 36.99 27.499 66.053 27.499c62.93 0 109.528-30.25 131.386-84.997c8.647.25 27.142 0 36.51-18.75c.24-.5.72-1.5 2.401-5.249l.961-2zM139.986 0h-26.42v24.999h26.42zm0 29.999h-26.42v24.999h26.42zm-31.225 0h-26.42v24.999h26.42zm-31.225 0H51.115v24.999h26.421zM46.311 59.998H19.89v24.999h26.42zm31.225 0H51.115v24.999h26.421zm31.225 0h-26.42v24.999h26.42zm31.226 0h-26.422v24.999h26.422zm31.225 0H144.79v24.999h26.422z"/></svg></div></div><span style="order: 0; font-size: 86px; color: rgb(0, 0, 0); text-shadow: rgba(0, 0, 0, 0.09) 0px 0px 0px; line-height: 1; white-space: nowrap;">构建时</span><span style="order: 2; font-size: 86px; color: rgb(0, 0, 0); text-shadow: rgba(0, 0, 0, 0.09) 0px 0px 0px; line-height: 1; white-space: nowrap;">指定用户启动</span></div></foreignObject><g class="ratio-guide" style="display: none;"><rect x="0.16666666666674246" y="0" width="1066.6666666666665" height="600" fill="none" stroke="rgba(255,0,0,0.5)" stroke-width="2" stroke-dasharray="10 5"/><text x="10.166666666666742" y="30" fill="rgba(255,0,0,0.5)" font-size="20">16:9</text></g><rect x="0" y="0" width="100%" height="100%" fill="none" stroke="rgba(255,0,0,0.8)" stroke-width="2" class="canvas-border" style="display: none;"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

+89
View File
@@ -0,0 +1,89 @@
---
title: Docker 构建时指定用户启动
published: 2025-04-01
image: /posts/docker-user-start/img/cover.svg
description: 解决 Docker 容器默认以 root 用户运行导致的权限问题,指定非 root 用户启动应用。
tags: ['Docker', '运维', 'Linux']
draft: false
---
## 前言
我们在 Linux 使用应用时,往往不会直接使用 root 用户作为应用启动项,但 Dockerfile 默认就是 root 用户,导致挂载生成的文件路径往往是 root 权限,非 root 用户访问就很不方便。
## 解决方案
### 1. 安装用户管理工具
若镜像非自带用户管理:
```dockerfile
# 权限管理
RUN apk add --no-cache shadow
```
### 2. 创建用户并修改权限
```dockerfile
# 创建用户组和用户
RUN addgroup -S <app_user_group> && adduser -S -G <app_user_group> <app_user>
# 修改目录权限
RUN chown -R <app_user_group>:<app_user> /data
```
### 3. 指定用户启动
执行 docker 时加 `--user` 参数:
```bash
docker run -it --user <app_user> <app_image>:<image_version>
```
## 完整示例
```dockerfile
FROM alpine:latest
# 国内源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update --no-cache
# 权限管理
RUN apk add --no-cache shadow
# 调试
RUN apk add --no-cache bash
# 设置时区
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
# 创建用户组和用户
RUN addgroup -S glog && adduser -S -G glog glog
COPY ./glog_static_musl_1 /app/
RUN mkdir /data
VOLUME [ "/data" ]
WORKDIR /data
# 修改权限
RUN chown -R glog:glog /data
RUN chown -R glog:glog /app
# 指定用户启动
USER glog
CMD [ "/app/glog_static_musl_1", "logsweb", "./conf.ini" ]
```
## 总结
关键步骤:
1. `apk add --no-cache shadow` - 安装用户管理工具
2. `addgroup` + `adduser` - 创建用户
3. `chown -R` - 修改目录权限
4. `USER <username>` - 指定运行用户
这样容器内的应用就不再以 root 身份运行,更加安全。
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+93
View File
@@ -0,0 +1,93 @@
---
title: Hello World - 欢迎来到尼克的小窝
published: 2026-05-31T08:00:00
description: 博客上线啦!这篇文章介绍本站的技术架构、功能特性和搭建过程中的一些思考。
image: /posts/hello-world/img/cover.jpg
pinned: true
tags: ['博客', 'Nuxt']
draft: false
---
## 博客上线啦!
Hello World!欢迎来到**尼克的小窝**,这是我的第一篇博客文章。
经过一段时间的折腾,这个基于 Nuxt 3 的个人博客终于上线了。在这里记录一下这个项目的技术选型和功能特性。
## 技术栈
本站使用了以下技术:
| 技术 | 用途 |
|------|------|
| **Nuxt 3** | Vue 3 全栈框架,提供 SSG、路由、API 等能力 |
| **TypeScript** | 类型安全,提升开发体验 |
| **Tailwind CSS v4** | 原子化 CSS,快速构建 UI |
| **@nuxt/content** | Markdown 驱动的内容管理 |
| **Shiki** | 代码语法高亮 |
| **Giscus** | 基于 GitHub Discussions 的评论系统 |
## 功能特性
目前已实现的功能:
- **博客系统** - Markdown 文章,支持代码高亮、目录导航、搜索
- **暗色模式** - 跟随系统或手动切换
- **文章搜索** - 支持标题、描述、正文、标签多维度搜索
- **标签系统** - 文章标签分类,高频标签快速筛选
- **友链页面** - 展示友情链接,支持申请
- **赞助页面** - 微信收款码展示
- **封面制作** - 在线生成封面图片工具
- **网易云热评** - 首页随机展示音乐评论
- **访客统计** - 不蒜子计数器,带数字滚动动画
- **SEO 优化** - 自动生成站点地图、Open Graph 标签
## 代码示例
本站的配置非常集中,一个文件搞定全站定制:
```typescript
// config/site.ts
export const siteConfig = {
siteName: '尼克的小窝',
url: 'https://nixus.top/',
avatar: 'https://q2.qlogo.cn/headimg_dl?dst_uin=xxx&spec=100',
// ... 改这一个文件,全站生效
}
```
文章的 frontmatter 也很简洁:
```yaml
---
title: 文章标题
published: 2026-05-31T08:00:00
description: 文章简介
tags: ['标签1', '标签2']
---
```
## 部署
本站使用 SSG 静态生成,构建后直接部署到静态托管即可:
```bash
npx nuxi generate
```
构建产物在 `.output/public` 目录下,可以直接丢到 Nginx、Vercel、Netlify 等平台。
## 后续计划
- [ ] RSS 订阅
- [ ] 文章目录优化
- [ ] 更多小工具
- [ ] 持续输出内容
## 致谢
感谢 [二叉树树](https://2x.nz) 的开源项目,本站的 UI 设计参考了他的项目。
---
**感谢阅读!如果觉得不错,欢迎点个 Star 或者留个评论 (☞゚ヮ゚)☞**
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+162
View File
@@ -0,0 +1,162 @@
---
title: Nuxt Content v3 踩坑记录:目录、图片与那些反直觉的设计
published: 2026-05-31T14:00:00
description: 从 SvelteKit 迁移到 Nuxt 3 的过程中,遇到了不少 Nuxt Content v3 的坑。这篇文章记录了目录导航、图片路径、ProseImg 组件等问题的排查和解决过程。
image: /posts/nuxt-content-lessons/img/cover.jpg
tags: ['Nuxt', '踩坑', 'Nuxt Content']
draft: false
---
# 背景
最近在用 Nuxt 3 + Nuxt Content v3 搭建一个个人博客,参考了一个 SvelteKit 项目(svaf)的架构。原以为换个框架只是语法差异,没想到在几个关键功能上踩了不少坑。
这篇文章记录了三个核心问题的排查过程和最终方案。
# 问题一:目录导航(TOC)只有 h2
## 现象
Nuxt Content 内置了 TOC 数据,挂在 `post.body.toc` 上。直接用它渲染目录,发现**只有 h2 标题,h3 全部丢失**。
## 排查
查了官方文档,发现 `build.markdown.toc.depth` 可以控制深度:
```typescript
// nuxt.config.ts
content: {
build: {
markdown: {
toc: { depth: 3 } // 包含 h3
}
}
}
```
配置改了,清了缓存重启,**依然只有 h2**。这个配置似乎不起作用。
## 最终方案
放弃依赖 Nuxt Content 的 toc,直接从 `post.body` 的 AST 里递归提取所有标题:
```typescript
function extractHeadings(node: any, list: Heading[] = []): Heading[] {
if (!node) return list
if (Array.isArray(node)) {
// minimark 格式:["h2", {id: "xxx"}, "文本"]
if (typeof node[0] === 'string' && /^h[1-6]$/.test(node[0])) {
const level = Number(node[0].charAt(1))
const text = extractText(node.slice(2)).trim()
list.push({ id: node[1]?.id || slugify(text), text, level })
}
for (const child of node) extractHeadings(child, list)
}
return list
}
```
关键点:Nuxt Content v3 的 body 使用 **minimark** 格式,标题节点是数组 `["h2", {id: "xxx"}, "标题文本"]`,不是对象。
# 问题二:图片路径解析错误
## 现象
markdown 里用相对路径引用图片:
```markdown
![SSHFS挂载效果](img/sshfs.avif)
```
HTML 输出的 `<img src="img/sshfs.avif">` 看起来没问题,但浏览器请求的却是 `/posts/img/sshfs.avif` 而不是 `/posts/sshfs/img/sshfs.avif`
## 排查
1. 检查了 `<base>` 标签 —— 没有
2. 检查了重定向 —— 没有
3. 直接访问 `/posts/sshfs/img/sshfs.avif` —— 返回 200 OK
4. 直接访问 `/posts/img/sshfs.avif` —— 404
服务器路由没问题,问题是**浏览器解析相对路径时基于了错误的基准 URL**。
这可能和 Nuxt 的客户端路由有关:SPA 导航时,浏览器的"当前页面"和 Vue Router 的"当前路由"不同步,导致相对路径解析出错。
## 最终方案
两步解决:
**第一步:自定义 ProseImg 组件**
创建 `components/content/ProseImg.vue`,覆盖 Nuxt Content 默认的图片渲染:
```vue
<script setup>
const route = useRoute()
const resolvedSrc = computed(() => {
if (!props.src) return ''
if (props.src.startsWith('/') || props.src.startsWith('http')) return props.src
const base = route.path.replace(/\/$/, '')
return `${base}/${props.src}`
})
</script>
<template>
<img :src="resolvedSrc" :alt="alt" />
</template>
```
**第二步:启用 MDC prose 组件**
```typescript
// nuxt.config.ts
mdc: {
components: {
prose: true // 让 Nuxt Content 使用自定义 ProseImg
}
}
```
# 问题三:content 目录的图片如何服务
## 现象
原站(SvelteKit)的图片放在 `content/posts/{slug}/img/` 下,markdown 用相对路径引用,一切正常。Nuxt 项目里,content 目录的文件不会被当成静态资源服务。
## 方案对比
| 方案 | 优点 | 缺点 |
|------|------|------|
| 复制到 public/ | 简单 | 文件重复,需同步 |
| Vite 插件拦截 | 原站方案 | Nuxt 不直接支持 |
| Server Route | 不复制文件 | 需要手动写路由 |
最终选择 **Server Route**,和原站的 Vite 插件思路一致:
```typescript
// server/routes/posts/[slug]/img/[...file].ts
export default defineEventHandler(async (event) => {
const url = getRequestURL(event)
const match = url.pathname.match(/^\/posts\/([^/]+)\/img\/(.+)$/)
if (!match) return
const [, slug, filename] = match
const filePath = resolve('content/posts', slug, 'img', filename)
const data = await readFile(filePath)
setResponseHeader(event, 'content-type', MIME[ext])
return data
})
```
请求 `/posts/sshfs/img/sshfs.avif` → 读取 `content/posts/sshfs/img/sshfs.avif` → 返回图片。
# 总结
| 问题 | 根因 | 方案 |
|------|------|------|
| TOC 只有 h2 | `toc.depth` 配置不生效 | 从 body AST 手动提取 |
| 图片路径错误 | SPA 路由导致相对路径解析基准错误 | ProseImg 组件 + mdc.prose 配置 |
| content 图片 404 | content 目录不暴露给浏览器 | Server Route 直接读取返回 |
Nuxt Content v3 的设计理念是"图片放 public/,用绝对路径"。如果想像 SvelteKit 项目那样把图片和文章放一起,需要额外做不少工作。
不过一旦这些坑都踩过,整体开发体验还是很流畅的。Nuxt 的生态系统、自动导入、模块系统都比 SvelteKit 成熟很多。
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@@ -0,0 +1,75 @@
---
title: Nuxt 3 + Tailwind CSS 客户端导航链接下划线闪烁问题
published: 2026-05-31T18:47:00
image: /posts/nuxt-tailwind-underline-fix/img/cover.png
description: 记录 Nuxt 3 配合 Tailwind CSS 时,客户端导航导致链接文字出现下划线闪烁的问题及解决方案。
tags: ['Nuxt', 'Tailwind CSS', 'CSS', '踩坑']
author: Nixus
---
## 问题描述
在 Nuxt 3 项目中使用 Tailwind CSS 时,发现一个奇怪的现象:
- 首次通过客户端导航(点击 `<NuxtLink>`)跳转到某个页面时,**所有链接文字都会出现下划线**
- 手动刷新页面后,下划线消失,样式恢复正常
这个问题在开发模式和生产构建后都会出现。
## 原因分析
Tailwind CSS 的 Preflight(基于 modern-normalize)会重置链接样式:
```css
a {
color: inherit;
text-decoration: inherit;
}
```
正常情况下,这会让链接继承父元素的 `text-decoration`(通常是 `none`),从而去掉浏览器默认的下划线。
问题出在 **Nuxt 的客户端导航机制**
1. 用户点击 `<NuxtLink>`Vue Router 拦截导航
2. 新页面组件异步加载并渲染
3. **在 CSS 完全加载/应用之前,页面已经渲染了**
4. 此时浏览器使用默认样式(链接带下划线)
5. CSS 加载完成后,下划线消失
这就是典型的 **FOUCFlash of Unstyled Content** 问题。
## 为什么刷新就正常了?
刷新页面时,浏览器会等待所有 CSS 加载完成后再渲染页面(SSR 模式下 HTML 和 CSS 是一起返回的)。所以刷新后样式是正确的。
而客户端导航是 JavaScript 驱动的,CSS 的加载和页面的渲染是异步的,就出现了时序差。
## 解决方案
尝试了多种方案:
- `@layer base` 覆盖 — 无效,CSS layers 的优先级问题
- `corePlugins.preflight: false` — 无效,且破坏其他基础样式
- `features.inlineStyles: true` — 无效
- 页面过渡动画 — 只是掩盖,不能根治
最终有效的方案是在 `app.vue` 中用 `!important` 强制覆盖:
```vue
<style>
a:not(.prose a) {
text-decoration-line: none !important;
}
</style>
```
**关键点:**
- `!important` 是为了对抗 Tailwind CSS layers 的优先级
- `:not(.prose a)` 排除文章正文区域,保留文章内链接的下划线(可读性需要)
## 总结
这是 Nuxt 3 + Tailwind CSS 的一个已知问题,本质上是 CSS 加载时序导致的 FOUC。`!important` 虽然不够优雅,但在这个场景下是务实的解决方案。
如果你有更好的方案,欢迎在 [Issues](https://github.com/wishesl/SuBlog/issues) 提出!
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+62
View File
@@ -0,0 +1,62 @@
---
title: SvelteKit 入门:为什么它比你想象的更简单
published: 2026-05-30
description: SvelteKit 是一个全栈 Svelte 框架,这篇文章带你了解它的核心概念和优势。
image: /posts/sveltekit-intro/img/cover.jpg
tags: ['SvelteKit', '前端框架']
draft: false
---
## 什么是 SvelteKit
SvelteKit 是 Svelte 的官方全栈框架,类似于 React 之于 Next.js 的关系。它提供了路由、SSR、SSG 等能力,让开发者可以快速构建现代 Web 应用。
![SvelteKit Logo](https://raw.githubusercontent.com/sveltejs/branding/master/svelte-logo.svg)
## 核心特点
### 1. 编译时框架
Svelte 和 React/Vue 最大的区别在于:**它在编译阶段就把组件转成原生 JS**,运行时没有虚拟 DOM。
```typescript
// Svelte 组件(编译后变成直接的 DOM 操作)
let count = 0
function increment() {
count += 1
}
```
### 2. 文件路由
`src/routes/` 目录结构即 URL,不需要手动配置路由表:
```
src/routes/
├── +page.svelte → /
├── about/
│ └── +page.svelte → /about
└── posts/
├── +page.svelte → /posts
└── [slug]/
└── +page.svelte → /posts/:slug
```
### 3. 全栈能力
SvelteKit 内置了服务端渲染(SSR)、静态站点生成(SSG)、API 路由等功能,开箱即用。
## 和其他框架对比
| 特性 | SvelteKit | Next.js | Nuxt |
|------|-----------|---------|------|
| 学习曲线 | 低 | 中 | 中 |
| 包体积 | 小 | 大 | 中 |
| 运行时开销 | 无 | 有 | 有 |
| 生态 | 小 | 大 | 大 |
## 总结
如果你追求**简洁的代码**和**极致的性能**,SvelteKit 是一个非常值得尝试的选择。它的学习曲线平缓,写起来就像在写原生 HTML/CSS/JS。
![Svelte 编译过程](https://svelte.dev/svelte-logo-horizontal.svg)