标签 Deno 下的文章

赛拉研究实验室在 Grist-Core 中发现的一处严重漏洞,让看似无害的电子表格公式,成为攻陷整个企业组织的关键。该漏洞编号为CVE-2026-24002,CVSS 评分高达 9.1 分,攻击者可利用其突破该平台的 Pyodide 沙箱防护,实现完全的远程代码执行(RCE)
Grist 作为 Excel 的可编程替代工具,在政府、教育及各行业中应用广泛,其核心优势是支持在单元格中运行 Python 代码以实现工作流自动化。但研究人员发现,这一 “可编程数据层” 的底层架构存在严重脆弱性。
与依赖明显代码注入手段的传统攻击不同,该漏洞的利用方式是将合法的数据包武器化,攻击流量会沿着 Grist 解析公式的常规数据处理路径执行,让日常的电子表格更新操作,沦为悄无声息的系统接管行为。
研究报告详细阐述了攻击者可突破沙箱边界的三种具体方式:
  1. 类层级遍历:利用 Python 的对象模型,访问os.system()等被禁用的内置函数;
  2. 直接调用 C 库:借助 ctypes 库,从内存中直接调用系统底层函数;
  3. 滥用 Emscripten 运行时:调用emscripten_run_script_string()函数,在宿主运行时环境中执行 JavaScript 代码。
该漏洞造成的后果极为严重:此次沙箱逃逸并非仅能实现本地服务器的远程代码执行,还可攻陷运行租户工作流的 SaaS 控制层,实现该层面的远程代码执行
Grist 安全团队已迅速采取行动修复该漏洞,此次修复涉及底层架构的根本性调整:将 Pyodide 公式的执行环节,默认迁移至 Deno 运行时环境中进行。
这一调整的关键意义在于改变了漏洞的触发后果:此前的情况是 “一旦突破 Pyodide 沙箱,就会直接攻陷宿主系统”,而现在宿主运行时的权限完全由 Deno 的权限模型管控,从根源上降低了风险。
研究人员敦促管理员立即将 Grist 升级至1.7.9 及更高版本,同时需注意切勿禁用新的防护机制。报告警示,若设置配置项GRIST_PYODIDE_SKIP_DENO=1,将会绕过此次修复,该操作应被视为主动选择使用防护能力更弱的隔离边界
研究人员指出:当一款自动化或数据平台成为受信任的执行环境时,其沙箱逃逸漏洞带来的就不再是单一服务器的故障,而是整个信任边界的崩塌

从底层引擎优化角度,深入剖析 Hono 路由器的极致性能奥秘

引言

在众多 JavaScript Web 框架中,Hono 以其极致的性能表现脱颖而出。特别是在 Cloudflare Workers、Deno 等边缘计算环境中,Hono 的路由匹配速度在同类框架中名列前茅。这背后的秘密是什么?答案就藏在它的 RegExpRouter 实现中。

⚠️ 重要提示:本文重点讨论 RegExpRouter 的核心原理。在实际应用中,Hono 使用 SmartRouter 自动组合多种路由器(RegExpRouter、TrieRouter、LinearRouter)以达到最佳性能。

本文将通过一个精简的实现,深入剖析 Hono 路由器的核心原理,帮助你理解:

  • 为什么 RegExp 路由比传统路由快
  • 路由是如何预编译的
  • 参数提取的巧妙设计

📌 关键要点

项目说明
核心原理将路由预编译为正则表达式,利用引擎底层优化
性能优势减少 JS 层开销,一次原生调用完成匹配
最佳环境Cloudflare Workers、Deno 等边缘计算平台
实际架构SmartRouter + RegExpRouter + TrieRouter 组合
权衡考虑Node.js 环境性能打折扣,路由注册较慢

传统路由的性能瓶颈

在理解 Hono 的优化之前,我们先看看传统路由的处理方式:

// 传统路由的典型实现
function matchRoute(path, routes) {
  for (const route of routes) {
    // 1. 字符串分割
    const pathParts = path.split('/');
    const routeParts = route.path.split('/');

    // 2. 逐段比较
    if (pathParts.length !== routeParts.length) continue;

    const params = {};
    let matched = true;

    // 3. JS 层面的循环和条件判断
    for (let i = 0; i < pathParts.length; i++) {
      if (routeParts[i].startsWith(':')) {
        params[routeParts[i].slice(1)] = pathParts[i];
      } else if (pathParts[i] !== routeParts[i]) {
        matched = false;
        break;
      }
    }

    if (matched) return { route, params };
  }
  return null;
}

性能问题在哪里?

  • ❌ 大量的字符串操作(splitslice
  • ❌ 多层循环和条件判断,全在 JavaScript 层面执行
  • ❌ 每个请求都要重复这些操作

Hono 的解决方案:下沉到引擎层

Hono 的核心思想非常简单却极其巧妙:

将路由匹配逻辑从 JavaScript 层下沉到 JavaScript 引擎底层(C++ 实现的正则表达式引擎)

1. 预编译阶段(应用启动时)

在应用启动时,Hono 会将路由路径转换为正则表达式:

// 路由路径:/user/:id/posts/:postId
// 转换为正则:/^\/user\/([^/]+)\/posts\/([^/]+)$/

const paramNames: string[] = [];

const regexPath = path.replace(/:([a-zA-Z0-9_]+)/g, (_, paramName) => {
  paramNames.push(paramName); // 记录参数名
  return '([^/]+)';           // 替换为捕获组
});

const pattern = new RegExp(`^${regexPath}$`);

关键点:

  • :id([^/]+):匹配除 / 外的任意字符
  • 参数名被保存在 paramNames 数组中
  • 这个"昂贵"的编译过程只在启动时执行一次

2. 匹配阶段(请求到来时)

当请求到来时,只需要一行代码:

const match = pattern.exec(path);

这行代码的魔法:

  • exec 是 JavaScript 引擎的原生方法,由 C++ 实现
  • ✅ 正则引擎在底层进行了高度优化(状态机、JIT 编译等)
  • ✅ 无需手动分割字符串、无需 JS 层循环
  • ✅ 参数提取通过捕获组自动完成

3. 参数提取

匹配成功后,参数提取也非常简洁:

const params: Record<string, string> = {};
route.paramNames.forEach((name, index) => {
  params[name] = match[index + 1]; // match[0] 是完整匹配
});

完整实现解析

让我们看完整的简化实现,包含所有必要的类型定义:

// ============ 类型定义 ============

/**
 * 路由处理函数类型
 * @param params - 从 URL 中提取的参数对象
 */
type Handler = (params: Record<string, string>) => void;

/**
 * 路由信息接口
 */
interface Route {
  method: string;        // HTTP 方法(GET, POST, etc.)
  path: string;          // 原始路径模式(如 /user/:id)
  handler: Handler;      // 路由处理函数
  paramNames: string[];  // 参数名数组(如 ['id', 'postId'])
}

/**
 * 匹配结果接口
 */
interface MatchResult {
  handler: Handler;
  params: Record<string, string>;
}

// ============ 核心路由器实现 ============

export class DemoRegExpRouter {
  // 存储预编译的正则表达式和路由信息
  private routes: { pattern: RegExp; route: Route }[] = [];

  // 注册路由:预编译
  add(method: string, path: string, handler: Handler) {
    const paramNames: string[] = [];

    // 将 :param 转为正则捕获组
    const regexPath = path.replace(/:([a-zA-Z0-9_]+)/g, (_, paramName) => {
      paramNames.push(paramName);
      return '([^/]+)';
    });

    const pattern = new RegExp(`^${regexPath}$`);
    this.routes.push({ pattern, route: { method, path, handler, paramNames } });
  }

  // 匹配路由:执行正则
  match(method: string, path: string) {
    for (const { pattern, route } of this.routes) {
      if (route.method !== method && route.method !== 'ALL') continue;

      const match = pattern.exec(path); // 核心:原生正则匹配

      if (match) {
        const params: Record<string, string> = {};
        route.paramNames.forEach((name, index) => {
          params[name] = match[index + 1];
        });
        return { handler: route.handler, params };
      }
    }
    return null;
  }
}

实战示例

const router = new DemoRegExpRouter();

// 注册路由
router.add('GET', '/user/:id', (params) => {
  console.log(`获取用户详情,ID: ${params.id}`);
});

router.add('GET', '/user/:id/posts/:postId', (params) => {
  console.log(`获取用户 ${params.id} 的文章 ${params.postId}`);
});

// 匹配请求
const result = router.match('GET', '/user/123/posts/456');
if (result) {
  result.handler(result.params);
  // 输出: 获取用户 123 的文章 456
}

性能优势分析

为什么 RegExpRouter 更快?

  1. 减少 JS 执行开销:从多层循环降到一次原生调用
  2. 引擎优化:正则引擎经过数十年优化,包含 JIT、状态机等技术
  3. 减少内存分配:无需频繁创建数组、对象
  4. 一次性编译:预编译阶段完成所有准备工作,请求时零额外开销

性能对比概览

方案实现层次执行效率适用场景
传统路由JavaScript 层中等通用场景
RegExpRouter引擎底层极快边缘计算、高并发
📊 实际 Benchmark:根据 Hono 官方测试,在 Cloudflare Workers 和 Deno 环境中,Hono 是同类框架中最快的。但在 Node.js 环境中,由于适配器开销,性能优势会打折扣。

Hono 真实实现的进一步优化

本文的简化版每个路由都是独立的正则表达式。而 Hono 的真实实现采用了多路由器协同的策略:

1. SmartRouter(智能路由器)

Hono 默认使用 SmartRouter,它会:

  • 根据路由特征自动选择最快的路由器
  • 动态组合 RegExpRouter、TrieRouter、LinearRouter
  • 检测路由模式并持续使用最优方案

2. 路由器分工

路由器适用场景特点
RegExpRouter动态参数路由最快,但不支持所有模式
TrieRouter复杂路由模式支持所有模式,性能优秀
LinearRouter少量路由简单可靠,路由少时够用

3. 实际优化策略

  • 静态路由优先/about/contact 等使用 Map 快速查找
  • 路由预分组:按 HTTP 方法分组,减少匹配次数
  • 延迟编译:RegExpRouter 编译较慢,适合应用启动时初始化,不适合无服务器环境的冷启动

这些优化使得 Hono 在处理数百个路由时仍然保持极高性能。

局限性与权衡

RegExpRouter 并非银弹,在使用时需要注意以下限制:

RegExpRouter 的局限

  • 不支持所有路由模式:某些复杂的路由规则无法用简单正则表示
  • 注册阶段较慢:路由编译需要时间,不适合每次请求都重新初始化的环境(如某些无服务器冷启动场景)
  • 调试难度:编译后的正则表达式可读性较差,出错时难以定位
  • 模式约束:如带严格约束的参数 /:id(\\d+) 需要额外处理

运行环境差异

环境性能表现原因
Cloudflare Workers⭐⭐⭐⭐⭐ 极快原生 Web 标准 API
Deno⭐⭐⭐⭐⭐ 极快原生 Web 标准 API
Bun⭐⭐⭐⭐ 很快高性能 JS 运行时
Node.js⭐⭐⭐ 中等需要适配器转换
💡 选型建议:如果你的应用运行在边缘计算环境(Cloudflare Workers、Vercel Edge Functions 等),Hono 是绝佳选择。如果是传统 Node.js 服务器,Fastify 可能是更好的选择。

但对于大多数 Web 应用场景,RegExpRouter 的性能与功能平衡已经足够优秀。

总结

Hono 的 RegExpRouter 通过"预编译 + 引擎下沉"的策略,在特定环境中将路由匹配性能提升到极致:

核心优势

  1. 预编译:启动时一次性将路由转为正则,摊销成本
  2. 引擎下沉:利用 C++ 实现的正则引擎,避免 JS 层开销
  3. 参数捕获:巧妙利用正则捕获组,无需手动解析
  4. 智能组合:SmartRouter 自动选择最优路由器,兼顾性能与功能

设计哲学

这种设计哲学值得我们思考:性能优化的终极目标,往往是让"热路径"代码尽可能多地运行在更底层的优化环境中

对于 Web 框架来说,路由匹配就是最热的路径之一。Hono 在边缘计算环境中找到了这个问题的最优解,同时通过多路由器架构保证了广泛的适用性。

最佳实践

  • 边缘计算/Serverless:Hono 是首选(Cloudflare Workers、Deno Deploy)
  • 高并发场景:充分发挥 RegExpRouter 性能优势
  • ⚠️ Node.js 环境:考虑性能权衡,或选择 Fastify 等专门优化的框架
  • ⚠️ 复杂路由规则:了解 RegExpRouter 的限制,必要时使用 TrieRouter

延伸阅读:

技术资源:


📝 文章说明

本文基于对 Hono 框架的学习和研究编写,代码示例为简化版实现,用于教学目的。实际的 Hono RegExpRouter 实现更加复杂和优化。

性能数据和对比来自 Hono 官方文档和社区测试,具体数值会因测试环境、负载类型、运行时版本等因素而异。实际使用时请根据自己的场景进行测试。

如果您发现文中有任何不准确之处,欢迎指正。

一个基于 Hono 的全栈 Web 框架,结合了 Islands 架构和边缘计算的强大能力

引言

在现代 Web 开发中,我们面临着一个永恒的挑战:如何在提供丰富交互体验的同时,保持快速的加载速度和优秀的性能?传统的单页应用(SPA)虽然交互流畅,但首屏加载慢、SEO 困难;而传统的服务端渲染(SSR)虽然首屏快,但缺乏现代前端框架的开发体验。

HonoX 的出现,为这个问题提供了一个优雅的解决方案。它是基于超快的 Hono Web 框架构建的全栈框架,采用 Islands 架构,完美平衡了性能和开发体验。

什么是 HonoX?

HonoX 是一个全栈 Web 框架,它建立在 Hono 之上。Hono 是一个轻量级、超快速的 Web 框架,可以运行在任何 JavaScript 运行时(Cloudflare Workers、Deno、Bun、Node.js 等)。

核心特性

  1. Islands 架构 - 渐进式水合,只在需要的地方加载 JavaScript
  2. 文件路由系统 - 基于文件系统的直观路由
  3. 边缘优先 - 为 Cloudflare Workers 等边缘运行时优化
  4. 类型安全 - 完整的 TypeScript 支持
  5. 零配置 - 开箱即用的最佳实践
  6. 极致性能 - 继承 Hono 的超快性能

Islands 架构:重新思考前端水合

什么是 Islands 架构?

Islands 架构是一种现代前端架构模式,最早由 Etsy 的前端架构师 Katie Sylor-Miller 提出,后来被 Astro、Fresh 等框架采用。

想象一个网页是一片海洋,而需要交互的组件是海洋中的"岛屿":

┌─────────────────────────────────┐
│  静态 HTML(服务端渲染)          │
│                                 │
│  ┌─────────┐      ┌─────────┐  │
│  │ Island  │      │ Island  │  │
│  │ (交互)  │      │ (交互)  │  │
│  └─────────┘      └─────────┘  │
│                                 │
│         ┌─────────┐             │
│         │ Island  │             │
│         │ (交互)  │             │
│         └─────────┘             │
└─────────────────────────────────┘

这种架构的优势在于:

  • 减少 JavaScript 负载 - 只加载真正需要的 JavaScript
  • 提升首屏性能 - 静态内容立即可见
  • 渐进式增强 - 交互组件逐步加载和激活
  • 更好的 SEO - 完整的服务端渲染内容

HonoX 中的 Islands

在 HonoX 中使用 Islands 非常简单:

// app/islands/Counter.tsx
import { useState } from 'hono/jsx'

export default function Counter({ initialCount = 0 }) {
  const [count, setCount] = useState(initialCount)

  return (
    <div class="counter">
      <button onClick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

只需将组件放在 app/islands/ 目录下,HonoX 会自动处理:

  • 服务端渲染
  • 客户端代码分割
  • 按需水合

在页面中使用:

// app/routes/index.tsx
import Counter from '../islands/Counter'

export default function Home() {
  return (
    <div>
      <h1>我的页面</h1>
      <p>这段文字是纯静态的,不需要 JavaScript</p>
      <Counter initialCount={0} />
    </div>
  )
}

文件路由系统:约定优于配置

HonoX 采用基于文件的路由系统,让路由管理变得直观:

app/routes/
├── index.tsx           → /
├── about.tsx           → /about
├── blog/
│   ├── index.tsx       → /blog
│   └── [slug].tsx      → /blog/:slug
└── api/
    ├── users.ts        → /api/users
    └── users/
        └── [id].ts     → /api/users/:id

动态路由

使用方括号定义动态路由参数:

// app/routes/blog/[slug].tsx
import { createRoute } from 'honox/factory'

export default createRoute((c) => {
  const { slug } = c.req.param()

  return c.render(
    <article>
      <h1>文章:{slug}</h1>
    </article>
  )
})

API 路由

API 路由返回 JSON 数据:

// app/routes/api/users/[id].ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()

// GET /api/users/:id
app.get('/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: 'User ' + id })
})

// POST /api/users/:id
const schema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
})

