简介
这是一个使用 obsidian
作为 backend
的个人博客, 虽然这么说, 其实并没有为 obsidian
的 api
做出单独的渲染优化, 所以很多插件大概率是不兼容的
因为我对 obsidian
的用途只是一个单纯的 markdown
编辑器, 如果您有上述需求, 可能官方的托管服务更适合您
博客使用 astro
的内容集合和 markdown
的 frontmatter
字段管理文章, 通过 obsidian
的模板快速插入相关信息
模板大概类似这样
---
pubDate:
- "{{date}}"
tags: []
featured: false
draft: true
---
Obsidian 配置
设置-选项-文件与链接
- 始终更新内部链接
- 开启
- 新建笔记的存放位置
- 选择指定的附件文件夹,路径参考
/todo
- 选择指定的附件文件夹,路径参考
- 内部链接类型
- 选择 基于当前笔记的相对路径
- 使用 Wiki 链接
- 关闭
- 附件默认存放路径
- 选择 指定的附件文件夹,路径参考
/assets
- 选择 指定的附件文件夹,路径参考
第三方插件配置
-
Paste image rename
从外部复制图片到
Obsidian
中,文件名中默认包含空格,而Astro
暂不支持在Markdown
中导入的文件名中包含空格或者转义字符的图片,所以需要此插件自动修改文件名- 配置
Pattern
第一个选项,配置参考{{DATE:x}}
- 开启
Auto rename
- 配置
样式
astro
内置了组件级别的 style
, 但是使用 markdown.render()
返回的内容是没有样式的, 因为它不属于 astro
组件的一部分, 你必须创建一个全局的类, 然后在上面修改样式, 这一点的工作原理和 tailwind
类似
一个好的想法可能是单独使用一个组件用于渲染 markdown
markdown
使用 tailwind
的 prose
插件可能不是一个好主意, 预置的样式有点多了, 不方便修改, 建议在 global.css
中写一个自用的类
viewTransitions
客户端导航
使用 viewTransitions
会开启客户端导航模式
在客户端导航中, 我们使用 astro:page-load
代替 DOMcontentLoad
事件
prefetch
astro
的 prefetch
在客户端导航期间默认是 prefetchALl
, 默认行为仍然是 hover
要注意的是, 如果你的网站响应速度不够快, 最好不要修改默认策略为 load
或者 viewport
, 如果访客网速很低, 这些行为最终会 fallback
到 tap
而不是 hover
, 这可能会导致更糟糕的体验
特别是在客户端导航期间, 浏览器在点击后没有反馈, 又没有设置 before-prepration
的动画的时候, 界面会出现假死
暗黑模式
暗黑模式应该在客户端导航生命周期的 before-swap
周期被添加, 以避免页面背景闪烁或者重置
页面刷新的时候仍然会白屏, 因为此时 dark
类还没有被添加到 <head>
上, 建议在 <viewTransition/>
之前添加一个内联脚本, 用于从 localstorge
中读取 theme
变量并决定是否添加 dark
注意, 这和 before-swap
并不冲突
一个是负责页面刷新后, 重新获取 document
, 此时的 document
的 head
是没有 dark
的, 所以会白屏, 一个是负责客户端导航期间添加 dark
类
动画
viewTransitions
的退出动画效果目前和部分浏览器 (chromium
系) 的兼容并不太好, 建议只使用进入动画, 退出动画设置为 0s
, 替代方案可以是手动添加动画类
脚本与事件处理
以下讨论均在客户端导航的前提下
脚本应该在合适的时机被执行, 以免阻塞渲染, 也就是说, 绝大多数脚本都应该在 astro:page-load
事件中完成
只有少数, 例如添加暗黑模式需要在 before-swap
事件中完成
内联脚本
在绝大多数情况下, 我们不需要内联脚本
假如我们有一个脚本用于给 post
的 toc
添加 click
事件用于显示或者隐藏目录,
第一个想法可能是在 toc
组件中写一个内联脚本, 其实也可以写一个条件判断当前路径是否在 post
的目录下, 再添加相应的事件
<script is:inline>
// 将会被直接插入 HTML,不会有任何变化!
// 本地导入并不会被解析,也不会生效。
// 如果组件被多次使用,脚本会出现多次。
</script>
托管
Cloudflare
之前是在 netlify
上托管的, 最近我的网站都迁移到 cloudflare
上了, 其实对于静态博客网站来说, netlify
还是很慷慨的
同样要配置缓存标头, cloudflare
还会给每个 pages
分配一个 dev
的域名, 也可以排除在搜索引擎之外, 要放到 /public/_headers
里
https://wunhao-com.pages.dev/*
X-Robots-Tag: noindex
https://wunhao.com/*
Cache-Control: max-age=86400
缓存
Netlify
, Vercel
等平台为了更灵活/广泛的应用, 默认的 HTTP 标头使用的是 Cache-control: max-age=0 must-revalite
, 这意味着尽管缓存会被存放在本地, 但是会立即过期, 在下次导航时, 必须先向服务器发出请求 if-none-match
, 在接受到 304
之后, 才可以重用缓存, 我们可以配置自定义标头采用更激进的缓存策略
在项目的根目录创建一个 netlify.toml
,并设置过期时间为一天或者更久
[[headers]]
for = "/*"
[headers.values]
cache-control = "max-age=86400"
重定向
Redirect options | Netlify Docs
启用漂亮 URL 后,Netlify 会将
/about
等路径转发到/about/
(静态网站和单页应用程序中的常见做法),并将/about.html
等路径重写为/about/
。
netlify 默认会开启 pretty url
, 例如, 当你访问 /about
的时候, 会被重定向到 /about/
, 这样的重定向会在导航的时候带来一次额外的往返, 如果你距离代理服务器过远 (>500ms
), 还是会影响体验的
astro
在构建的时候也会把 /about.astro
构建为 /about/index.html
在导航链接的时候, 你应该填写 /about/
而不是 /about
, 总的来说, 只要你的 astro
文件在构建后会生成一个同名目录以及 index.html
, 你就应该在末尾加上 /
, 或者说 tailing splash
,这样就可以避免重定向
注意: 这里只是个细枝末节的优化, 你很难把控到请求响应的每一部分, 例如, 本站为了兼容 obsidian
, post
的链接选择了以 md
结尾, 就意味着一定会发生重定向
post
目录
post
目录中的内容可能会被多次点击, 理想的做法是不应该将同一页面点击的链接也加入历史记录, 否则用户在访问的时候可能需要返回很多次才能回 上一页面
tags
页面上的组件也同理, 最好也添加上
<a href="/main" data-astro-history="replace">
递归渲染
在 Astro
中 markdown
可以通过 frontmatter
的 layout
字段指定 layout
, 并单独作为页面, layout
可以在 astro. props
中接收到 heading
数组
我的 markdown
文件是通过 obsidain
管理的, 所以选择了使用内容集合的方式
无论如何, 首先你要获取到 heading
数组
总体思路就是, 将 1 维的 heading
数组, 转换为嵌套数组, 然后利用 astro.self
递归渲染组件, 因此我们需要两个组件
组件
- 第一个组件是入口, 用来处理数据
---
// github.com/rezahedi/rezahedi.dev/blob/main/src/components/TOC.astro
import NestedList from "./NestedList.astro";
const { headings } = Astro.props;
function getNested(data) {
let arr = [];
for (let i = 0; i < data.length; i++) {
const nextIndex = data
.slice(i + 1)
.findIndex((item) => item.depth <= data[i].depth);
if (nextIndex > -1) {
data[i].children = getNested(data.slice(i + 1, i + nextIndex + 1));
arr.push(data[i]);
i += nextIndex;
} else {
// 如果没有找到满足条件的索引,则将剩余的元素作为当前元素的 children 属性
data[i].children = getNested(data.slice(i + 1))
// 推送当前元素到结果数组中
arr.push(data[i]);
// 结束循环
i = data.length;
}
}
return arr;
}
const nestedArr = getNested(headings);
---
<div>
<NestedList items={nestedArr} />
</div>
- 第二个组件用来递归渲染
---
const { items } = Astro.props;
---
<ul>
{items.map((item) => (
item.depth < 6 && (
<li >
<a href={`#${item.slug}`} >
{item.text}
</a>
{Array.isArray(item.children) && item.children.length > 0 ? (
<Astro.self items={item.children} />
) : null}
</li>
)
))}
</ul>
网站分析
本站使用自托管的 Umami 进行网站分析
- 创建 postgre 数据库, vercel 或者 supabase 有免费的
fork
umami 的 GitHub - umami-software/umami: Umami is a simple, fast, privacy-focused alternative to Google Analytics.- 是的, 在大多数情况下, 你不需要对 umami 进行配置
- 使用 vercel 或者 netlify 等平台托管, 托管的时候设置数据库的
env
环境变量
注意, netlify 的 nextjs rumtime 目前 (2024-04-23) 有问题, /setting
页面打不开, 可以在本地跑, 然后拿到 data-id
, 修改一下域名就可以.
netlify 默认生成的 build
命令是错误的, 不是 yarn run build-app
,应该修改为 yarn build
收集数据
对于 astro 项目, 在 baselayout
的 </body>
结束之前插入一个 inline
内联脚本就可以保证脚本在每一页都运行, 记得要加上 defer
没有选择在 <head>
引入主要是考虑用户体验, 以及, 如果是客户端导航, 在 head
引入是不会重新触发请求的