Skip to content
liudon@localhost:~/blog$ cd /posts/twikoo-2-netlify-cors

Twikoo 2.x 升级踩坑:Netlify CORS 报错与解决方案

# 记录 Twikoo 2.x 升级后 Netlify 评论服务出现 CORS 报错的问题、原因以及临时和长期解决方案。

author: Liudon date: 2026-09-22 words: 1643 read_time: 4m
$ less index.md

前言

刚更新完上一篇《我给博客做了一个时光机》,顺手打开文章看看效果,结果发现评论区挂了。

浏览器控制台报错:

Access to XMLHttpRequest at 'https://comment.example.com/'
from origin 'https://blog.example.com' has been blocked by CORS policy:

Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

评论服务一直使用 Twikoo + Netlify,之前也运行得好好的,最近并没有修改过 CORS 配置。

排查后发现,问题刚好出现在 Twikoo 从 1.x 自动升级到 2.x 之后。

最后实际碰到了两个问题:

Twikoo 1.x
   ↓
升级 Twikoo 2.x
   ↓
Node 18 不再支持
   ↓
升级 Node 22
   ↓
服务恢复运行
   ↓
OPTIONS 预检仍然没有 CORS Header
   ↓
Netlify Adapter 存在 Bug

如果你也是使用 Netlify 部署 Twikoo,升级 2.x 后评论突然出现 CORS 错误,可以重点看下面两个地方。

先升级 Node.js

Netlify 原来使用的是:

Node v18.20.8

升级 Twikoo 2.x 后,构建日志出现大量:

npm warn EBADENGINE
required: { node: '>=20.19.0' }
current: { node: 'v18.20.8' }

甚至直接访问 Twikoo 后端时返回:

{"code":1000,"message":"crypto is not defined"}

Twikoo 2.x 已经不再适配 Node 18。

所以首先把 Netlify 的 Node.js 升到 22:

NODE_VERSION=22

或者项目根目录增加:

.nvmrc

内容:

22

然后重新部署。

如果访问 Twikoo 后端还有类似:

crypto is not defined

先不要折腾 CORS,把 Node 版本解决掉。

Node 22 后还是 CORS 报错

升级 Node 22 后,Twikoo 后端可以正常运行,但评论区依然报:

No 'Access-Control-Allow-Origin' header

这时可以直接模拟浏览器的 OPTIONS 请求。

下面的 https://blog.example.com 需要替换成你自己的博客域名:

BLOG_ORIGIN='https://blog.example.com' # 替换为你自己的博客域名

curl -i -X OPTIONS 'https://comment.example.com/' \
  -H "Origin: ${BLOG_ORIGIN}" \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type'

https://comment.example.com/ 同样替换成自己的 Twikoo 服务地址。

返回:

HTTP/2 204

看起来没毛病。

但是 Response Header 里没有:

Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers

也就是说:

OPTIONS
   ↓
204
   ↓
缺少 CORS Header
   ↓
浏览器阻止后续 POST

根因

继续看 Twikoo 2.x 的 Netlify Adapter:

packages/server-netlify/src/main.ts

里面有这样一段:

export function fromTkResponse(tkRes: TkResponse): NetlifyResult {
  if (tkRes.status === 204) {
    return { statusCode: 204, headers: {}, body: "" };
  }

  return {
    statusCode: tkRes.status,
    headers: { ...tkRes.headers, "Content-Type": "application/json" },
    body: JSON.stringify(tkRes.body),
  };
}

Twikoo 公共层其实已经生成了 CORS Header。

但是 OPTIONS 返回 204 时:

headers: {}

又把这些 Header 全部丢掉了。

实际流程变成:

Browser
   ↓
OPTIONS
   ↓
Twikoo Pipeline
   ↓
生成 CORS Header
   ↓
Netlify Adapter
   ↓
204 分支
   ↓
headers: {}
   ↓
CORS Header 丢失

所以这并不是自己的 CORS 配置错误,而是 Netlify Adapter 在处理 204 时存在问题。

临时解决方案

请参考长期解决方案,升级版本即可解决。