app.post('/:id', zValidator('json', schema), (c) => {
  const data = c.req.valid('json')
  const id = c.req.param('id')

  return c.json({
    id,
    ...data,
    updated: true
  })
})

export default app

中间件系统:强大且灵活

HonoX 继承了 Hono 的中间件系统,让你可以轻松处理横切关注点:

// app/routes/_middleware.tsx
import { createRoute } from 'honox/factory'
import { compress } from 'hono/compress'
import { logger } from 'hono/logger'
import { secureHeaders } from 'hono/secure-headers'

export default createRoute((c, next) => {
  // 日志记录
  logger()(c, next)

  // 安全头
  secureHeaders()(c, next)

  // 响应压缩
  compress()(c, next)

  return next()
})

自定义中间件

创建自定义中间件也很简单:

// 性能计时中间件
export const timing = createMiddleware(async (c, next) => {
  const start = Date.now()
  await next()
  const end = Date.now()

  c.header('Server-Timing', `total;dur=${end - start}`)
})

// 认证中间件
export const auth = createMiddleware(async (c, next) => {
  const token = c.req.header('Authorization')

  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  // 验证 token...
  await next()
})

性能优化:从框架层面开始

HonoX 内置了多种性能优化:

1. 自动代码分割

每个 Island 组件自动分割成独立的 chunk:

// 自动生成类似这样的输出
dist/
├── client/
│   ├── island-Counter.js    (3KB)
│   ├── island-Search.js     (5KB)
│   └── island-Modal.js      (4KB)
└── server/
    └── index.js

2. 流式 SSR

使用 Suspense 实现流式渲染:

import { Suspense } from 'hono/jsx'
import AsyncData from '../islands/AsyncData'

export default function Page() {
  return (
    <div>
      <h1>立即显示的标题</h1>

      <Suspense fallback={<div>加载中...</div>}>
        <AsyncData />
      </Suspense>
    </div>
  )
}

页面渲染流程:

  1. 立即发送 HTML 头部和静态内容
  2. 异步组件准备好后流式发送
  3. 最后发送激活脚本

3. 智能缓存策略

// 静态资源长期缓存
app.get('/static/*', async (c) => {
  c.header('Cache-Control', 'public, max-age=31536000, immutable')
  return c.next()
})

// API 响应 ETag 缓存
app.get('/api/data', async (c) => {
  const data = await fetchData()
  const etag = generateETag(data)

  if (c.req.header('If-None-Match') === etag) {
    return c.body(null, 304)
  }

  c.header('ETag', etag)
  return c.json(data)
})

类型安全:端到端的 TypeScript

HonoX 提供完整的类型安全,从路由到 API:

// 定义 API 类型
type User = {
  id: string
  name: string
  email: string
}

// API 路由自动推断类型
const app = new Hono<{ Variables: { user: User } }>()

app.get('/api/user', (c) => {
  const user = c.get('user') // 类型:User
  return c.json(user)
})

// 在客户端使用类型
const response = await fetch('/api/user')
const user: User = await response.json()

部署:边缘优先

HonoX 针对边缘运行时优化,特别是 Cloudflare Workers:

Cloudflare Pages 部署

# 构建
npm run build

# 部署
npm run deploy

优势:

  • 全球 CDN - 300+ 个边缘节点
  • 零冷启动 - Workers 即时响应
  • 自动扩展 - 无需配置
  • 低成本 - 免费层每天 100,000 请求

其他平台

HonoX 也支持部署到:

  • Vercel - 使用 Node.js 适配器
  • Netlify - Edge Functions
  • Deno Deploy - 原生支持
  • 传统服务器 - Node.js

实战案例:构建一个博客

让我们用 HonoX 构建一个完整的博客系统:

1. 文章列表页

// app/routes/blog/index.tsx
import { createRoute } from 'honox/factory'
import { getPosts } from '../../lib/posts'

export default createRoute(async (c) => {
  const posts = await getPosts()

  return c.render(
    <div class="blog">
      <h1>博客文章</h1>
      <ul>
        {posts.map(post => (
          <li key={post.slug}>
            <a href={`/blog/${post.slug}`}>
              <h2>{post.title}</h2>
              <time>{post.date}</time>
            </a>
          </li>
        ))}
      </ul>
    </div>
  )
})

2. 文章详情页

// app/routes/blog/[slug].tsx
import { createRoute } from 'honox/factory'
import { getPost } from '../../lib/posts'
import CommentSection from '../../islands/CommentSection'

export default createRoute(async (c) => {
  const { slug } = c.req.param()
  const post = await getPost(slug)

  if (!post) {
    return c.notFound()
  }

  return c.render(
    <article>
      <header>
        <h1>{post.title}</h1>
        <time>{post.date}</time>
        <div>{post.author}</div>
      </header>

      <div dangerouslySetInnerHTML={{ __html: post.content }} />

      {/* 评论区使用 Island 实现交互 */}
      <CommentSection postId={slug} />
    </article>,
    {
      title: post.title,
      description: post.excerpt,
    }
  )
})

3. 交互式评论组件

// app/islands/CommentSection.tsx
import { useState } from 'hono/jsx'

type Comment = {
  id: string
  author: string
  content: string
  createdAt: string
}

