开发问题与踩坑记录
面向在 cikio 生态里写代码的人。每条按 现象 → 原因 → 处理 写,只收已经定论、可复现的问题。
这份清单是活的:排查出一个新坑,直接在本页对应小节补一条,走一次 PR 即可。 不写进来:密钥、Token、账号 ID、数据库 ID、内网地址 —— 本页是公开的,一律不落敏感信息。
一、本机工具链(Windows)
Section titled “一、本机工具链(Windows)”Node 的 fetch 报证书错误
Section titled “Node 的 fetch 报证书错误”- 现象:
unable to verify the first certificate … try running Node.js with --use-system-ca,Astro 构建期脚本、任何fetch都失败。 - 原因:本机在企业 TLS 拦截代理后面,Node 不信任代理 CA(浏览器和 JDK 各自有信任库,表现不一样)。
- 处理:
NODE_OPTIONS=--use-system-ca pnpm build(Node ≥ 22.15)。JDK 侧另用-Djavax.net.ssl.trustStoreType=Windows-ROOT -Djavax.net.ssl.trustStore=NUL。
经代理下载的二进制被静默截断
Section titled “经代理下载的二进制被静默截断”- 现象:构建时报
RangeError: Offset is outside the bounds of the DataView,只影响部分产物,极难定位(真实案例:satori 渲染动态 OG 图时字体文件解析越界)。 - 原因:代理把下载截断了却不报错,文件体积小于正常值(10.5 MB 的字体只下到 8.2 MB)。
- 处理:下载后核对文件体积;用
curl时加-H "Accept-Encoding: identity"。
git push 卡在证书
Section titled “git push 卡在证书”- 现象:
git push报证书/SSL 相关错误。 - 处理:
git -c http.sslBackend=schannel push origin main(走 Windows 系统证书)。
子包加了依赖,CI 全红
Section titled “子包加了依赖,CI 全红”- 现象:只提交子目录,
ci.yml两个 job 都失败。 - 原因:CI 用
frozen-lockfile,而根pnpm-lock.yaml没跟着提交。 - 处理:子包改依赖时,一并
git add pnpm-lock.yaml(仓库根)。
pnpm deploy 不干活
Section titled “pnpm deploy 不干活”- 现象:
pnpm --filter docs deploy什么都没部署。 - 原因:
deploy与 pnpm 内置命令同名。 - 处理:必须写
pnpm --filter docs run deploy。
esbuild 误报字符串未闭合
Section titled “esbuild 误报字符串未闭合”- 现象:
Unterminated string literal,但代码里引号是配对的。 - 原因:属性值跨行书写会让 esbuild 误判。
- 处理:把跨行的属性值改成单行,或换成对象/变量形式。
二、Cloudflare 部署
Section titled “二、Cloudflare 部署”*.workers.dev 打不开
Section titled “*.workers.dev 打不开”- 原因:该域名在国内被 DNS 污染/屏蔽。
- 处理:一律走
cikio.cn子域;本地验证、给别人的链接都别用 workers.dev。
静态站 404 显示浏览器的错误页
Section titled “静态站 404 显示浏览器的错误页”- 现象:访问不存在的路径,浏览器显示「找不到此 xxx 页 / HTTP ERROR 404」,站点样式和入口都没有。
- 原因:Workers Static Assets 的
not_found_handling默认是none,未命中就返回空 body 的 404。 - 处理:
wrangler.toml里写[assets] not_found_handling = "404-page",回落到产物里的404.html。
Pages 的运行时绑定没生效
Section titled “Pages 的运行时绑定没生效”- 原因:Pages 优先读仓库根的
wrangler.toml,只在控制台设置绑定不够。 - 处理:根
wrangler.toml里声明[[d1_databases]]、[[r2_buckets]]等;注意migrations_dir要改成相对仓库根的路径。密钥用wrangler pages secret put,不要依赖.env或构建变量。
自定义域被部署覆盖
Section titled “自定义域被部署覆盖”- 现象:
wrangler deploy动了控制台手动绑定的域名。 - 处理:手动绑定的域不要在
wrangler.toml里声明routes。
wrangler d1 execute 连错库
Section titled “wrangler d1 execute 连错库”- 规则:定位名填
wrangler.toml顶层[[d1_databases]]的database_name,再追加--preview由preview_database_id切到预览库;直接填预览库名会报Please define a preview_database_id。 - 附带的坑:含空格的 SQL 用
--file=xxx.sql传参,--command="..."会被 shell 拆开。
触发器里的分号
Section titled “触发器里的分号”- 原因:
CREATE TRIGGER的 SQL 体内含分号,wrangler d1 migrations apply会把它拆成多条语句,触发器实际从未部署。 - 处理:别依赖触发器做自增/默认值,改为在写入前显式算出值(如插入前
SELECT MAX(id)+1)再传进INSERT;已出错的数据另写回填迁移。
三、authserver
Section titled “三、authserver”- CLI 包名:官方 CLI 的 npm 包名是
auth(npx auth),@better-auth/cli已废弃,别再用。 - OIDC Provider 插件:旧的
oidcProvider插件已废弃(有安全公告),必须用独立包@better-auth/oauth-provider的oauthProvider;只有当客户端请求openidscope 时才表现为 OIDC Provider,JWKS 由jwt()插件提供。 - 数据库迁移路线:D1 只在 Workers 运行时可达,Kysely 适配器要内省实时数据库,本地必然失败 —— 走 drizzle 适配器:
auth generate --adapter drizzle --dialect sqlite→drizzle-kit generate→wrangler d1 migrations apply --remote。 - 端点路径:
basePath为/api/auth时,OIDC 发现端点是/api/auth/.well-known/openid-configuration(path-appending),不是根路径。 - 本地 Origin 被改写:
wrangler.toml声明了routes后,wrangler dev会把本地请求的 Origin 重写成自定义域,必须把它加进trustedOrigins,否则回调被拒。 - OAuth 客户端凭据:
client_secret在库里是哈希存储、PKCE 默认强制 —— 手写INSERT造客户端时不能塞明文,必须存哈希。
四、文档站(本站)
Section titled “四、文档站(本站)”- URL 由文件路径决定:Starlight 0.42 不支持 frontmatter
slug覆盖,content/ccu/assets.md就是/ccu/assets/。挪文件=换 URL,要同步处理旧地址。 - 侧栏显式
slug写错会整站构建失败(Starlight 直接抛错并点名是哪个 slug)——这是刻意保留的严格性,别改成宽松校验。 - 「编辑此页」的链接:Starlight 用
filePath拼,而filePath落在src/content/docs/(构建期拷贝的生成物、已 gitignore),拼出来的地址在仓库里不存在。本站用src/routeMiddleware.ts把前缀换成真源目录。别用import.meta.url或import.meta.glob去解这个问题——中间件被打进dist/.prerender/,前者拿错根目录、后者返回空。 - 草稿机制:没写完的页面 frontmatter 标
draft: true,生产构建不输出、本地可见;定稿删掉这一行。不要让已发布页面链接到草稿页。 - 新增一篇文档要同步三处:文件、
astro.config.mjs的sidebar、scripts/fetch-content.mjs的PUBLISHED_SLUGS(policy/目录下的由 autogenerate 自动跟进,不用管)。 - 删掉内容文件后编辑器报「找不到文件」:
tsconfig.json的include是**/*,若不额外排除content/,TypeScript 语言服务会把正文文件也纳入程序;删掉或改名后它仍拿着旧文件列表,报找不到文件 …/content/xxx.mdx。把content、.content-cache加进exclude(构建产物src/content/docs本来就排除)即可,astro check不受影响;改完若编辑器仍报,重启一次 TS 服务。
五、数据与接口
Section titled “五、数据与接口”- 表结构别凭记忆:同一个库里不同表的字段并不一致(例:
posts只有createdAt/updatedAt,没有publishedAt;announcements、featured_issues才有)。Drizzle 不做运行时列校验,写错列只会在运行到那行时崩。改查询前先看 schema。 - IP 属地不要实时查第三方:Cloudflare 边缘会免费注入
cf对象(cf.regionCode/cf.region/cf-ipcountry),绝大多数 IPv4 访客就能拿到精确省份,在写入时固化进 DB 即可,之后直接读字段。- ipinfo 的免费档(Lite)只返回国家/大洲,没有省份 region 字段,补省要付费套餐;拿 lite token 去打标准端点会被静默吞掉。
- ip-api.com 免费版只有 HTTP,而 Workers 的
fetch只支持 HTTPS(及 localhost),直接失败 —— 不要用。
- 站内信 ≠ 邮件:后台审核类动作只写站内信;只有走
notify()的通知才会按用户偏好(email_enabled)决定要不要发邮件,且互动类(评论回复、点赞、新关注)默认不发。评估「用户有没有收到」时要先看是哪条路径。 - 共享包是源码直出:
main/exports直接指向src/index.ts,没有构建步骤,子项目用workspace:*引用。改动即时生效,但也意味着没有编译期保护。
六、怎么往这里补一条
Section titled “六、怎么往这里补一条”### 标题:一句话现象
- 现象:<报错原文或表现>- 原因:<为什么>- 处理:<可直接照做的步骤>只写已定论、可复现的;不写讨论过程与推测;涉及敏感信息的一律不写。拿不准是否该公开的,先按上面的「不写进来」清单自查,再问项目负责人。