如果现在就需要恢复评论,可以先修改自己部署项目里的:

netlify/functions/twikoo.js

原来可能只是:

exports.handler = require('twikoo-netlify').handler

可以暂时在外面包一层,自己处理 OPTIONS:

const { handler } = require('twikoo-netlify')

const ALLOWED_ORIGINS = new Set([
  'https://blog.example.com', // 替换为你自己的博客域名
])

function corsHeaders(origin) {
  if (!origin || !ALLOWED_ORIGINS.has(origin)) {
    return {}
  }

  return {
    'Access-Control-Allow-Origin': origin,
    'Access-Control-Allow-Credentials': 'true',
    'Access-Control-Allow-Methods': 'POST',
    'Access-Control-Allow-Headers':
      'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
  }
}

exports.handler = async function (event, context) {
  const origin =
    event.headers?.origin ||
    event.headers?.Origin ||
    ''

  if (event.httpMethod === 'OPTIONS') {
    return {
      statusCode: 204,
      headers: corsHeaders(origin),
      body: ''
    }
  }

  const result = await handler(event, context)

  return {
    ...result,
    headers: {
      ...(result.headers || {}),
      ...corsHeaders(origin)
    }
  }
}

重新部署后再测试:

BLOG_ORIGIN='https://blog.example.com' # 替换为你自己的博客域名

curl -i -X OPTIONS 'https://comment.example.com/' \
  -H "Origin: ${BLOG_ORIGIN}" \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type'

正常应该能看到:

HTTP/2 204

Access-Control-Allow-Origin: https://blog.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST

这个方案适合临时救急。

等官方版本修复以后,这层 Wrapper 就可以删掉。

长期解决方案

真正应该修的是 Twikoo 自己的 Netlify Adapter。

改动其实只有一行:

- return { statusCode: 204, headers: {}, body: "" };
+ return { statusCode: 204, headers: { ...tkRes.headers }, body: "" };

这样 OPTIONS 返回 204 时,可以继续保留公共 Pipeline 已经生成好的 CORS Header。

相关修复已经提交 PR:

twikoojs/twikoo#1165

长期方案就是等待 PR 合并,官方重新发布版本后升级 twikoo-netlify,再删除前面的临时 Wrapper。

PR 已合并,官方已打包 2.0.7 版本,请升级版本即可。

总结

如果升级 Twikoo 2.x 后,Netlify 部署的评论突然挂掉,可以先看两个地方:

Node 版本
   ↓
至少 20.19,建议直接 Node 22

OPTIONS 预检
   ↓
204 是否带 Access-Control-Allow-Origin

Node 版本不对,先升级 Node。

Node 已经正常,但 OPTIONS 的 204 没有 CORS Header,可以先用上面的 Wrapper 临时解决,然后等待官方修复版本再升级。

这次让我比较意外的,其实不是出现 Bug。

大版本重构出现 Bug 很正常。

问题在于 Twikoo 2.0 不只是普通版本更新:

Node 运行时要求发生变化
+
后端大规模重构
+
现有 Netlify 部署存在兼容问题

但对于原来使用 latest 或自动更新的用户来说,很可能在完全没有主动升级的情况下,就直接从 1.x 跳到了 2.x。

我的情况就是:

刚写完一篇文章
↓
打开博客看看
↓
评论突然挂了

这种涉及运行时最低版本变化的大版本升级,我觉得官方这次处理得有些草率。

至少应该在升级前做更明显的周知,或者给现有部署用户留出一个更明确的迁移窗口,而不是让大量使用 latest 的老部署直接跟着升级。

否则用户最后看到的只有一句:

No 'Access-Control-Allow-Origin' header

然后从 CORS 一路查到 Netlify、Node.js,最后再翻到 Adapter 源码。

因为版本问题太多,官方一天更新了几个版本,希望后续版本升级做好测试,先灰度测试,不要一把全量,毕竟好多人都在用的。

$ twikoo --page /posts/twikoo-2-netlify-cors/
loading comments
NORMAL index.md words: 1643 EOF