export default function CommentSection({ postId }: { postId: string }) {
  const [comments, setComments] = useState<Comment[]>([])
  const [loading, setLoading] = useState(false)

  const loadComments = async () => {
    setLoading(true)
    const res = await fetch(`/api/comments/${postId}`)
    const data = await res.json()
    setComments(data.comments)
    setLoading(false)
  }

  const submitComment = async (e: Event) => {
    e.preventDefault()
    const form = e.target as HTMLFormElement
    const formData = new FormData(form)

    await fetch(`/api/comments/${postId}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        author: formData.get('author'),
        content: formData.get('content'),
      }),
    })

    form.reset()
    loadComments()
  }

  return (
    <section class="comments">
      <h2>评论</h2>

      <button onClick={loadComments}>
        {loading ? '加载中...' : '加载评论'}
      </button>

      {comments.map(comment => (
        <div key={comment.id} class="comment">
          <strong>{comment.author}</strong>
          <p>{comment.content}</p>
          <time>{comment.createdAt}</time>
        </div>
      ))}

      <form onSubmit={submitComment}>
        <input name="author" placeholder="您的名字" required />
        <textarea name="content" placeholder="评论内容" required />
        <button type="submit">提交评论</button>
      </form>
    </section>
  )
}

4. 评论 API

// app/routes/api/comments/[postId].ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()

const commentSchema = z.object({
  author: z.string().min(1).max(50),
  content: z.string().min(1).max(1000),
})

// 获取评论
app.get('/:postId', async (c) => {
  const { postId } = c.req.param()

  // 从数据库获取评论
  const comments = await db.comments
    .where('postId', postId)
    .orderBy('createdAt', 'desc')
    .get()

  return c.json({ comments })
})

// 添加评论
app.post('/:postId', zValidator('json', commentSchema), async (c) => {
  const { postId } = c.req.param()
  const data = c.req.valid('json')

  const comment = await db.comments.create({
    postId,
    ...data,
    createdAt: new Date().toISOString(),
  })

  return c.json(comment, 201)
})

export default app

与其他框架对比

HonoX vs Next.js

HonoX 的优势:

  • 更轻量(核心更小)
  • 边缘优先设计
  • 更简单的学习曲线
  • 更快的冷启动

Next.js 的优势:

  • 更成熟的生态系统
  • 更多的官方集成
  • React Server Components
  • 更强大的图像优化

HonoX vs Astro

相似点:

  • 都使用 Islands 架构
  • 都注重性能
  • 都支持多框架

HonoX 的优势:

  • 更好的 API 路由
  • 原生支持边缘运行时
  • 更轻量的运行时

Astro 的优势:

  • 可以混用多个前端框架
  • 更丰富的内容处理功能
  • 更好的静态站点生成

HonoX vs Fresh

相似点:

  • 都基于 Islands 架构
  • 都使用文件路由
  • 都注重性能

HonoX 的优势:

  • 支持更多运行时
  • 更灵活的中间件系统
  • 基于 Hono 的强大生态

Fresh 的优势:

  • Deno 原生集成
  • Preact 默认支持
  • 更简单的配置

最佳实践

1. 合理使用 Islands

✅ 好的做法:

// 只将需要交互的部分做成 Island
<article>
  <h1>{title}</h1>
  <p>{content}</p>
  <ShareButtons /> {/* Island */}
  <CommentSection /> {/* Island */}
</article>

❌ 避免:

// 不要把整个页面都做成 Island
export default function Page() {
  const [state, setState] = useState()
  // 整个页面都会在客户端水合
}

2. 优化数据获取

✅ 好的做法:

// 在服务端并行获取数据
export default createRoute(async (c) => {
  const [user, posts, comments] = await Promise.all([
    getUser(),
    getPosts(),
    getComments(),
  ])

  return c.render(<Page user={user} posts={posts} comments={comments} />)
})

❌ 避免:

// 避免串行请求
const user = await getUser()
const posts = await getPosts() // 等待上一个完成
const comments = await getComments() // 又要等待

3. 使用流式渲染

// 对于慢速数据使用 Suspense
export default function Page() {
  return (
    <>
      <Header /> {/* 快速渲染 */}

      <Suspense fallback={<Skeleton />}>
        <SlowData /> {/* 异步加载 */}
      </Suspense>

      <Footer />
    </>
  )
}

4. 实现有效缓存

// 分层缓存策略
const app = new Hono()

// 1. 边缘缓存
app.use('/api/*', cache({
  cacheName: 'api-cache',
  cacheControl: 'max-age=60',
}))

// 2. 浏览器缓存
app.use('/static/*', async (c, next) => {
  await next()
  c.header('Cache-Control', 'public, max-age=31536000, immutable')
})

// 3. 条件请求
app.use('/data/*', etag())

未来展望

HonoX 还在快速发展中,以下是一些令人期待的方向:

  1. 更多的运行时支持 - 包括 AWS Lambda、Azure Functions 等
  2. 增强的开发工具 - 更好的调试体验、性能分析工具
  3. 更丰富的生态 - 官方插件、第三方集成
  4. 框架无关的 Islands - 支持 React、Vue、Svelte 等
  5. 增量静态生成 - 类似 Next.js 的 ISR

结论

HonoX 代表了现代全栈框架的一个重要方向:

  • 性能优先 - Islands 架构和边缘计算
  • 开发体验 - 简单直观的 API
  • 灵活性 - 支持多种运行时和部署方式
  • 类型安全 - 完整的 TypeScript 支持

如果你正在寻找一个轻量、快速、现代的全栈框架,特别是需要部署到边缘运行时,HonoX 是一个值得考虑的选择。

虽然它还比较年轻,生态系统不如 Next.js 那样成熟,但它的设计理念和技术方向都非常正确。随着 Hono 生态的发展,HonoX 也将变得越来越强大。

资源链接


欢迎在评论区分享你对 HonoX 的看法和使用经验!

Ryan Dahl 在 1 月 20 日给软件工程下了结论:“人类写代码的时代已经结束。”留下的工作里,不包括继续手写语法。

 

如果这话出自某个科技网红,大概刷过去就算了。但 Ryan Dahl 不一样——他不仅写出了 Node.js,后来还“推倒重来”做了 Deno。你可以把他的意思理解为:写代码这部分会越来越自动化,而人的价值会更多落在判断、取舍和责任上。

 

而在 Ryan Dahl 这次“宣判”之前,1 月 3 日,Ruby on Rails 作者 DHH 也在 X 上连发多条,语气罕见地偏“乐观派”:

 

“别让那些粗制滥造和尴尬翻车,遮住你对 AI 的惊叹。自从我们把计算机连上互联网以来,这是我们让计算机做到过的最令人兴奋的事。如果你在 2025 年一直对 AI 悲观或怀疑,不如在 2026 年的开端,用一点乐观和好奇再试试看?”

 

于是,社区里迅速冒出一种更夸张、但传播力极强的解读:“DHH 都松口了。”“连最不买账的人都开始给 AI 站台——你还有什么理由不用?”甚至有人干脆把它说成:“DHH 也扛不住了,最终还是向 AI 屈服低头了。”

 

但你真去听 DHH 的原话,会发现所谓“DHH 屈服论”,并不是那么回事儿。

 

在最新一期播客中,他说在 37signals,AI 没有在写真实产品,更谈不上“从零写出什么东西”。

 

他在用 AI,而且每天都用,但更多是做那种“一发入魂”的小实验;一旦进入真工程:要持续演进、要迭代、要打磨,他就会觉得:“这在浪费我的时间,到这一步我自己写更快。”

 

所以他们的新产品 Fizzy 里 95% 的代码,还是人类亲手敲出来的。

 

他还补了一句:我们离那种“AI 让一切始终更好、更快、更省心”的明显拐点,还差一点

 

“就现在而言,我仍然在意代码的样子。我在意它的美感。我在意打磨、推敲、润色。”

 

更关键的是,他不是在怀旧。他明确说:“手写代码依然有竞争力。”“至少在此时此刻,这是一个仍然有竞争力的选择。”

 

而且他的判断正好和 Ryan Dahl 相反:“我们并没有到 AGI,没有到那种‘人类写代码的时代死了’的程度。”

 

挺好玩的是,DHH 还说要远离 Anthropic 的 CEO:他一听到那种“再过五分钟就不需要程序员了”的口吻就火大,直接开喷:“你们到底用的啥模型啊?”反正他自己用的是 Opus 4.5(或当下版本),但在他的体验里,这种“程序员马上下岗”的说法完全不符合现实——尤其是那些要长期维护、持续迭代、不断演进的真实工程,离“五分钟结束”差得十万八千里。

 

以下是 DHH 播客整理全文翻译:

 

“如果浏览 Web 的不再是人类”

 

主持人:欢迎大家来到《Next Token》。今天这期节目对我来说有点特别,可能要追溯到 25 年前。很高兴请到 DHH——David Heinemeier Hansson。欢迎你。

DHH:很高兴来,谢谢邀请。

 

主持人:我猜你可能是刚从赛车里下来(笑)。

DHH:现在是休赛期,正好歇一歇。

 

主持人(Torsten):那我就先来点“热血沸腾”的话题。我从 2010 年左右就开始关注你,你可能是对我影响最大的前五位程序员之一。如果没有你,我可能不会走到今天。我职业生涯中有七八年都在写 Rails,看了你所有的书、博客。我们其实从没见过面,但有一次“交集”让我印象极深——我发过一条关于 Cookie Banners 的吐槽推文,那是我人生中传播最广的一条推文。那天中午我被 Cookie Banners 气疯了,随手发了一条,然后彻底炸了。第二天你转推并评论说:“这就是为什么人们不再浏览 Web,而是开始用 ChatGPT。” 所以我想直接问你:欧盟最近说要“取消 Cookie Banners”,你觉得这真的能改善什么吗?还是说——已经太迟了?

 

DHH:我认为 Cookie Banners 是 Web 体验变得糟糕的一个主要原因。它们几乎比早期那种弹窗广告还要糟糕——你知道的,“打地鼠”“打猴子”那种 2000 年初的弹窗。当年浏览器还能通过技术手段封杀弹窗,但 Cookie Banners 没有一个统一、有效的技术解决方案。我知道有插件能挡,但大多数人不会装。结果就是:Cookie Banners 成了互联网的一场瘟疫。

 

我是丹麦人,所以我觉得我有资格狠狠吐槽欧盟。Cookie Banners 最初的出发点是“高尚的”——限制数据收集、提高透明度。但这套东西在第一个 Cookie Banners 出现 5 分钟后,就已经被证明是失败的。可欧盟花了整整 15 年,才开始承认这个问题。现在他们说要“移除”Cookie Banners。

但“移除”是什么意思?你以为这就能抹掉你对互联网造成的破坏吗?不可能。接下来30 年,仍然会有大量网站继续保留 Cookie Banners——因为删掉它比留着更麻烦,或者网站早就没人维护了。

 

这是一件非常悲哀的事。当然,我并不是说:如果没有 Cookie Banners,人们就不会去用 ChatGPT。 那不现实。但它确实在可测量的层面上伤害了 Web,让浏览体验变得远比必要的程度更糟。

 

一旦你已经在用户体验上制造了第一道伤口,后面再多来几刀,心理成本就低多了。Cookie Banners 把“底线”拉得太低了,以至于很多 Web 设计师会觉得:再多放点广告、再恶心一点,好像也没那么糟。 这就像“破窗理论”。

 

主持人:那在 Cookie Banners 把 Web 搞成这样之后,你觉得互联网浏览的未来会走向哪里?

如果未来主要“浏览 Web 的不再是人类”,那这些问题还重要吗?

 

DHH:这是一个好问题。我觉得现在有很多聪明的人都在试图搞明白这件事,我们也在尝试各种不同的做法。某种意义上,这真的很像上世纪 90 年代中后期——当时我们在摸索互联网的第一个版本:这一切究竟会怎么运作?谁会掌握权力?谁会成为平台?谁又会成为把关者?所有这些问题,如今再次被抛回到空中,悬而未决。

 

不管我个人怎么看它最终会走向哪里,我都觉得这是一件令人兴奋的事情。互联网和计算技术,已经很久没有像现在这样让人感到兴奋了——上一次有这种感觉,还是在 2007 年。

 

那是 iPhone 刚刚问世的时候,我们迎来了一个全新的形态。随后经历了很长一段时间:好,一切都转向移动端了。而现在,我们又站在另一次巨大的转折点上——这一次,不只是“移动”不再以同样的方式重要了,它不再是你思考和构建产品时的那个主导视角。

 

与此同时,还有大量没有答案的问题。如果人类不再亲自阅读互联网内容,因此也不再阅读广告,那究竟是谁在为互联网写作?谁还会去生产那些美好的内容?当我们摆脱了 cookie 弹窗,重新拥有一个“干净体面”的门面,这件事真的还重要吗?

 

如果这件事本身已经不再重要,如果人们不再想为互联网写作,那 AI 又将从哪里获取它所需要的信息?我觉得现在有太多悬而未决的问题,以至于没有任何人哪怕稍微知道,最终的解决方案会是什么样子。而这,恰恰是活在这个时代最令人振奋的地方。

 

我毫不怀疑,将来我们回头看今天这个时刻时,会说:“好吧,这里发生了一次决定性的变化。”而且,这种变化在当下的可感知程度,甚至比前两次都要更明显。

 

互联网的出现,花了五六年的时间才真正渗透进社会,对整个社会产生巨大影响。后来是手机,速度快了一些,但也没有快到哪里去——iPhone 本身也经历了好几代迭代,我们一开始甚至都没有 App Store,这些东西都是慢慢才出现的。

 

但 AI 不一样。

 

AI 的出现,在当下这一刻就已经非常明显。任何一个用过第一版 ChatGPT 的人,都会立刻意识到:哇,这完全是一个全新的东西,它将重写规则。

 

所以,在这三次巨大的技术变迁中——互联网的诞生、移动时代的到来,以及现在的 AI——这是第一次,我们在实时发生的过程中就清楚地知道:世界一定会变得完全不同,而我们却不知道最终会变成什么样。

 

因此,我觉得你能做的最好的事情,就是接受三点:第一,我们不知道答案;第二,这真的令人兴奋;第三,赶紧上车,狠狠干脆坐稳了,看看它会把我们带到哪里去。

 

因为还有另一种冲动,过去在互联网时代出现过,在移动时代也出现过:那就是一部分人会说,“我更喜欢以前的样子。我喜欢变革发生之前的一切。我不喜欢 AI。我不喜欢也许会被整个互联网重新中介化。我不喜欢这些东西。我们能不能把一切都倒回去?”

 

不,不能。你没有这种权力。你无法把这些东西倒回去。

 

你当然可以在个人层面选择:我不用生成式 AI,或者我不买任何包含 AI 方案的产品。但这种想法,本质上是一种“阿米什式”的思维方式——而在任何时代,这都只是非常小众的选择。

 

如果这就是你,如果这就是你想与世界互动的方式,那很好,祝你一切顺利。我们有时候确实需要一些“疯子”来提醒我们:事情也可以用完全不同的方式来做。但这,并不会改变历史前进的轨迹。

 

“这真的是一个无比令人兴奋的时代”

 

主持人:你的兴奋更多来自哪里?是因为规则被打乱、棋盘被掀翻?还是因为你真的想用 AI 做事?

 

DHH:首先也是最重要的一点,我热爱计算机。我喜欢看到计算机做出以前做不了的新事情。说实话,让我觉得非常惊讶的是:有这么多在科技行业工作的人,其实并不怎么喜欢计算机——甚至包括那些每天都要和计算机打交道、让计算机“跳舞”的程序员,并不是所有人都真的喜欢计算机。

 

但我不一样。我爱计算机。我真的爱计算机本身,爱的是它作为一台机器的纯粹性。我并不是只把计算机当成一种“工具”,不是只想用它来完成某个目的。确实有一大类人,把计算机仅仅视为通往某个结果的手段。但不是这样,对我来说,这要更深得多——我就是单纯地热爱计算机这个东西本身,也热爱看到它去做全新的事情。

 

而现在发生的这件事,是计算机在我这一生中做过的最令人兴奋的新事情之一,至少可以和当年“计算机连上网络”这件事相提并论。

 

那时我们从 Commodore 64、Amiga 时代走过来,突然“砰”地一下就上网了,用小小的调制解调器拨号,连接世界各地的 BBS,听着它唱出那种刺耳却又美妙的声音——那同样是一次巨大的转变,也彻底改变了我和计算机之间的关系。

 

而现在,很可能是第二次这样规模的变化。

 

另一件让我感到兴奋的,是棋盘被彻底翻转了。尤其是我们已经形成了一些根深蒂固的格局。比如 Apple,我和那家公司有过不少摩擦。我非常期待看到 Apple 通过 App Store 以及整个移动生态所建立的那种“封闭控制”,被彻底掀翻,因为它也许将不再以同样的方式重要。

 

当然,我也并不天真到以为:只要棋盘一翻转,接下来就会迎来一个人人和谐共处的“涅槃世界”,一切都会变成开放平台,没有任何人占据主导地位。这显然不可能发生。不管最终的主导者叫 OpenAI、xAI、Google,还是别的什么名字,某种形式的集中和垄断,迟早都会出现。

 

但至少在现在,我们还处在“尚未整合”的阶段。有这么多公司同时在追逐前沿模型,却没有任何一家明显胜出。

 

就在五秒钟前,整个科技行业还准备给 Google 判死刑——“他们错过了浪潮”,“早期研究是他们做的,《Attention Is All You Need》那篇论文也是他们团队出的,但后来落后了整整九个月”,当时大家已经在谈论 Google 的衰落了。而现在,他们也许又重新回到了领先位置,至少在某些领域确实如此。

 

这种不确定性本身就让人兴奋——我们并不知道,最终谁会占据主导地位,甚至都不确定“主导地位”这种东西是否一定会出现。

 

这件事也很有意思。就在几周前,我还在推特上说,跑本地模型这件事有点“奇怪”。因为我之前试过一些本地模型,说不上什么时候,总之那时体验一般。但就在这周,我又开始重新跑本地模型,然后我心里想:“靠,我之前说的话,保质期也太短了吧。”

 

现实变化的速度已经快到:三个月前说的任何一句话,现在看起来都可能有点傻。

 

而且我真的被本地模型现在的水平震惊到了。它们当然还比不上最前沿的模型,但如果再往前看两年呢?有没有一种可能,根本不会出现一个“唯一的赢家”?赢家反而会是开放模型?最终的局面,会不会类似开源软件对后端软件世界造成的影响?

 

过去我们是有绝对主导者的。我们有过 Sun,有过 IBM,在某种程度上也有过 Microsoft。但这些都已经不存在了。整个后端世界——从 Linux 到各种数据库,再到 Ruby、Rails,以及所有这些东西——几乎全都是开源的。你再也看不到那种一家独大的绝对统治。

 

而在另一边,在前端世界,尤其是移动端,我们却看到的是彻底的垄断:只有两个赢家,Google 和 Apple。他们对平台拥有完全的控制权,而且还在不断收紧螺丝。我们唯一的希望,似乎只剩下立法或监管,而说实话,我对这条路也已经越来越悲观了。

 

所以现在的局面真的很令人兴奋——它可能朝两个完全不同的方向发展。

 

我们很可能还是会走向某种形式的垄断,因为这是面向用户的界面层。而在历史上,我几乎想不起有哪个时代,这种层面没有被“征服”过。

 

但也有另一种可能:这些开放模型会好到一个程度,以至于“谁占据商业主导地位”这件事根本不重要,你甚至不需要那种商业上的统治。

 

这真的是一个无比令人兴奋的时代。

 

“我们的产品也试过 AI 功能,但最后都没上线”

 

主持人:这挺有意思的——你正好是在这个变动时期推出新产品。HEY 大概是五年前发布的,然后最近 Fizzy 也上线了。我们特别想知道:37signals 内部现在到底在发生什么?你们到底怎么用 AI?你们做 Fizzy 的时候,用没用 AI?用到什么程度?我很想听点“细节层面的现实”,AI 在 37signals 具体怎么落地、怎么被用起来的。

 

DHH:哦,用的,当然用。我们每一个开发者都在某种程度上使用 AI。我自己每天也在用 AI。

但我也得先加一句前提:我虽然对我们即将进入的新现实非常兴奋,但我每天处理的仍然是“此时此刻真实存在的东西”。你必须学会在“ hype 的列车”和“现实的列车”之间保持平衡。

 

而在我的“现实列车”里,AI 没有在写 Fizzy(一个 Kanban 工具)。

 

AI 也没有从零写任何东西。

 

我确实用过 AI 做过各种“一发入魂”的实验——但它们通常都只停留在“一发入魂”。因为只要我进入真正的细节:要持续演进、要迭代、要打磨,我就会想:“嗯,这就是在浪费我的时间。到这个阶段,我自己写反而更快。

 

当然,AI 在另一些方面确实能大幅加速。我们在做这些产品时,也在一定程度上使用 AI。但我们并没有大量用 AI 来写 Ruby 代码。如果用 AI 写 Ruby,通常也只是“机械式翻译”——比如:“这里有个我们知道已经存在的东西,你能把它用 Ruby 版本写出来吗?” 它能给出一个初稿,有时候会稍微帮点忙。

 

AI 更有价值的地方是在我们的一些 Go 代码上,因为那里面“样板代码”更多,收益更明显。

但即便是 Ruby 和 Go 这两块,也谈不上“改变游戏规则”。

 

真正改变游戏规则的是:

  • 你想学习一个新 API

  • 你想理解一个新概念模型

  • 或者我们做实验,直接用 AI 去尝试构建“能真正带来价值”的 AI 功能

在这些方面,收益更大。

 

但我们离那种——某些 CEO(比如 Anthropic 的 CEO 那种语气)说的——“再过五分钟我们就不需要程序员了”还差得远。我就想问一句:你们到底用的是什么模型?我用的是 Opus 4.5(或者现在的版本),但那种说法完全不符合现实——至少对于“持续演进”这类工作来说,是完全不成立的。

 

我仍然保持开放心态,我也能看到那种承诺。我记得互联网在 1994、1995 年那会儿是什么状态,我当然能做外推:我们也许真的会走到那一步。也许我们会到一个阶段:人类不再编写大多数代码。

 

但如果你看 Fizzy:95% 的代码,是人类亲手敲出来的

 

主持人:有意思。真的?你们内部也这样认为?

 

DHH:你回头看 Fizzy 的整个开发历史,会更有意思。我们在 Fizzy 里做过一堆 AI 功能实验:我们试过做一个 AI 驱动的命令行,用来和卡片(cards)交互;我们也试过 AI 摘要,给一些内容自动做总结。但最后这两项我们都没有发布

 

Basecamp 也是一样:我们实验过很多不同的 AI 功能,但没有一个能达到“明显更好、用户会一直爱用”的标准,所以都没进最终版本。

 

我仍然相信未来这会改变。只是我们现在还没到那个时刻。

 

我也见过其他地方做得更成熟的案例。比如我在 Shopify 董事会,Shopify 做的 Sidekick(他们的 AI agent)——用来帮助商家搭建店铺、优化店铺——真的很不可思议。那里面有一些非常具体、非常可触达的收益,我觉得几乎无可争辩。

 

我们仍然处在一个阶段:距离“AI 让一切始终更好、更快、更省心”那种明显的拐点,还差一点。

 

也正因为还没到那个拐点,所以才会出现一些反弹——我认为其中不少反弹甚至是合理的。

因为很多人用了所谓“AI 功能”之后会觉得:“这玩意儿太烂了。”“不更好,也不更快,甚至很蠢。”

 

比如摘要。我们刚刚还提到 Apple。Apple 对新闻、短信之类的摘要,我真不知道有多少人真喜欢开着它。它在很多情况下都离谱地糟糕、离谱地错误。连 Apple 这种体量的公司都做不对,那你基本可以合理推测:很多别的公司也同样做不对。

 

不过我也想强调:最近我们确实找到了几个非常好的 AI 用例。其中一个是我们的安全漏洞赏金项目(通过 HackerOne 运行)。我们会收到海量的报告——某个研究员声称在我们的应用里发现了漏洞。我们必须处理这些报告,而现实的数学非常残酷。我们大概会收到……可能一个季度 300 份报告之类的数量。但真正“靠谱、有效、值得修”的——大概只有 3 份。

 

也就是说,真正有价值的比例大概只有1%。而这个 1% 非常重要,因为它们可能真的指出了一个严重问题,我们必须修。但为了抓住这 1%,你必须花巨大精力去验证剩下99%的垃圾——这对团队来说是巨大的麻烦、巨大的时间黑洞、巨大的烦躁来源。

 

AI 在这件事上简直太厉害了:它能在报告进来时就先处理一遍,给我们一个初步判断——“这到底是扯淡,还是不扯淡?”然后还会帮我们写回复邮件。

 

而写回复其实才是痛点的一半:当 99% 的提交都是彻头彻尾的狗屎,写这些狗屎的人还常常—— 根本不懂自己在说什么,却又特别理直气壮,还特别不耐烦,甚至还一副“你必须立刻给我 5000 美金赏金”的态度。

 

这时候让人类程序员保持冷静、不直接对他们开喷,是很难的。真的,你会很想直接骂人。

 

AI 就完全没这个负担。它特别乐意用一种非常冷静的语气写一大段回复:“为什么你这个东西不成立。”它帮我们省了大量时间。

 

主持人:有意思。所以 AI 是拿到报告之后,去看你们代码库,然后判断它到底对不对?

 

DHH:对。没错。就是这样。把这两件事结合起来。

 

主持人:听起来需要一点技巧:拿到安全报告,很多是垃圾,但到了某个层级,你确实得打开代码去确认“这到底是不是真的”。

 

DHH:以前要看 100 份报告,现在可能只要看 5 份——这就是真实的生产力提升。就算你最后要看 10 份、20 份,只要你能把原本 100 份的工作压缩到 20 份,这就是 AI 承诺的生产力收益。如果我们能把这种压缩能力用到业务的其他方面——那简直太好了。这也是为什么我们一直在尝试把 AI 用在一些具体环节上。

 

另一个我们断断续续尝试了好几年的方向是客服支持(support)。但 support 很微妙:如果你只能 90% 正确,那其实很糟糕。因为这意味着你会有 10% 的概率把事情说错——而且是对着客户说错。你如果给客户一个完全错误的答案,让客户体验很差,客户可能就直接流失了。

 

那这个客户的终生价值是多少?

 

你以为 AI 带来的那点“节省成本”,可能瞬间就被一次流失抵消得干干净净。我们上一次认真测试让 AI “做完整客服链路”,大概是 18 个月前左右。效果不太行。但一切都在飞速变化。我知道 Intercom 有一个叫 Finn 的 AI agent,采用得很好,看起来我们也确实该再试一次。

 

而这又回到我最初的那种兴奋:一切变化太快了。

 

有些人会觉得这很让人迷失方向,我觉得这也是很多焦虑的来源。但如果你像我一样,只是单纯喜欢看计算机变得更强大——那现在真的就是一场大戏。坐在第一排,实时看它发生。

 

我们从“那个吃意大利面的人”——看起来像噩梦一样的生成图——走到了今天这种几乎不可区分的输出。接下来,我们很可能会在更多领域看到同样的跃迁。你得保持一种“敬畏感”和“惊奇感”。

 

如果你此刻身处这个行业,和计算机打交道——你的“惊奇感”就是你的安全绳。它能对冲焦虑,对冲不确定性,让这一切变得可承受。

 

当然,我们并不能消除不确定性和焦虑。比如:我的工作三个月后还存在吗?这种焦虑非常合理。但你可以用惊奇感来对冲它:“这些硅做的小东西也太聪明了吧。”

AI 时代,为什么你发布的产品别人看不见?

 

主持人:它们真的很神奇。这就引出了一个更大的问题:软件商业模式的未来到底会怎样?这确实很神奇,但也真的太不一样了。你能不能展开讲讲:创业公司会走向哪里?软件产品会走向哪里?软件工程师会走向哪里?未来到底会怎样?

 

DHH:有一点我现在非常确定:今天发布一个新产品,从“把它做出来”的角度看,是史上最容易的。AI 让构建更容易;工具史上最好;Ruby 和 Rails 也从未如此成熟。对所有人来说,这都很棒。结果就是:市场被海量新产品发布淹没了——永无止境的“上百万、上亿级别”的新发布。

 

这就是你现在要面对的现实。门槛被降低了。而我不确定所有人都会在“轮到自己发布时”还为门槛降低而兴奋——因为你一发布,可能就是一片寂静,连个回响都没有。我们刚发布 Fizzy,算是一次不错的发布,但它并没有像我们历史上某些发布那样“声量巨大”。

 

这当然不只是 AI 的原因,还有社交媒体算法的原因。以前,我在 X(Twitter)上有粉丝,他们就能看到我发的东西。但现在,你会发现:X 上正在发生 Facebook 在 2010 年左右发生过的那一幕——你有粉丝,但你触达不了他们,除非你付钱给平台“买触达”。

 

但现在甚至都不只是“付钱”这么简单。问题变成:我甚至都看不到我合伙人 Jason 的推文了。除非他发了一条“爆款(banger)”,爆到病毒式传播,否则他的内容就不会出现在我的 For You 页面里。一切被压缩成了“你能不能发出爆款”。

 

拥有大量粉丝这件事的价值,被严重稀释了。我在 X 上有五十多万粉丝——这在我发一些犀利观点、能引起传播时依然好用。但当我想发“右勾拳”(也就是营销、转化)的时候,它不再提供过去那种收益。当然,这种变化也不全是坏处。现在小账号也可能爆:就算你只有 10 个粉丝,只要你发了一条爆款,算法也可能把你推上去。算法选赢家和输家的方式,反而让那些没有花 20 年积累粉丝的人受益。但这真的好吗?我大概发了 7 万条推文——这真是离谱。但 18 年下来,这些投入几乎没有“可积累的剩余权益”(residual equity)。

 

我不确定这是不是我们长期想要的生态。但可以确定的是:对我们的营销方式、产品发布方式来说,这已经是一个全新的世界。

 

我们公司现在的阶段是:我们能承受“靠一靠、观望一下”,说一句“挺有意思”。但如果你还处在“必须打出名气”的阶段,你肯定会更焦虑。因为以前那套打法,已经不像过去那样奏效,你得发明新的东西。

 

事实上,这种认知直接影响了 Fizzy 的发布策略:我们承认——你不能再用老办法发布产品了。你手里的名单、你已有的受众,不可能再用“传统方式”被激活。你需要持续不断的“滴灌”:一滴、一滴、一滴。

 

如果我们希望 Fizzy 这个品牌能在用户心里留下印象,以至于当他们遇到我们要解决的问题时,会想起它、会去 fizzy.do,我们就必须设计一种策略,让我们能一直这样做下去。这也部分解释了为什么我们从一开始就把 Fizzy 开源。

 

把 Fizzy 从发布第一天就开源——

  • 对所有想学习“生产级 Ruby/Rails 应用如何构建”的人来说,这是一个巨大的礼物;

  • 同时,对我们来说,它也给了我们一个“更频繁谈论 Fizzy 的许可”。

 

现在社交平台上,纯商业化的转化号召(call-to-action)越来越推不动。以前它传播力也一般,但好歹还能“硬塞”一下——那就是所谓的“右勾拳”。现在右勾拳打不出去,你就得换一种卖法。我目前觉得最管用的策略,是把“给价值”和“求转化”合成一拳:轻击(jab)和右勾拳(right hook)不再分开打,而是同一条内容里同时完成。

 

比如我会发:“Fizzy 里有个很酷的小功能——可能是我们做的,也可能是社区做的,或者我只是想提醒你注意到它。”这条对开发者有用;与此同时,我也顺势把品牌名反复露出来:Fizzy、Fizzy、Fizzy……品牌就是靠重复进入脑子。

 

关键是:重复仍然有效,但必须绑着价值一起出现。光当“慷慨的好人”持续免费输出已经不够了——你得把输出和你正在做的产品强绑定。这就是我们现在的打法。当然规则也可能随时被改写,但就此刻来看,这就是现实的游戏规则。

 

主持人:你说“现在你只要把东西做出来就行”,这句话听起来很有趣,因为我觉得你以前不会这么说。你从一开始就很重视营销——从最早的 Rails demo、到各种“挑衅”、到你如何推销愿景……你一直都在想怎么卖、怎么讲故事。但现在市场被淹没了,好像营销反而变得更重要。

 

更巧的是,我们内部也在聊类似的事。我们在做 AMP(我们在做一个 coding agent),我们内部一直说:现在外界没有太多“强烈的 OTE”(那种外溢式的注意力/势能)。我们想做的是:用一个故事把人“拉着走”——告诉他们我们在这个动荡的时代学到了什么,让他们产生一种感觉:“如果你跟我们走,门是开着的;如果你跟我们走,我们会分享我们学到的东西。”这不是那种“社交媒体上再来 10 个小贴士”的套路,而更像是:“我们一起干这件事。”

 

而你刚刚说的,正好对应了很多人最近在讲的: “爆款发布(big launch)这套已经不灵了。”Product Hunt 死了。Hacker News 的 launch 也……

 

而且我认识 Fizzy,就是因为 Jason 一直在 X 上做这些小 screencast:“现在进展到哪了”、“这里出了一些 X 问题”、“这里哪里又崩了”。我会偶尔刷到它们,可能是 Grok 或者算法觉得我会喜欢。但我的感觉是:我被“拉着走”了——像在跟着你们一起把产品做出来。所以我后来才注意到:噢,原来它上线了。

 

DHH:你说得对,这确实是我们这个时代发生的巨大变化之一。我记得我们在 2006 年写《Getting Real》(那本书)时,我们谈过“爆款发布(blockbuster launch)”这套模型:先放 teaser(预告),再放 trailer(预热视频),最后来一个 blockbuster launch(大爆发)。

 

这套模型已经死了。爆款不再发生。因为我们已经没有共享文化了。没有共享的事件。我们只有每个人各自的个性化信息流——正如你说的,算法之神决定:今天给你投喂哪一小块“刚好合适”的东西。所以,一方面,你必须“灌满渠道”(flood the channel)。

 

另一方面,也有个有意思的反面:以前我会更克制,比如提醒自己别发太多推。有时候我会突然进入那种“多条意识流同时开喷”的状态,但在过去你会想:“哎,我今天已经发第七条了,会不会太多?”

 

现在这种限制不存在了。你一天发 100 条都没关系。因为你不会“淹没”任何人的 For You 页面——算法会替你处理。而你发得越多,你就越有机会让一些小种子落地、生长、发芽。你还需要更长的周期。

 

爆款发布以前的核心逻辑是:“就在这一天,我们发布,然后所有人都在这一天关注。”现在不会了。大家不会在同一天关注同一件事。但随着时间推移,如果你把“发布”理解为:一整个季度、或者一年、甚至某些情况下是一整个十年——你依然可以做“分步骤的搭建”,依然能起作用。因为营销的底层真价值仍然成立:口碑传播、故事激活、好产品、好钩子——这些依然有效。

 

只是,它变得慢得多。你不会再看到那种巨大峰值,然后被“发布日的高潮”爽到。某种意义上,现在的发布没有那个“超级尖峰”了。当然,很多人本来也从来没有过“超级尖峰”,因为大多数发布都什么也不会发生——失败一直是常态。但我现在更强烈地觉得:你越来越难“工程化制造一个爆款”。

 

这个夏天我又学到(或者说被提醒)了一点。我在做一个项目叫Omarchy——一个 Linux 发行版。我做得很开心。当我推进它时,我从营销角度体会到:如果你不断分享项目进展、再配合一个疯狂的发布节奏,价值非常大。

 

我记得第一个月我做了大概 40 次发布?简直离谱。节奏快得惊人,整个过程一直都充满了不确定性,所以特别刺激、特别带劲。这让我可以连续三个月“轰炸”所有人的信息流。更有意思的是:人们明明意识到自己在被轰炸,却仍然无力抵抗。我收到过无数条推文,大意都是:“行行行,我第 17 次听说 Omarchy 了,我服了,我试一下。”“我投降,好吧,我装。”这又回到了营销最本质的东西:重复。

 

有一个老的经验法则(我也不知道现在是不是过时了):你需要听到一个品牌七次,它才会在你遇到问题时被激活——你才会想起它能解决什么。所以我当时就是在努力让尽可能多的人“听到七次”。同时我也在做 Jason 说的那个:enthusiasm transfer(热情迁移)——把创作者的兴奋感转移给别人。这一直是营销的一部分,但现在比以前更重要,因为营销越来越“人格化”。

 

我们还发现:社交平台从来就不怎么喜欢公司账号,但现在它们几乎把公司账号都“幽灵化”了。我们公司账号发什么都没用:从 37signals 发,没人理;从 Basecamp 发,也没人理。一片寂静。然后我看到一些“巨型媒体账号”——几百万粉丝那种——表现也一样惨。这就是算法:它现在真的讨厌品牌账号。除非你是那种“神级品牌账号”——有账号运营团队,能自己成为内容源。

 

但另一部分也让我们意识到:这游戏即便对我们而言仍然很残酷——而且很耗人。这种耗人让我想起我听一些 YouTuber 讲过的东西:如果你是 influencer(网红)、content creator(内容创作者)——这俩词简直是现代词汇里最让我厌恶的词之一——你就会被迫持续生产内容。

 

你维持曝光的方式只有一个:不停输出、不停输出、不停输出(chop chop chop)。以前还有一种“喘息”:你做完 teaser、trailer、爆款发布,然后你还能休息五分钟。现在不行了。那种节奏不存在了。所以一切的速度被推到一个夸张的程度。说实话,我很庆幸我现在不需要“去攒人生的第一桶金”了(笑)。

 

主持人:我们最近也在高频发东西:过去 10 天我们写了 8 篇 release post。这和你做 Omarchy 的方式很像:你需要重复。但那种 5 年前的“空洞重复”已经不行了——比如:“两天前我们大发布,记得吗?”“一周前我们大发布,记得吗?”这种完全没效果。你必须一直有新内容,否则算法不推。节奏太夸张了。

 

而在我们这个做 AI agents 的领域,你还会被大模型厂商不断“催更”——他们两天发一个新模型,用户两天后就来问:“你怎么还不切?怎么还没上新?”所以现在疯狂的事情特别多。

 

我的问题是:你写过《It Doesn’t Have to Be Crazy at Work》(工作不必这么疯狂),但现实已经如此——这在实践中到底怎么改变软件开发?你一直是小团队、小公司路线的拥护者。 但现在如果你想让产品成功,你好像必须把一天切成两半:一半写代码,一半发推、做内容、做传播、分享进展。你觉得这会怎么影响未来的软件开发者/软件公司?营销和软件是在融合吗?

 

DHH:我一直都说:这些东西本来就是一回事。“Marketing is everything(营销就是一切)”——这是《Rework》里的一章。而“everything”真的就是一切:软件、发布、客服、那些乱七八糟的推文、写作、播客……全都是。我们这么干已经 25 年了。但我同意:现在的节奏、算法的胃口,确实到了一个“无底洞”的程度,这种感觉以前没有这么强烈。不过我也觉得:这可能就是竞争加剧的样子。

 

当年我们做 Basecamp 的时候,行业比现在小太多了。那时做 Web 产品的团队少得可怜,以至于我们能关注到每一次发布。后来进入 Product Hunt 时代,你至少还能“一天看一个新东西”。现在结束了。

 

甚至 OpenAI 发一个新模型——那可能烧了 4 亿美元——它也只能获得几个小时的峰值关注与兴奋。

 

所以,它在很多方面变得更难了。可另一方面,基本面依然没变,你得小心别被这些压力带着跑偏。做有趣的东西、做值得讲的东西——这带来的杠杆还在。

 

你要“脱颖而出”的难度变大了,因为参与者更多了。

 

但只要你真的突出,注意力仍然在那里。注意力并没有从系统里被抽走。甚至可以说:注意力比以往更多,因为参与系统的人更多了。

 

这有点像 Spotify。你总听音乐人抱怨 Spotify 付得太少,但你再看数据:音乐产业的规模依然很大,甚至更大,而且在很多情况下,更多收入是直接流向音乐人(因为他们不再必须签那些苛刻的发行合约)。

 

所以一部分现实就是:我们在抱怨“事情太美好了”,但又没有人真的开心。

 

有个段子讲得很好:“一切都很棒,但没人开心。”我觉得这确实说中了某种人性。事情确实很棒:越来越多人能更快地做出东西。而这自然会带来更多竞争。资本家最讨厌的一件事是什么?是竞争。这就是那个系统最大的讽刺。我们都在拼命挖“护城河(moat)”。但护城河是用来挡谁的?不是挡“龙”(Not dragons)——是挡竞争对手。

 

竞争对手,这才是护城河真正要挡的东西。这个隐喻本身也很有趣:你会想,那它把谁“圈”在里面?客户?你在护城河里放鳄鱼,让客户别游出来?这个隐喻挺自利,也挺资本家叙事的。但无论如何,我玩这个游戏,也乐在其中。同时我也很高兴——现在我比过去任何时候都更清楚地知道:我对“什么真正有效、什么无效”的确定性变少了。

 

一直以来,很多东西本就是谜。比如我们 2004 年发布 Basecamp,它一路成了现象级成功,今天仍然成功。

 

我经常会想:为什么?为什么偏偏是 Basecamp?在我 25 年的职业生涯里,我做过很多东西,但没有任何一个产品层面的命中,能像 Basecamp 这么“正中靶心”。我至今也不完全明白原因。尤其是现在,Basecamp 所在的领域竞争者多得多。但每周仍然有成千上万的人注册一个新的 Basecamp 账号。每周我都会想:这怎么可能?怎么会每周都有几千几千人来注册?

 

这一直是个巨大的谜。

 

我觉得这种谦逊非常重要——无论你在做产品、还是在做营销,你都要记住:你不可能了解一切。你不可能确切知道什么有效、什么无效。你能做的,是去尝试很多东西,然后得到一些迹象、一些推力、一些暗示:市场想要什么、算法想要什么、客户想要什么。

 

但你不可能制定一套“主战略”,并指望它具备可重复的复刻性。即便是在一个高度“爆款驱动”的行业——比如我刚刚提到的音乐行业——也没人真正搞明白。的确,有些人比别人更擅长做出爆款,但也没有谁掌握一套公式:“照着这套流程,我们就能稳定生产爆款。”商业也是一样。

 

只是现在曲调又变了。你可以因此沮丧:“我以前那套把戏不灵了。”也可以因此兴奋:“什么?那我更迫不及待想学习——现在到底什么才有效!”我也接受一个现实:我不可能永远拥有过去拥有的一切。世界不是这样运作的。

 

“独立开发者”之梦没变:核心还是“一个人也能干”

 

主持人:我感觉我们好像回到了 2004 年。我记得你发布 Basecamp 的时候,你在 YC 还是哪里做过一个演讲,你当时大意是说:如果你有个想法,然后能找到 1000 个客户,每人每月付你 25 美元,你的人生就彻底不一样了。那次演讲就是我决定辞掉 Web 开发工作、去做 Dropsend 的起点——也开启了我整个职业生涯。

 

我觉得我们又回到了那种状态:现在你真的可以有一个想法,甚至可能是“一人团队”。所以,我们现在是不是就处在这个阶段?还是说,所有 indie hackers(独立开发者)最终都会被“吃掉”?这难道不是好事吗?

 

DHH:我也觉得这是好事。而且这里还有个讽刺点:我 20 多年来一直在讲——开发者生产力真的重要

 

这就是 Ruby 和 Rails 的核心前提:你不需要一个八人团队,你一个人也能做出来。Rails 从一开始就试图成为“单人开发者的框架”,而且我认为它在这件事上比几乎所有框架都做得更成功。

 

而我们今天对 AI 兴奋的原因也一样:我们对小团队能获得的杠杆感到兴奋,因为 AI 能做很多事。

 

有一个根本事实没变:当你降低实验成本、降低构建一个“值得做的东西”的生产力成本时,你就会有更多“射门次数”(shots on goal)。

 

Ruby + Rails 能做到这一点;AI 也能做到;甚至更好的是:AI + Ruby on Rails 一起做到。

但我不确定游戏的本质在这点上发生了根本变化,也许只是变得对更多人可及了。

 

我觉得这大概率是好事——不,只能说:这就是好事。我们应该从“对人类整体有什么分类级别的好处”来理解:对全人类而言,难道不是更好——我们有更多实验吗?即便最终“命中并变成可持续商业”的人,可能比例更低(我甚至不确定这是否属实,但先这么假设)。

 

而作为一个文明整体,我们最终仍然会在更多类别、更多细分领域里,更快地获得更好的软件。问题的一部分在于:无论是 Web 开发圈,还是独立开发者(indie hacker)圈,很多讨论都过于短视地集中在那些我们一直反复折腾的“通用大类”上。

 

比如待办事项应用。好吧,我职业生涯里大概已经做过七个了,而全球可能已经有二十亿个同类产品。最后真正成功的,可能也就那么几个,剩下 99% 都失败了。

 

但你知道吗?你有没有试过给美发沙龙做软件?他们可没有一万种选择。有时候,他们甚至几乎没有任何选择,除了那些“狗屎一样”的系统。那种三十年前做出来的烂软件,出自一些对“好软件”毫不在意的人之手。所以,如果你愿意跳出这些吸引了绝大多数人的大而泛的领域,其实机会依然多得很。

 

颇具讽刺意味的是,我自己长期以来恰恰以“不去碰这些方向”为傲——只解决我自己的问题。因为我觉得那样更简单,而且也确实如此:当你解决的是自己的问题时,你立刻就能判断你做出来的软件到底好不好。

 

这并不意味着它一定会成功,但至少你有了第一道过滤器。如果让我去给美发沙龙做软件,我其实并不知道什么是好、什么是坏,我得不停地去问别人:“你们怎么看?你们给我什么反馈?”老实说,我不确定自己是否适合为了正在构建的软件,去进行这么多和他人的互动。

 

但我认为,对那些愿意这么做的创业者来说,机会是非常多的,而这其实也是大多数人。只要我们稍微把视野放宽一点,不要总是说:“天啊,现在再做一个新的待办事项应用太难了。”因为这个领域在过去三十年里,已经被来来回回地“薅”了大概五十亿次。

 

但你往外看——就只要离开它五米远——到处都是一大片未被开发的绿地。真的,到处都是。

 

DHH 说 95% 代码是手写的,但他又天天用 AI

 

主持人:David 你说 Fizzy 95% 的代码还是手写的,对吧?你每天都在用 AI。但对我来说,今年正好相反:我现在大概 90% 的代码都是 AI 写的。所以我的疑问是:如果你说你不怎么用 AI 写代码、或者 AI 不替你写代码——那生产力提升到底从哪里来?尤其对一家小公司来说,比如给美发店做软件,它不需要庞大的客服团队,也不需要很多外围部门,核心就是把软件做出来、交付出来。所以你觉得 AI 让软件开发更快的关键在哪里?

 

DHH:我说说我自己的体验——从这波 AI 开始我就一直在用。

 

我的生产力提升,主要来自:它让我更强、更聪明、更快——

  • 更快上手新 API、新技术

  • 更快理解新概念(我会让 AI 解释给我听)

  • 更快找到“为什么这个 bug 会这样”的正确线索

 

比如 Omarchy 这个项目,如果没有 AI,它就不会存在。我不会有耐心去 Linux 论坛里翻半天,去解读那些晦涩的错误信息到底是什么意思。这对我来说不可能。

 

AI 带来的巨大提升,是给了我一个地方,把错误信息贴进去,然后得到比那种居高临下、还过时三年的 Stack Overflow 回答更好的线索。

 

收益巨大。真的巨大。

 

还有我需要读某个东西时、学习某个东西时,它也很有帮助。举个快例子:我们最近把 Rails 的 CSRF 防护机制改了——从以前“把 token 放进 cookie”的方式,改成使用现代浏览器的新特性:通过一个 header 来做。

 

我可以直接问 AI:“那个 header 是什么?”“什么时候开始支持的?”“具体有哪些细节?”这些答案我当然也能手动查:去 caniuse.com、看历史、查 RFC……全都能做。但 AI 能把这些东西一盘端上来,整合在一起,省事又快。

 

“AI 只是让我变聪明了”

 

我能更快学到更多东西。而这正是我真正喜欢的地方:不是让 AI 替我做事,而是用 AI让我更聪明

 

当然,这种模式未来未必会成为主流。

 

就像你说的,你已经让 AI 写很多代码,甚至多数代码。我完全准备好在某个时点,我也会进入那种状态。

 

但就现在而言,我仍然在意代码的样子。我在意它的美感。我在意打磨、推敲、润色。

 

这可能是一种“奢侈”,有点像现代的马鞍匠:他会在意字母压得是否刚好、针脚是否完美。你可以说:“但你已经不是交通运输的主力生产体系了。”我会说:那又怎样?只要我还享受,我就会继续做我手写代码的“马鞍”。

 

而且我也意识到:这种模式目前仍然是有竞争力的。

 

在 37signals,我们并不觉得自己在产出能力、发布能力、改进能力上落后。因此我对一些说法保持怀疑:“AI 已经强到可以把标准 SaaS 公司的一半程序员裁掉,还能跑得更快。”我没看到。

 

我当年也用同一套“根本测试”来审视云计算:“我们能不能用更少的人、花更少的钱,做更多的事?”我们几年前退出云,就是因为这个测试没有通过。而且我也不太听说这个测试在别处通过过。云计算并没有让你把运维团队砍半、把基础设施预算砍半。很多时候恰恰相反:上云之后团队规模翻倍,账单翻四倍。

 

主持人:你们切换之后是不是省了类似每月一百万美元?很夸张的数字?

 

DHH:我们现在大概是一年省200 万美元。我们云预算峰值大概是 340 万美元,现在的持续成本在 100 多万美元左右。所以在成本上,节省非常巨大。

 

这和 AI 有一些相似之处——不完全相同,但有相似之处:我觉得现在很多人在用 AI,脑子里觉得自己“好高产”,但他们其实交付更少、做出来的东西更少,甚至理解得更少。

 

“Vibe Coding”的风险:能力会从指尖流走

DHH:AI 还有另一个因素:当我尝试“氛围式写代码”(vibe coding)的时候——尤其在一个我还没完全内化的新领域——我能明显感觉到我的能力在从指尖滴走。

 

我刚开始做 Omarchy 时,写了很多 bash。我以前从没系统写过大量 bash,最多就是命令行里用用。然后我发现自己一次又一次问 AI:“某个 if 条件到底怎么写?”

 

这时你就会想:“为什么我没有内化这件事?我没内化,是因为我把它外包给 AI 了。”那这样更好吗?我现在更划算了吗?还是说,我跟当年那些老师一样天真:他们以为有了计算器,学生就不需要背乘法表了?不对。如果你不能迅速在脑子里算出 7×7,你真的会把自己变成傻子。

 

主持人:那你有没有形成一种直觉:该在哪里划线?你不可能知道一切,对吧?你也会把你不会的事交给信任的同事去做,你不会因为让同事设计某个东西就觉得“能力在流失”。你能接受:“这事我不需要会 / 我不想会”。那在 2025 这样疯狂的一年里,你有没有更清晰的边界:哪些你想自己掌握、哪些你可以忽略?比如 bash。为了推进 Omarchy,你觉得 bash 该学到什么程度?又有哪些可以不学?

 

DHH:我觉得我得会几乎全部,除了怎么在 bash 里搞数组(笑)。因为 bash 里数组那玩意儿复杂得离谱,简直反人类。但我其实认为:人类大脑是个很惊人的器官,它不会像 LLM 那样“容量到顶就装不下”。我们用得越多,记忆和能力的“配额”会增长。

 

所以我真正担心的趋势是:随着时间推移,我知道得更少、我变得更不胜任。我需要一条向上增长的移动平均线。

 

我不需要把所有领域都吞进去——我不需要什么都懂。但一年结束时,我应该在更多领域懂得更多。如果我不在这种上升轨道上,我会无聊。我无聊就会没动力。没动力我就什么也不干。这也是 AI 讨论的一部分:我们得想清楚,我们真正享受这套方程式里的哪一部分。

 

我个人不享受当项目经理。我会做——而且不止偶尔——因为我想要“组织一群人”能产出的结果。

 

但当我看 AI 这件事时,我不想当一群 AI agent 的项目经理。那不是我想要的状态。

 

我喜欢写代码。而至少在此时此刻,这是一个仍然有竞争力的选择。

 

当然,这可能三个月后就变了;下周就变了;随时都可能变。但 AI 公司那些领袖已经预言“再过五分钟就结束了”预言了很久了——现在也没结束。

 

你看 AI 公司自己,它们也还在招聘大量程序员。

 

我们并没有到 AGI,没有到那种“人类写代码的时代死了”的程度。

 

这并不否认你说的:有些程序员已经觉得自己大多数代码都让 AI 写了。但至少在市场上——按我看到的情况——还没有出现那种“压倒性差距”,就像:一个公司用马车送啤酒,另一个公司用卡车送啤酒。那种经济差距会非常快把前者淘汰。我还没在 AI 身上看到这种情况。也许数据有滞后;也许已经发生了——我仍然怀疑。

 

即便我在长期上是极度“AI 乐观派”,但就当下,我没看到。

 

有时神得离谱,有时烂得没法维护

 

DHH:而且原因之一是:我每天都在“盯”着它。我一直在问 AI:你能给我写这段代码吗?

它会写。然后我会想:“不,我不喜欢这个。”“我甚至不想维护它。”“它做得还不如大多数初级程序员会被要求做到的水平。”

 

但偶尔,它也会给出另一种答案:我问它一个东西,它拼出来的结果让我震惊:“它怎么知道的?它怎么能把这些全部串起来?”那真的很惊人。

 

所以我感觉它像一个闪烁的灯泡:你在完全黑暗里,它突然一闪——你觉得“我什么都看见了”。两秒后,啪,又全黑。如果你能让这个灯泡稳定下来、一直亮着——那对人类当然是巨大的福音。

 

顺便说一句,我很喜欢美国的一点就是:美国把这个“闪烁灯泡”当成一种信仰——相信我们能把它变可靠,能到 AGI。现在大家就是一场巨大的押注:押注这一定会发生。即便我这么 AI 乐观,我仍然会对这种规模的“集体确信”感到惊叹:一个经济体一起说: “不管花多少代价,100 万亿、1000 万亿,我不在乎,我们一定能到那里。”我会想:这也许就是为什么它会成为“第一名”。

 

主持人:确实是个令人兴奋的时代。就像你说的——能活在此时此刻本身就是一种奇迹。我们也差不多到一小时的时间上限了。今天能和你重新连上线真的很开心,感谢你抽时间来。你现在也在忙 Fizzy。要不你简单跟大家说说:Fizzy 是什么?在哪能了解更多?然后我们就收尾。

 

DHH:当然。Fizzy 在fizzy.do。它是对 Kanban(看板)的一个全新诠释。这里还有个小故事:Jason 特别擅长解释“为什么值得回头重新解决一个问题”。

 

Kanban 这个概念来自 50 年代,是丰田为了管理生产线提出来的。后来我们把它做成了软件。第一代软件化的版本大概是 2000 年初。再后来 Trello 出现,把这个领域彻底带火、带爆。但我们还是回到这个领域,说:“你知道吗?我觉得我们还能做一个更好、更舒服的版本。”

 

很多人很难理解软件这件事:明明一个问题领域已经有很多玩家了,为什么你还要进去?原因可能只是:你想做得更好、更有趣、更轻量、更丰富多彩、更令人愉悦、功能更少——这些带着“爱”的细节,我们都烘焙进了 Fizzy。而且我们把它定价得很便宜:1000 张卡片免费,之后是 每月 20 美元。同时我们也把整个代码库开源了:如果你想自托管(self-host),你可以免费用。服务器我们不替你付,你自己折腾就行。你也可以贡献代码,也可以从中学习。

 

做 Fizzy 是一件很快乐的事,而且它也像一个实验室。我们现在正在做 Basecamp 5。我们在 Fizzy 上尝试了很多新技术——不管是编程层面还是产品层面——我们会把最好的想法带回 Basecamp 5。如果你关心我对这些话题(或任何话题)的观点,你可以去 dhh.dk,我的东西都在那。

 

主持人:太棒了。很高兴你来做客,也迫不及待想看未来会发生什么。感谢你的时间,我们下期再见。

 

参考链接:

https://www.youtube.com/watch?v=uWqno4HM4xA

https://www.reddit.com/r/ClaudeCode/comments/1qhiicv/the_creator_of_nodejs_says_the_era_of_writing/

CLI Proxy API 的核动力驴一天 3 个小版本,每天打开电脑都是打开 render 更新部署。
于是搓了这样一个用 Deno Deploy 的 cron 触发 Render deploy hook 的小工具
每天凌晨 4 点自动更新 render 部署的 cpa(你也可以用来更新其他 render 项目)


去这里复制一下 Deploy Hook 就好了,改一下代码里面占位的 RENDER_HOOK_URL
非常简单的小工具

/**
 * Project: render-auto-deploy (sanitized, hardcoded URL version)
 * Purpose: 用 Deno Deploy Cron 定时触发 Render Deploy Hook(自动 redeploy)
 *
 * ✅ 你需要改的地方(部署时只改这两处):
 * 1) 把 RENDER_HOOK_URL 改成你自己的 Render Deploy Hook URL(包含 service id + key)
 * 2) 如果想换时间,把 CRON_SCHEDULE 改成你要的 cron(注意:Deno.cron 用 UTC)
 *
 * ✅ 验证方法:
 * - 打开 "/":看到 running + 当前 UTC 时间 + schedule
 * - 打开 "/trigger":立刻触发一次 Render 部署(返回 deploy.id)
 * - 去 Render Events:会出现 “Triggered via Deploy Hook”
 *
 * 🔥 时区提醒:
 * - Deno.cron 的 cron 表达式按 UTC 解释
 * - 例:UTC 20:00 = 北京时间次日 04:00
 */

// ======================【部署时改这里 1】======================
// Render Deploy Hook(占位符,换成你自己的)
// 形如:https://api.render.com/deploy/<YOUR_SERVICE_ID>?key=<YOUR_DEPLOY_KEY>
const RENDER_HOOK_URL =
  "https://api.render.com/deploy/<YOUR_SERVICE_ID>?key=<YOUR_DEPLOY_KEY>";

// ======================【部署时改这里 2】======================
// Cron 表达式按 UTC 解释:
// - "0 20 * * *" => 每天 UTC 20:00(北京时间次日 04:00)
// - 测试用: "*/1 * * * *" => 每分钟触发一次(用来验证 cron 是否生效)
const CRON_SCHEDULE = "0 20 * * *";

function nowIso() {
  return new Date().toISOString();
}

async function triggerRenderDeploy(source: "cron" | "http") {
  const runId = crypto.randomUUID();
  console.log(`[${nowIso()}] [${source}] runId=${runId} ⏰ trigger start`);

  // 超时:避免网络卡住导致任务悬挂
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 30_000);

  try {
    const resp = await fetch(RENDER_HOOK_URL, {
      method: "POST", // Render deploy hook 通常 GET/POST 都可,这里用 POST
      signal: controller.signal,
      headers: { "user-agent": "render-auto-deploy/deno" },
    });

    const text = await resp.text().catch(() => "");

    if (!resp.ok) {
      // 抛错 => 让 cron 的 backoffSchedule 生效,自动重试
      throw new Error(
        `Render hook failed: ${resp.status} ${resp.statusText} body=${text.slice(0, 500)}`,
      );
    }

    console.log(
      `[${nowIso()}] [${source}] runId=${runId} ✅ trigger ok body=${text.slice(0, 500)}`,
    );

    return { ok: true, runId, status: resp.status, body: text };
  } finally {
    clearTimeout(timeout);
  }
}

// ====== 定时任务(必须在模块顶层定义,Deno Deploy 才会识别为 Cron)======
Deno.cron(
  "Daily Render Auto Deploy",
  CRON_SCHEDULE,
  // 失败重试节奏(毫秒):1s, 5s, 10s
  { backoffSchedule: [1000, 5000, 10000] },
  async () => {
    try {
      await triggerRenderDeploy("cron");
    } catch (e) {
      console.error(`[${nowIso()}] [cron] ❌`, e);
      throw e; // 继续抛出以触发重试
    }
  },
);

// ====== Web:健康检查 + 手动触发(方便部署后立即验证)======
Deno.serve(async (req) => {
  const url = new URL(req.url);

  // 健康检查:确认服务活着
  if (url.pathname === "/") {
    return new Response(
      `Auto-Deploy is running.\nUTC now: ${nowIso()}\nSchedule(UTC): ${CRON_SCHEDULE}\n`,
      { headers: { "content-type": "text/plain; charset=utf-8" } },
    );
  }

  // 手动触发:GET /trigger
  // 部署完立刻访问一次,看返回里有没有 deploy.id
  if (url.pathname === "/trigger") {
    try {
      const result = await triggerRenderDeploy("http");
      return new Response(JSON.stringify(result, null, 2), {
        headers: { "content-type": "application/json; charset=utf-8" },
      });
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      return new Response(JSON.stringify({ ok: false, error: msg }, null, 2), {
        status: 500,
        headers: { "content-type": "application/json; charset=utf-8" },
      });
    }
  }

  return new Response("Not found", { status: 404 });
});


📌 转载信息
原作者:
sssun
转载时间:
2026/1/20 10:04:34

以 “默认安全” 架构闻名的现代 JavaScript/TypeScript 运行时 Deno 近期曝出两个重大安全漏洞。这两个漏洞分别影响运行时的加密兼容性层Windows 平台的命令执行机制,可能导致敏感服务器密钥泄露,并允许攻击者执行任意代码。

CVE-2026-22863:高危密钥泄露漏洞(CVSS 9.2)

两个漏洞中最严重的是 CVE-2026-22863,CVSS 评分 9.2。该漏洞存在于 Deno 的 node:crypto 兼容层 —— 一个用于让 Deno 运行 Node.js 代码的模块。
根据安全公告,node:crypto 的实现没有正确终结(finalize)加密器(cipher)。在标准的加密流程中,final() 方法应结束加密过程并清理内部状态。然而在受影响的 Deno 版本中,该方法没有真正关闭 cipher,导致加密器处于 “可无限加密” 的异常状态。
这一缺陷的影响极为严重。报告指出,这种状态管理错误 “可能导致攻击者进行暴力破解,或发起更高级的攻击以窃取服务器密钥”。分析中提供的 PoC 显示,调用 cipher.final() 后返回的是一个仍处于激活状态的 Cipheriv 对象,其内部缓冲状态仍然可被访问,而非预期的已关闭 CipherBase

CVE-2026-22864:Windows 命令执行绕过(RCE)

第二个漏洞 CVE-2026-22864 影响 Deno 在 Windows 上启动子进程的能力。研究人员发现,这是一个 “对已修补漏洞的绕过”,涉及在执行批处理文件时使用 Deno.Command API
Deno 原本包含防止 shell 注入的安全机制。当用户尝试直接运行 .bat 文件时,Deno 会抛出 PermissionDenied 错误,并提示开发者 “使用 shell 执行 bat 或 cmd 文件”,以确保参数被安全处理。

然而研究人员找到了绕过方法:

通过修改文件扩展名的大小写(例如使用 .BAT)或操控传入命令的参数,攻击者可以绕过 Deno 的安全限制。报告中包含的 PoC 截图显示,一个脚本通过在批处理执行上下文里注入参数 ["&calc.exe"],成功执行了 Windows 计算器(calc.exe),从而在目标机器上实现任意代码执行

建议与修复

官方强烈建议用户立即升级到 Deno v2.6.0 或更高版本以修复这两个漏洞。

以 “默认安全” 架构闻名的现代 JavaScript/TypeScript 运行时 Deno 近期曝出两个重大安全漏洞。这两个漏洞分别影响运行时的加密兼容性层Windows 平台的命令执行机制,可能导致敏感服务器密钥泄露,并允许攻击者执行任意代码。

CVE-2026-22863:高危密钥泄露漏洞(CVSS 9.2)

两个漏洞中最严重的是 CVE-2026-22863,CVSS 评分 9.2。该漏洞存在于 Deno 的 node:crypto 兼容层 —— 一个用于让 Deno 运行 Node.js 代码的模块。
根据安全公告,node:crypto 的实现没有正确终结(finalize)加密器(cipher)。在标准的加密流程中,final() 方法应结束加密过程并清理内部状态。然而在受影响的 Deno 版本中,该方法没有真正关闭 cipher,导致加密器处于 “可无限加密” 的异常状态。
这一缺陷的影响极为严重。报告指出,这种状态管理错误 “可能导致攻击者进行暴力破解,或发起更高级的攻击以窃取服务器密钥”。分析中提供的 PoC 显示,调用 cipher.final() 后返回的是一个仍处于激活状态的 Cipheriv 对象,其内部缓冲状态仍然可被访问,而非预期的已关闭 CipherBase

CVE-2026-22864:Windows 命令执行绕过(RCE)

第二个漏洞 CVE-2026-22864 影响 Deno 在 Windows 上启动子进程的能力。研究人员发现,这是一个 “对已修补漏洞的绕过”,涉及在执行批处理文件时使用 Deno.Command API
Deno 原本包含防止 shell 注入的安全机制。当用户尝试直接运行 .bat 文件时,Deno 会抛出 PermissionDenied 错误,并提示开发者 “使用 shell 执行 bat 或 cmd 文件”,以确保参数被安全处理。

然而研究人员找到了绕过方法:

通过修改文件扩展名的大小写(例如使用 .BAT)或操控传入命令的参数,攻击者可以绕过 Deno 的安全限制。报告中包含的 PoC 截图显示,一个脚本通过在批处理执行上下文里注入参数 ["&calc.exe"],成功执行了 Windows 计算器(calc.exe),从而在目标机器上实现任意代码执行

建议与修复

官方强烈建议用户立即升级到 Deno v2.6.0 或更高版本以修复这两个漏洞。

主要用来解决纯前端发起 api 请求时的 cors 跨域问题,直接透传给目标 api

支持在线调试,为了防止大串 base64 干扰,Body 里会显示高亮占位符,真实请求时自动替换为图片 base64


📌 转载信息
原作者:
fish2018
转载时间:
2026/1/11 08:37:04

在佬的基础上改了个 deno 版的
加了自动获取模型和去除末尾模型标记
单 ip 每天限制 150 次好像
每分钟 5 次


📌 转载信息
原作者:
zzzx9
转载时间:
2026/1/8 17:39:20