普通视图
Vue3 v-if与v-show:销毁还是隐藏,如何抉择?
到底选 Nuxt 还是 Next.js?SEO 真的有那么大差距吗 🫠🫠🫠
我正在开发 DocFlow,它是一个完整的 AI 全栈协同文档平台。该项目融合了多个技术栈,包括基于
Tiptap的富文本编辑器、NestJs后端服务、AI集成功能和实时协作。在开发过程中,我积累了丰富的实战经验,涵盖了Tiptap的深度定制、性能优化和协作功能的实现等核心难点。
如果你对 AI 全栈开发、Tiptap 富文本编辑器定制或 DocFlow 项目的完整技术方案感兴趣,欢迎加我微信 yunmz777 进行私聊咨询,获取详细的技术分享和最佳实践。
关于 Nuxt 和 Next.js 的 SEO 对比,技术社区充斥着太多误解。最常见的说法是 Nuxt 的 Payload JSON 化会严重影响 SEO,未压缩的数据格式会拖慢页面加载。还有人担心环境变量保护机制存在安全隐患。
实际情况远非如此。框架之间的 SEO 差异被严重夸大了。Nuxt 采用未压缩 JSON 是为了保证数据类型完整性和加速水合过程,这是深思熟虑的设计权衡。所谓的安全问题,本质上是第三方生态设计的挑战,而非框架缺陷。
真正影响搜索引擎排名的是内容质量、用户体验、页面性能和技术实践,而非框架选择。一个优化良好的 Nuxt 网站完全可能比未优化的 Next.js 网站排名更好。
一、服务端渲染机制对比
Next.js:压缩优先
Next.js 新版本使用压缩字符串格式序列化数据,旧版 Page Router 则用 JSON。数据以 <script> 标签形式嵌入 HTML,客户端接收后解压完成水合。
这种方案优势明显:HTML 文档体积小,传输快,初始加载速度优秀。App Router 还支持流式渲染和 Server Components,服务端可以逐步推送内容,不需要等待所有数据准备就绪。
权衡也很清楚:增加了客户端计算开销,复杂数据类型需要额外处理。
Nuxt:类型完整性优先
Nuxt 采用未压缩 JSON 格式,通过 window.__NUXT__ 对象嵌入数据。设计思路基于一个重要前提:现代浏览器的 JSON.parse() 性能极高,V8 引擎对 JSON 解析做了大量优化。相比自定义压缩算法,直接用 JSON 格式解析速度更快。
核心优势是水合速度极快,支持完整的 JavaScript 复杂类型。Nuxt 使用 devalue 序列化库,能够完整保留 Map、Set、Date、RegExp、BigInt 等类型,还能处理循环引用。这意味着从后端传递 Map 数据,前端反序列化后依然是 Map,而不会被转换为普通 Object。
当然,包含大量数据时 HTML 体积会增大。不过对于大数据场景,Nuxt 已经支持 Lazy Hydration 来处理。
设计哲学差异
Next.js 优先考虑传输速度,适合前后端分离场景。Nuxt 优先保证类型完整性,更适合全栈 JavaScript 应用。由于前后端都用 JavaScript,保持数据类型一致性可以减少大量类型转换代码。
实际测试表明,大多数场景下这种方案不会拖慢首屏加载。一次传输加快速水合的策略,整体性能往往更优。
二、对 SEO 的实际影响
Payload JSON 化的真实影响
从 SEO 角度看,Nuxt 的方案有独特优势。爬虫可以直接从 HTML 获取完整内容,无需执行 JavaScript。即使 JS 加载或执行失败,页面核心内容依然完整存在于 HTML 中。这对 SEO 至关重要,因为搜索引擎爬虫虽然能执行 JavaScript,但更倾向于直接读取 HTML。
HTML 体积增大的影响被严重高估了。实际测试数据显示,即使 HTML 增大 50-100KB,对 TTFB 的影响也在 50-100ms 以内,用户几乎感知不到。而水合速度提升可能节省 100-200ms 的交互响应时间,整体用户体验反而更好。
Next.js 的性能优势
Next.js 采用压缩格式后,HTML 体积更小,服务器响应更快。实际数据显示平均 LCP 为 1.2 秒,INP 为 65ms,这些指标确实优秀。
Next.js 13+ 的 Server Components 进一步优化了数据传输。服务端组件的数据不需要序列化传输到客户端,直接在服务端渲染成 HTML,大大减少了传输量。对于主要展示内容的页面,这种方式可以实现接近静态页面的性能。
ISR 功能也很实用。页面可以在构建时生成静态 HTML,然后在后台按需更新,既保证了首屏速度,又能及时更新内容。
核心结论
框架对 SEO 的影响被严重夸大了。Google 的 Evergreen Googlebot 在 2019 年就已经能够完整执行现代 JavaScript。无论 Nuxt 还是 Next.js,只要正确实现了 SSR,搜索引擎都能获取完整内容。
框架选择对排名的影响可能不到 1%。真正影响 SEO 的是内容质量、页面语义化、结构化数据、内部链接结构、技术实践(sitemap、robots.txt)和用户体验指标。
三、SEO 功能特性对比
元数据管理
Next.js 13+ 的 Metadata API 提供了类型安全的元数据配置,与 Server Components 深度集成,可以在服务端异步获取数据生成动态元数据:
// Next.js
export async function generateMetadata({ params }) {
const post = await fetchPost(params.id);
return {
title: post.title,
description: post.excerpt,
openGraph: { images: [post.coverImage] },
};
}
Nuxt 的 useHead 提供响应式元数据管理,配合 @nuxtjs/seo 模块开箱即用。内置 Schema.org JSON-LD 生成器,可以更方便地实现结构化数据:
// Nuxt
const post = await useFetch(`/api/posts/${id}`);
useHead({
title: post.value.title,
meta: [{ name: "description", content: post.value.excerpt }],
});
useSchemaOrg([
defineArticle({
headline: post.title,
datePublished: post.publishedAt,
author: { name: post.author.name },
}),
]);
Next.js 同样可以实现结构化数据,但需要手动插入 <script type="application/ld+json"> 标签。虽然需要额外工作,但提供了更大的灵活性。
语义化 HTML 与无障碍性
Nuxt 提供自动 aria-label 注入功能,@nuxtjs/a11y 模块可以在开发阶段自动检测无障碍性问题。Next.js 需要开发者手动确保,可以使用 eslint-plugin-jsx-a11y 检测问题。
语义化 HTML 对 SEO 的重要性常被低估。搜索引擎不仅读取内容,还会分析页面结构。正确使用 <article>、<section>、<nav> 等标签,可以帮助搜索引擎更好地理解内容层次。
静态生成与预渲染
Next.js 的 ISR 允许为每个页面设置不同的重新验证时间。首页可能每 60 秒重新生成,文章页面可能每天重新生成。这种精细化控制使得网站能在性能和内容新鲜度之间找到平衡:
// Next.js ISR
export const revalidate = 3600; // 每小时更新
Nuxt 3 支持混合渲染模式,可以在同一应用中同时使用 SSR、SSG 和 CSR,为不同页面选择最适合的渲染策略:
// Nuxt 混合渲染
export default defineNuxtConfig({
routeRules: {
"/": { prerender: true },
"/posts/**": { swr: 3600 },
"/admin/**": { ssr: false },
},
});
Next.js 14 的 Partial Prerendering 更进一步,允许在同一页面混合静态和动态内容。静态部分在构建时生成,动态部分在请求时渲染,结合了 SSG 的速度和 SSR 的灵活性。
四、性能指标与爬虫友好性
Core Web Vitals 表现
从各项指标看,Next.js 平均 LCP 为 1.2 秒,表现优秀。Nuxt 的 LCP 可能受 HTML 体积影响,但在 FCP 和 INP 方面得益于快速水合机制,同样表现出色。
需要强调的是,这些差异在实际 SEO 排名中影响极其有限。Google 在 2021 年将 Core Web Vitals 纳入排名因素,但权重相对较低。内容相关性、反向链接质量、域名权威度等传统因素权重仍然更高。
更重要的是,Core Web Vitals 分数取决于真实用户体验数据,而非实验室测试。网络环境、设备性能、缓存状态等因素对性能的影响远大于框架本身。一个优化良好的 Nuxt 网站完全可能比未优化的 Next.js 网站获得更好的分数。
两个框架都提供了丰富的优化工具。Next.js 的 next/image 提供自动图片优化、懒加载、响应式图片。Nuxt 的 @nuxt/image 提供类似功能,并支持多种图片托管服务。图片优化对 LCP 的影响往往比框架选择更大。
爬虫友好性
两个框架在爬虫友好性方面几乎没有差别。都提供完整的服务端渲染内容,无需 JavaScript 即可获取页面信息,能正确返回 HTTP 状态码,支持合理的 URL 结构。
Nuxt 的额外优势在于内置 Schema.org JSON-LD 生成器,有助于搜索引擎生成富文本摘要。结构化数据对现代 SEO 至关重要,通过嵌入 JSON-LD 格式的数据,可以告诉搜索引擎页面内容的具体含义。这些信息会显示在搜索结果中,提高点击率。
两个框架在处理动态路由、国际化、重定向等 SEO 关键功能上都提供了完善支持。
五、安全性问题澄清
环境变量保护机制
关于 Nuxt 会将 NUXT_PUBLIC 环境变量暴露到 HTML 的问题,需要明确这并非框架缺陷。Nuxt 的机制是只有显式设置为 NUXT_PUBLIC_ 前缀的变量才会暴露到前端,非 public 变量不会出现在 HTML 中。
正常情况下,开发者不应该将重要信息设置为 public。任何重要信息都不应该放到前端,这是前端开发的基本原则,与框架无关。
Nuxt 3 的环境变量系统被彻底重新设计。运行时配置分为公开和私有两部分:
// Nuxt 配置
export default defineNuxtConfig({
runtimeConfig: {
// 私有配置,仅服务端可用
apiSecret: process.env.API_SECRET,
// 公开配置,会暴露到客户端
public: {
apiBase: process.env.API_BASE_URL,
},
},
});
Next.js 使用类似机制,以 NEXT_PUBLIC_ 前缀的变量会暴露到客户端:
// 服务端组件中
const apiSecret = process.env.API_SECRET; // 可用
// 客户端组件中
const apiBase = process.env.NEXT_PUBLIC_API_BASE; // 可用
const apiSecret = process.env.API_SECRET; // undefined
实际开发中的安全挑战
真正的问题在于某些第三方库要求在前端初始化时传入密钥。一些 BaaS 服务(如 Supabase、Firebase)的前端 SDK 设计就需要在前端初始化,开发者无法控制这些第三方生态的设计。
以 Supabase 为例,它提供 anon key(匿名密钥)和 service role key(服务角色密钥)。anon key 设计为可以在前端使用,通过行级安全策略在数据库层面控制权限。service role key 则绕过所有安全策略,只能在服务端使用。
理想解决方案是将依赖密钥的库放到服务端,通过 API 调用使用。如果某个库需要在前端运行明文密钥,这个库本身就存在重大安全风险。但现实往往更复杂,生态适配问题难以完全避免。
值得注意的是,Next.js 同样存在类似的安全考量。两个框架在环境变量保护方面的机制基本一致,问题根源在于第三方生态设计,而非框架缺陷。
对 SEO 的影响
环境变量问题本质上是安全问题,而非 SEO 问题。只要正确配置,不会影响搜索引擎爬取。
真正影响 SEO 的安全问题是:网站被攻击后注入垃圾链接、恶意重定向、隐藏内容等。这些行为会直接导致网站被搜索引擎惩罚,甚至从索引中移除。防止这类问题需要全方位的安全措施,远超框架本身的范畴。
六、实际应用场景
内容密集型网站
对于博客、新闻网站、文档站点,Nuxt 往往表现更好。内容块水合速度快,开箱即用的 SEO 功能完善,开发体验好。
Nuxt 的 @nuxt/content 模块提供了基于 Markdown 的内容管理系统,支持全文搜索、代码高亮、自动目录生成。配合 @nuxtjs/seo 模块,可以快速构建 SEO 友好的内容网站:
// Nuxt Content 使用
const { data: post } = await useAsyncData("post", () =>
queryContent("/posts").where({ slug: route.params.slug }).findOne()
);
技术博客、文档网站特别适合这种方案。VuePress、VitePress 等静态站点生成器也是基于类似思路构建的。
动态应用
对于电商平台、SaaS 应用等需要高级渲染技术和复杂交互的场景,Next.js 可能更合适。ISR 和部分预渲染能够更好地应对动态内容需求。
电商网站的 SEO 挑战在于商品数量庞大、内容动态变化、需要个性化推荐。Next.js 的 ISR 可以为每个商品页面设置合适的重新验证时间,既保证 SEO 友好,又能及时更新库存、价格:
// Next.js 电商页面优化
export default async function ProductPage({ params }) {
const product = await fetchProduct(params.id);
return (
<>
<ProductInfo product={product} />
<Suspense fallback={<Skeleton />}>
<AddToCartButton productId={params.id} />
</Suspense>
</>
);
}
export const revalidate = 1800; // 30分钟重新验证
混合场景
对于兼具内容和应用特性的混合场景,两个框架都能很好胜任。许多现代网站都是混合型的:既有内容页面(博客、文档),又有应用功能(用户中心、后台管理)。
关键是为不同类型页面选择合适的渲染策略。Nuxt 3 的 routeRules 提供路由级别的渲染控制:
// Nuxt 混合渲染场景
export default defineNuxtConfig({
routeRules: {
"/": { prerender: true }, // 首页预渲染
"/blog/**": { swr: 3600 }, // 博客缓存 1 小时
"/dashboard/**": { ssr: false }, // 用户中心客户端渲染
"/api/**": { cors: true }, // API 路由
},
});
Next.js 通过不同文件约定实现类似功能。可以在 app 目录中使用 Server Components 和 Client Components 组合,在 pages 目录中使用传统 SSR/SSG 方式。
七、开发者的真实痛点
超越 SEO 的实际考量
通过分析实际案例发现,开发者选择框架的真正原因往往不是 SEO。许多从 Nuxt 迁移到 Next.js 的团队,主要原因包括第三方生态兼容性问题(如 Supabase 等 BaaS 服务的前端依赖),以及开发体验(启动速度慢、构建时间长)。
客观来说,Nuxt 在开发服务器启动速度和构建时间方面确实存在优化空间。不过随着 Rolldown、Oxc 等下一代工具链的完善,这些问题有望改善。这说明 SEO 和安全问题可能被过度强调了,真正影响开发者选择的是开发体验和生态适配。
开发服务器启动速度直接影响开发效率。如果每次重启都需要等待 30-60 秒,一天下来可能浪费几十分钟。构建时间同样重要,尤其在 CI/CD 环境中。构建时间过长会延迟部署,影响快速迭代能力。
生态兼容性是另一个重要因素。某些库只提供 React 版本,某些只提供 Vue 版本。虽然可以通过适配器跨框架使用,但会增加维护成本。
技术方案的权衡
没有完美的技术方案,只有最适合的选择。Nuxt 优先保证数据类型完整性和快速水合,Next.js 追求更小体积和更快 TTFB。
不同开发者有不同需求:内容型网站与应用型产品侧重点不同,小团队与大型商业项目考量各异。技术讨论中有句话很好地总结了这点:几乎所有框架都在解决同样的问题,差别只在于细微的实现方式。
对小团队来说,开发效率可能比性能优化更重要。快速实现功能、快速迭代,比追求极致性能指标更有价值。对大型团队来说,长期维护性和可扩展性更重要。
技术债务是另一个需要考虑的因素。随着项目发展,早期为了快速开发而做的权衡可能成为瓶颈。选择一个社区活跃、持续演进的框架很重要。Next.js 和 Nuxt 都有强大的商业支持(Vercel 和 NuxtLabs),这保证了框架的长期发展。
八、综合评估与选择建议
SEO 能力评分
从 SEO 能力看,Next.js 可以获得 4.5 分(满分 5 分)。优势在于优秀的 Core Web Vitals 表现、更小的 HTML 体积、成熟的 SEO 生态,以及 ISR 和部分预渲染等先进特性。不足是需要手动配置结构化数据,语义化 HTML 需要开发者特别注意。
Nuxt 可以获得 4 分。优势包括内置 Schema.org JSON-LD 生成器、自动语义化 HTML 支持、在内容型网站的优秀表现,以及快速水合带来的良好体验。Payload JSON 导致 HTML 体积增大在实际应用中影响微小,已有 Lazy Hydration 等解决方案。开发服务器启动和构建速度慢是开发体验问题,与 SEO 无关。
需要说明的是,两者 SEO 能力实际上都接近满分,0.5 分的差异主要体现在开箱即用的便利性上,而非实际 SEO 效果。在真实搜索引擎排名中,这 0.5 分的差异几乎不会产生可察觉的影响。
选择 Next.js 的场景
如果项目对 Core Web Vitals 有极高要求,或者需要复杂渲染策略的动态应用,Next.js 是更好的选择。以下场景推荐使用:
- 电商平台,需要
ISR平衡性能和内容新鲜度 - SaaS 应用,对交互性能要求极高
- 国际化大型网站,需要精细性能优化
- 团队已有 React 技术栈,迁移成本低
- 需要使用大量 React 生态的第三方库
- 对 Vercel 平台部署优化感兴趣
- 需要
Server Components的先进特性 - 项目规模大,需要严格的 TypeScript 类型检查
选择 Nuxt 的场景
如果项目是内容密集型网站(博客、新闻、文档),或者需要快速开发并利用框架的 SEO 便利功能,Nuxt 是理想选择。以下场景推荐使用:
- 技术博客、文档站点,内容是核心
- 新闻、媒体网站,需要快速发布内容
- 企业官网,强调 SEO 和内容展示
- 团队已有 Vue 技术栈,迁移成本低
- 需要使用 Vue 生态的 UI 库(如 Element Plus、Vuetify)
- 快速原型开发,需要开箱即用的功能
- 需要
@nuxt/content的 Markdown 内容管理 - 项目需要传递复杂的 JavaScript 对象(Map、Set、Date 等)
决策思路
对于需要优秀 SEO 表现、服务端渲染和良好开发体验的项目,两个框架都完全能够胜任。选择关键在于团队技术栈、第三方生态适配、开发体验等实际因素,而不应该仅仅基于 SEO 考虑。
在中小型项目、团队对两个框架都不熟悉、项目没有特殊渲染需求的情况下,两个框架都是合理选择。可以考虑以下因素决策:
- 团队成员的个人偏好(React vs Vue)
- 公司的技术战略和长期规划
- 现有项目的技术栈,保持一致性
- 招聘市场,React 开发者相对更多
- 社区资源,React 生态整体更成熟
- 学习曲线,Vue 的 API 相对更简单
九、核心结论
框架差异的真实影响
几乎所有现代框架都能很好地支持 SEO,差异只在于细微的实现方式。框架选择对 SEO 排名的影响远小于内容质量和技术实现。Payload JSON 化影响 SEO 的说法被夸大了,这是经过深思熟虑的设计权衡。对大多数应用来说,一次传输加快速水合的策略更优。
从搜索引擎角度看,只要页面能正确渲染、内容完整可见、HTML 结构合理、meta 标签正确,框架是什么并不重要。Google 的爬虫既可以处理传统静态 HTML,也可以执行复杂的 JavaScript 应用,还可以理解 SPA 的路由。
真正影响 SEO 的是:内容质量和原创性、页面加载速度(但 100-200ms 差异可以忽略)、移动端友好性、内部链接结构、外部反向链接、域名权威度、用户行为指标(点击率、停留时间、跳出率)、技术 SEO 实践(sitemap、robots.txt、结构化数据)。
框架选择在这些因素中的权重微乎其微。用 Nuxt 还是 Next.js,对实际排名的影响可能不到 1%。
性能指标的误区
Next.js 在 HTML 体积、TTFB 和 Core Web Vitals 平均值方面具有优势。Nuxt 则在数据类型支持、水合速度和网络传输效率方面表现出色。但这些差异在实际 SEO 排名中影响微乎其微。
常见的性能误区包括:过度关注实验室测试数据,忽略真实用户体验;追求极致分数,忽略边际收益递减;认为性能优化就是 SEO 优化;忽略其他更重要的 SEO 因素;在框架选择上纠结,而不是优化现有代码。
实际上,同一框架下,优化和未优化的网站性能差异可能是 10 倍,而不同框架之间的性能差异可能只有 10-20%。把精力放在代码优化、资源优化、CDN 配置等方面,往往比纠结框架选择更有价值。
决策因素梳理
技术因素方面,应该考虑团队技术栈(React vs Vue)、第三方生态适配、开发体验(启动速度、构建速度)。业务因素方面,需要评估项目类型(内容型 vs 应用型)、团队规模和能力、时间和预算。不应该主要基于 SEO 来选择框架,因为两者在 SEO 方面能力基本相当。
决策优先级建议:
第一优先级:团队技术栈和能力。团队熟悉什么就用什么,学习成本和招聘成本很高。
第二优先级:项目类型和需求。内容型倾向 Nuxt,应用型倾向 Next.js,混合型都可以。
第三优先级:生态和工具链。需要的第三方库是否支持,部署平台的支持情况,开发工具的成熟度。
第四优先级:性能和 SEO。只在前三者相同时考虑,实际影响很小。
十、实践建议
SEO 优化核心原则
内容质量永远是第一位的。框架只是工具,内容才是核心,技术优化是锦上添花而非雪中送炭。正确实现 SSR 比框架选择更重要。
SEO 最佳实践清单:确保所有重要内容在 HTML 中可见、为每个页面设置唯一的 title 和 description、使用语义化 HTML 标签、正确使用标题层级、为图片添加 alt 属性、实现结构化数据、生成 sitemap.xml 并提交、配置合理的 robots.txt、使用 HTTPS、优化页面加载速度、确保移动端友好、构建合理的内部链接结构、定期发布高质量原创内容、获取高质量的外部链接、监控和分析 SEO 数据。
Nuxt 优化建议
充分利用框架优势,包括数据类型完整性的便利。对于大数据量场景,使用 Lazy Hydration 功能。不要因为 payload 问题过度担心,实际影响很小。
性能优化技巧:使用 nuxt generate 生成静态页面、配置 routeRules 为不同页面选择合适渲染策略、使用 @nuxt/image 优化图片加载、使用 lazy 属性延迟加载不关键组件、优化 payload 大小避免传递不必要数据、使用 useState 管理 SSR 和 CSR 之间共享的状态、配置合理的缓存策略、监控 payload 大小必要时拆分数据。
// Nuxt 性能优化配置
export default defineNuxtConfig({
experimental: {
payloadExtraction: true,
inlineSSRStyles: false,
},
routeRules: {
"/": { prerender: true },
"/blog/**": { swr: 3600 },
},
image: {
domains: ["cdn.example.com"],
},
});
Next.js 优化建议
充分利用性能优势,包括 ISR 和部分预渲染,优化 Core Web Vitals 指标。在处理复杂数据类型时,Map、Set 等需要额外处理,要确保序列化和反序列化的正确性。
性能优化技巧:使用 ISR 为动态内容设置合理重新验证时间、尽可能使用 Server Components 减少客户端 JavaScript、使用 next/image 自动优化图片、使用 next/font 优化字体加载、配置 experimental.ppr 启用部分预渲染、使用 Suspense 和 loading.js 改善感知性能、代码分割按需加载、优化第三方脚本加载、使用 @next/bundle-analyzer 分析包大小、配置合理的缓存策略。
// Next.js 性能优化配置
const nextConfig = {
experimental: {
ppr: true,
optimizeCss: true,
optimizePackageImports: ["lodash", "date-fns"],
},
images: {
domains: ["cdn.example.com"],
formats: ["image/avif", "image/webp"],
},
};
框架无关的通用优化
无论选择哪个框架,以下优化都是必要的:使用 CDN 加速静态资源、启用 Gzip 或 Brotli 压缩、配置合理的缓存策略、优化首屏渲染延迟加载非关键资源、减少 HTTP 请求数量、使用 HTTP/2 或 HTTP/3、优化数据库查询、使用 Redis 等缓存层、监控真实用户性能数据、定期进行性能审计。
决策流程
如果主要关心 SEO,两个框架都完全够用,选择团队熟悉的即可,把精力放在内容质量和技术实现上。如果项目需要复杂数据结构,Nuxt 的 devalue 机制会让开发更便捷。如果追求极致性能指标,Next.js 的压缩方案可能略好一点,但差异在实际 SEO 排名中几乎可以忽略。如果被第三方生态绑定,这可能是最重要的决策因素。
决策流程建议:评估团队现有技术栈(如果已有 React/Vue 项目保持一致)、分析项目需求(内容型倾向 Nuxt、应用型倾向 Next.js)、检查第三方依赖(列出必需的库、确认是否有对应生态版本)、考虑部署环境(Vercel 对 Next.js 有特殊优化、Netlify 和 Cloudflare Pages 两者都支持)、评估长期维护成本(框架更新频率和稳定性、社区支持和文档质量)。
结语
通过深入分析技术原理和实际应用案例,可以得出一个明确的结论:框架之间的差异是细微的,对 SEO 的影响更是微乎其微。
选择你熟悉的、团队能驾驭的、生态适合的框架,然后专注于创造优质内容和良好的用户体验。这才是 SEO 成功的根本。技术框架只是实现目标的工具,真正决定网站排名和用户满意度的,始终是内容的质量和服务的价值。
理解技术决策的权衡,认清 SEO 的本质,基于实际需求选择,避免过度优化,把精力放在真正重要的地方。这些才是从技术讨论中应该获得的核心启示。
SEO 是一个系统工程,涉及技术、内容、营销等多个方面。框架选择只是技术层面的一个小环节。即使选择了最适合 SEO 的框架,如果内容质量不佳、用户体验糟糕、营销策略失败,网站依然无法获得好的排名。
相反,即使使用了理论上性能稍差的框架,但如果能够持续输出高质量内容、构建良好的用户体验、实施正确的 SEO 策略,网站依然可以获得优秀的搜索排名。这才是 SEO 的真谛。
最后,技术在不断演进。Next.js 和 Nuxt 都在快速迭代,引入新的特性和优化。今天的分析可能在明天就过时了。保持学习、关注技术动态、根据实际情况调整策略,这才是长久之计。
参考资料
- Nuxt SEO 官方文档:nuxtseo.com
- Next.js SEO 最佳实践:nextjs.org/docs/app/bu…
- Devalue 序列化库:github.com/Rich-Harris…
- Google 搜索中心文档:developers.google.com/search
- Core Web Vitals 指标说明:web.dev/vitals/
- Schema.org 结构化数据规范:schema.org/
- Nuxt 官方文档:nuxt.com/docs
- Next.js 官方文档:nextjs.org/docs
- Nitro 服务引擎:nitro.unjs.io/
- Web.dev 性能优化指南:web.dev/performance…
AI Agent 设计模式 - ReAct 模式
前言
上一篇我们介绍了 AI 应用的发展历程以及 Agent 的整体概念,这一篇将针对 ReAct(Reasoning + Acting)模式,并对其设计思想和工程实现进行一次更为系统、偏实战向的讲解。
在讲解 ReAct 之前,有必要先澄清一个经常被混用的问题:Agent 到底是什么?
在早期以及当下大量工程实践中,不同 AI 应用对 Agent 的定义并不一致。很多所谓的 Agent,本质上更接近一个预先定义好的 AI workflow:流程、工具、策略都由应用侧提前固化,用户只是触发执行。例如,一个 WebSearch 场景往往就对应一个「搜索 Agent」,或者通过特定提示词(如 /agent)来唤醒一组固定的搜索工具。
从工程视角看,这类 Agent 更多是能力封装与产品抽象,而不是研究语境中强调的「具备自主决策与反馈能力的智能体」。也正因为如此,随着概念被频繁复用,agent 这个词在实际讨论中逐渐变得模糊,单独听到它已很难准确判断其具体能力边界。
如果暂时抛开命名争议,从实现层面抽象来看,一个 Agent 的核心逻辑其实非常简单:一个受控的循环(loop)。在这个循环中,模型不断获取上下文、进行推理、执行动作,并根据结果继续调整行为,直到满足终止条件为止。
在工程实现中,这个过程往往可以被近似理解为:
- 多轮调用 LLM
- 中间可能伴随工具调用
- 有明确的退出条件(如任务完成、步数上限、token 预算)
基于这样的背景,各类 Agent 设计模式(也可以称为范式或框架)逐步出现,而其中最经典、也最具代表性的,便是 ReAct 模式。该模式最早发表于 2022 年,其结构至今仍足以支撑大量中低复杂度的 Agent 场景。
ReAct 模式
核心思想
![]()
ReAct 的提出,本质上是为了解决一个早期 LLM 应用中的割裂问题:推理与行动往往是分离的。
在 ReAct 出现之前,常见的两类模式分别是:
- Reason-only:模型进行显式推理(如 CoT、Scratchpad),但不与外部环境交互
- Act-heavy / Tool-driven:模型频繁调用工具获取信息,但推理过程并不显式呈现或不与行动交错
需要说明的是,这里的分类来自 ReAct 论文中的抽象对比,而并非对具体系统内部实现的严格学术归类。例如:
- SayCan 内部同样包含推理与可行性评估,并非“无 reasoning”
- WebGPT 也存在内部推理过程,只是推理与行动并未以交错形式呈现给模型
在这种背景下,ReAct(Reasoning + Acting) 的核心思想可以概括为一句话:
在行动中思考,在思考中决定行动。
它尝试将思考和行动统一到一个连续的闭环中,模拟人类解决问题时的自然过程:
- Thought:分析当前状态与目标
- Action:基于判断调用工具或执行操作
- Observation:观察行动结果
- Thought:根据新信息再次推理
- 重复上述过程,直到问题解决
从形式上看,ReAct 并没有引入复杂的新组件,而是通过 Thought → Action → Observation 的反复交替,显著提升了模型在多步任务、信息不完备任务中的表现稳定性。
![]()
上图是 ReAct 论文中的一个示例,主要对比了 Standard、Reason Only、Act Only 以及 ReAct 四种不同范式在同一问题下的表现差异。Standard 方式直接给出答案,既不显式展开推理,也不与外部环境交互;Reason Only 虽然在回答前进行了逐步推理,但推理过程完全依赖模型自身的知识,一旦前提判断错误,结论便无法被外部信息纠正;Act Only 则能够多轮调用搜索等工具获取信息,但由于缺乏明确的推理指导,行动过程较为盲目,最终仍然得出了错误结果。相比之下,ReAct 通过多轮 Thought → Act → Observation 的交错执行,使模型能够在行动结果的反馈下不断修正推理路径,最终在后续轮次中得到正确答案。
核心实现
从工程角度看,ReAct 的实现并不复杂,其本质就是一个带有终止条件的循环控制结构。可以用下面这段高度简化的伪代码来概括:
// 简化版实现逻辑
for (let i = 0; i < maxLoops; i++) {
// 1. 思考:LLM分析当前情况,决定做什么
const { thought, action, args, final } = await llmThink();
if (final) {
// 任务完成
break;
}
// 2. 行动:调用具体的工具
const result = await callTool(action, args);
// 3. 观察:将结果作为下一次思考的输入
context.push(`观察到:${result}`);
}
这段代码已经基本覆盖了 ReAct 的核心机制:
- 循环驱动:模型在多轮中逐步逼近目标
- 模型自决策:由 LLM 决定是否继续、是否调用工具
-
显式终止条件:通过
final或循环上限避免失控
在真实系统中,通常还会叠加更多安全与成本控制机制,例如:
- 最大循环次数(maxLoops)
- token 或调用预算
- 工具调用白名单
具体实现
下面将结合一份实际可运行的代码示例,展示一个简化但完整的 ReAct Agent 实现。
LLM 调用
这里使用 @ai-sdk 封装多厂商模型调用,示例中支持 OpenAI 与 Azure OpenAI。该部分属于基础设施层,与 ReAct 本身并无强耦合,因此不再展开其原理。具体的介绍和使用方式可以看我之前写的这篇文章 《AI 开发者必备:Vercel AI SDK 轻松搞定多厂商 AI 调用》 。
import { generateText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { createAzure } from "@ai-sdk/azure";
/**
* Build a model client from env/config.
* Supported providers: 'openai', 'azure'.
*/
export function getModelClient(options = {}) {
const {
provider = process.env.AI_PROVIDER || "openai",
apiKey = process.env.AI_PROVIDER_API_KEY,
baseURL = process.env.OPENAI_BASE_URL || process.env.AZURE_OPENAI_BASE_URL,
resourceName = process.env.AZURE_OPENAI_RESOURCE_NAME, // for azure
} = options;
if (process.env.AI_MOCK === "1") {
return { client: null, model: null };
}
if (!apiKey) {
throw new Error("Missing API key: set AI_PROVIDER_API_KEY");
}
if (provider === "azure") {
const azure = createAzure({ apiKey, resourceName });
return { client: azure, model: azure("gpt-5") };
}
const openai = createOpenAI({ apiKey, baseURL });
const modelName = process.env.OPENAI_MODEL || "gpt-4o-mini";
return { client: openai, model: openai(modelName) };
}
/**
* Chat-like step with messages array support.
* messages: [{ role: 'system'|'user'|'assistant', content: string }]
*/
export async function llmChat({ messages, schema, options = {} }) {
const { model } = getModelClient(options);
const system = messages.find((m) => m.role === "system")?.content;
const result = await generateText({
model,
system,
messages,
...(schema ? { schema } : {}),
});
return result;
}
核心作用只有一个:以 Chat 形式向模型发送上下文,并获得结构化输出。
ReAct 主逻辑
在下面这段代码中,我们完整实现了一个 ReAct Agent 的核心循环逻辑。首先通过 system prompt 对模型的输出形式和行为进行强约束,明确要求其仅以 JSON 格式返回结果,且只能包含 thought、action、args、final 四个字段,并分别约定了调用工具与结束任务时的输出规范。与此同时,在 user 消息中显式告知模型当前可用的 tools 列表,并附带每个工具的功能说明与参数定义,使模型能够在循环过程中基于明确的能力边界自主决策是否进行工具调用。
import { llmChat } from "../llm/provider.js";
import { callTool, formatToolList } from "../tools/index.js";
const SYSTEM = `You are a ReAct-style agent.
You must reason and act using the following loop:
Thought → Action → Observation
This loop may repeat multiple times.
Output format rules:
- You MUST respond with a single JSON object
- The JSON object MUST contain only the following keys:
- thought (string)
- action (string, optional)
- args (object, optional)
- final (string, optional)
- No additional keys are allowed
- Do NOT use Markdown
- Do NOT include any text outside the JSON object
Behavior rules:
- If you need to call a tool, output "thought", "action", and "args"
- If no further action is required, output "thought" and "final"
- When "final" is present, the response is considered complete and no further steps will be taken
- Do NOT include "action" or "args" when returning "final"
Always follow these rules strictly.`;
export async function runReAct({ task, maxLoops = 6, options = {} } = {}) {
const messages = [
{ role: "system", content: SYSTEM },
{
role: "user",
content: `Task: ${task}\nAvailable tools: ${formatToolList()}`,
},
];
const trace = [];
for (let i = 0; i < maxLoops; i++) {
const { text } = await llmChat({ messages, options });
let parsed;
try {
parsed = JSON.parse(text);
} catch (e) {
// console.warn("Parse failed. Text:", text);
// console.warn("Error:", String(e));
messages.push({ role: "assistant", content: text });
messages.push({
role: "user",
content: `Format error: ${String(e)}.
You previously violated the required JSON format.
This is a strict requirement.
If the response is not valid JSON or contains extra text, it will be discarded.
Retry now.`,
});
continue;
}
trace.push({ step: i + 1, model: parsed });
if (parsed.final) {
console.log("Final result:", parsed.final);
return { final: parsed.final, trace };
}
if (!parsed.action) {
messages.push({ role: "assistant", content: JSON.stringify(parsed) });
messages.push({
role: "user",
content:
"No action provided. Please continue with a tool call or final.",
});
continue;
}
console.log("Action:", parsed.action);
const observation = await callTool(parsed.action, parsed.args || {});
trace[trace.length - 1].observation = observation;
messages.push({ role: "assistant", content: JSON.stringify(parsed) });
messages.push({
role: "user",
content: `Observation: ${JSON.stringify(observation)}. Continue.`,
});
}
return { final: "Max loops reached without final.", trace };
}
Tools
Tools 指的是模型在 ReAct 循环中可以调用的具体外部能力接口。通常,一个工具由工具名称、功能描述、参数定义以及对应的 handler 实现组成。下面示例中实现了两个最基础的文件操作工具:readFileTool 用于读取文件内容,writeFileTool 用于写入文件,两者都完整描述了工具名称、用途、参数 Schema 以及实际执行逻辑。createTool 只是一个用于约定工具输出结构的辅助函数,本身并不涉及核心逻辑,主要用于在非 TS 环境下做基础的参数校验。
这里使用 zod 作为参数校验工具,它可以在 JS / TS 环境中统一使用,通过定义 schema 并在运行时执行 parse 校验,有效缓解模型参数幻觉问题;同时可以直接使用 schema 生成标准的 JSON Schema,作为工具参数说明提供给模型,从而减少手写参数描述的成本。
import fs from "fs/promises";
import path from "path";
import { z } from "zod";
import { createTool } from "./types.js";
const readFileSchema = z.object({ file: z.string() });
const writeFileSchema = z.object({ file: z.string(), content: z.string() });
export const readFileTool = createTool({
name: "read_file",
description: "Read a UTF-8 text file from workspace",
schema: readFileSchema,
handler: async ({ file }) => {
const abs = path.resolve(process.cwd(), file);
const data = await fs.readFile(abs, "utf-8");
return { ok: true, content: data };
},
});
export const writeFileTool = createTool({
name: "write_file",
description: "Write a UTF-8 text file to workspace (overwrite)",
schema: writeFileSchema,
handler: async ({ file, content }) => {
const abs = path.resolve(process.cwd(), file);
await fs.mkdir(path.dirname(abs), { recursive: true });
await fs.writeFile(abs, content, "utf-8");
return { ok: true, message: `wrote ${file}` };
},
});
实际效果
基于上述 ReAct 实现,我尝试让模型在本地环境中完成多个小游戏的生成与迭代,包括:2048、飞机大战、贪吃蛇、五子棋。从结果来看,整体完成度和可玩性都明显优于我早期纯手写的一些 demo。当然,需要强调的是:ReAct 并不会凭空提升模型能力,它更多是一种能力放大器。
最终效果在很大程度上仍然依赖于底层模型本身的代码生成、规划与理解能力,而 ReAct 负责的,是为这些能力提供一个稳定的执行框架。
2048
![]()
飞机大战
![]()
贪吃蛇
![]()
五子棋
![]()
结语
ReAct 并不是最复杂、也不是最“智能”的 Agent 模式,但它结构清晰、实现成本低、工程可控性强,是理解和实践 Agent 系统非常合适的起点。
在后续更复杂的场景中,往往会在 ReAct 之上叠加:规划(Plan & Execute)、反思(Reflection)、记忆与长期状态,但无论如何,ReAct 所确立的 思考—行动—反馈闭环,仍然是多数 Agent 系统绕不开的基础结构。
在下一篇中,我们将展开对 P&E(Plan and Execute)模式 的详细解析,重点介绍其设计理念、执行流程及具体实现方式。
我已将相关代码开源到 GitHub,感兴趣的同学可以下载到本地后执行一下玩玩: github.com/Alessandro-…
相关资料
- GitHub 仓库: github.com/Alessandro-…
- AI Agent 介绍: short.pangcy.cn/u/139a9efcc…
- ReAct 模式:www.bohrium.com/paper/read?…
百度一站式全业务智能结算中台
我用 NestJS + Vue3 + Prisma + PostgreSQL 打造了一个企业级 sass 多租户平台
年终总结 - 2025 故事集
📕 如果您刚好点了进来,却不想完整阅读该文章但又想知道它记录了什么。可跳到文末总结。
前言
时隔四个月,再执笔即将进入了新的一年 2026 年...
![]()
时间像往常一样无声息地流动,已近年尾,在过去的 2025 年,三百多天时间里面,发生了很多的事情,或喜,或悲,或静,或闹...此时,灯亮着,窗外偶尔有远处汽车的沙沙声。我在其中,开始回顾并记录撞进心底的瞬间和感受。
你好,世界
还是熟悉的四月份的一天凌晨,老妈跟我在走廊里踱步~
随着清脆的哭声响起,二宝如期而至。过了段时间,护士出来报出母女平安是我们听到的此刻最让人心安的话语。
为什么说是熟悉的四月份,因为老大也是四月份出生的
因为老婆在工作日凌晨分娩,所以我的休陪产的单也先提交了。在收到老婆产后无需我协助事情的话语后,我撤销了陪产单,屁颠屁颠地去上班赚奶粉钱了😄
嗯,从准奶爸到首次喜当爹至今,短短三年时间里面,自己已经是两个小孩的爸爸,真是一个让自己意想不到的速度。
自从当了父母之后,我们更加懂得自己父母的无私且伟大,孩子的天真和无知。
相对于第一次喜当爹时候,自己慌张无措,老妈辛苦地忙前忙后,手慌脚乱。有了第一次的经验,我们对于二宝的处理还是挺稳定:
- 在预产期临近的两三天,我们准备好了大包小包的待产包 -> alway stand by
- 产后的三天时间,请护工照看老婆和新生儿,老妈在旁边陪同,老爸在家照看大宝
- 出院后,老婆和二宝直接月子中心坐月子。老妈和我在家照看大宝,周末月子中心看二宝
![]()
👆即将出月子中心,大宝和二宝的合影👆
在日常里接力的我们
每天,我们都觉得时间不够用,能留出些许空间和时间来放松,已经很满足😌
老婆来回奔波的工作日
在休完三个多月的产假之后,老婆就去复工了。因为二宝还小,老婆会每天中午都回来哺乳。从小孩三个多月到七个多月,雷打不动,公司和家两头跑。
那一台小电驴,隔三差五就需要去充电。小小电驴,已经超出了它的价值~
好不容易,让二宝断奶了。断奶是件很痛苦的事情,要熬夜,涨奶胸痛等。我还记得在成功断奶后的那天晚上,老婆还特意叫我出去买瓶酒回来庆祝一下✨
![]()
👆5%-8% vol 的鸡尾酒👆
虽然二宝断奶了,但是老婆在工作不忙的时候,还是会中午回来看看。用我老婆的话说:有点讨厌,但是又有点舍不得二宝。
工作日,爷爷奶奶的时光
老婆跟我,工作日都需要上班,嗯~赚奶粉钱😀
然后,两个宝宝,工作日的时候主要给爷爷和奶奶带。
有时候,两个宝宝都需要奶奶抱,这可苦了奶奶的腰板子了。爷爷更多的时候,是充当了厨师的角色,保证一家人的三餐伙食,嗯~老爸的厨艺真好👍
爷爷奶奶一天下来的流程:早上带娃出去晒太阳,遛娃(主要是让大宝动起来,中午好睡觉);中午喂饭,午休(大宝一般中午休息两个钟,下午三或四点起来);下午洗澡(怕冷着小孩,一般天黑前洗完),喂饭,陪玩;晚上,等老婆和我下班回来,爷爷和奶奶才有空闲的时间。一般这个时候,爷爷就喜欢下楼去周边逛,奶奶就会躺着床上直一下腰板子(有时会跟爷爷下楼逛街)。工作日的时候,如果奶奶晚上没有出去逛街,那么,会在九点多喂完奶给大宝,奶奶会哄大宝睡觉;如果奶奶外出,那么我就会哄大宝睡觉。
![]()
👆奶奶生日的时候,两宝和爷爷奶奶合影👆
休息日,我们的时光
工作日,班上完了;休息日,该带娃了。爷爷奶奶休息日放假,想去哪里就去哪里,放松放松。
休息日带娃,我们的宗旨就是:尽量让娃多动。所以,我们基本都会外出。忙忙碌碌,嗯,我们分享两件事情:
我还记得,某个周末,我们在商场逛了一天,让大宝在商场里面走,她逛得贼开心(这可不,逛得有多累,睡得有多香),推着二宝。中午直接在商场里面解决吃饭的问题,大宝直接在婴儿车上解决了午睡的事情,二宝则是被老婆或者我背在身上睡觉。母婴室没人的时候,我们就会在里面小憩一会。等两宝醒来之后,再逛一下,一天的时间过得慢但是又很快。
今年的国庆连着中秋,我们在这个长假期里面,会带他们在小区里面露营(在草坪上铺一垫子),让她们自己玩。大宝走路的年纪,这里走那里走,我得屁颠屁颠跟她后面,从这里把她抱过来那里,从那边把她哄过来这边,真想拿条链子绑着她。相反,二宝就淡定多了,只能在那块布那里爬来爬去,被她妈妈限制着。
![]()
👆中秋节当晚,在哄两娃睡着后,老婆跟我在阳台拜月👆
没有惊喜的工位
相对于上一年工作的惊吓,今年的工作可以用没有惊喜来形容。
至于为什么说上一年是惊吓,今年没有惊喜。后面有时间,会出一篇文章来分享下。
简简单单的工位,一水杯,一记事本,一台式电脑,一绿植。屁股一坐,一整天嗖一下就过去了~
在公司,让我活跃起来的,就是中午吃饭的时候。我们的小团体(一安卓,一产品和我)开车去周边吃饭。这段时间,是我们唠嗑的时间,无拘无束,即使我们偶尔会浪费掉午休的时间,但是我还是觉得挺不错的,时间花得值...
工作上糟心的事十根手指可数不过来,触动且温暖了心窝的事情屈指可数。
记得招进来的一个新人,我带了他几天,最后入职短短几天被某人恶心而离职了。他离职的前一天,点了一杯奶茶给我,虽然自己嘴里面说着别客气,但是心里面暖暖的。他才进来短短几天就走人了,自己心里莫名生气:为什么我自己招的人,自己带着熟悉项目后,一转手就被恶心到要离职了???最终他却还温柔地以自我问题作离职的原因。
![]()
👆点了份奶茶放我桌面后的对话👆
把明天轻轻放进心里
2026 年悄然将至。在对新的一年有所展望之前,我们先回顾下年终总结 - 2024 故事集中立下的两个 Flags 和完成的情况:
| 序号 | 目标 | 实际 | 完成率 |
|---|---|---|---|
| 1 | 分享文章 20+ 篇 | 分享文章 18 篇 | 90% |
| 2 | 锻炼 30+ 次 | 锻炼 32 次 | 107% |
嗯~ 目标完成率还不赖。
![]()
👆每次锻炼我都会在朋友圈记录,每次耗时 45 分钟左右👆
对于分享文章,一开始就是秉承着记录自己在工作中遇到的一些问题,方便自己和其他人查找的宗旨来进行记录,后面是因为平台搞了奖励而进行的一些创作。而现在,随着 chatgpt, deepseek 等大语言模型的机器人横空出世,浅显的分享和问题的记录都显得鸡肋。所以,在 2026 新的一年内,文章的分享要更加有目的性和实际的意义。2026 年,谁知道会有几篇文章会出炉,也许一篇,也许十篇,也许二十篇,也许零篇。
对于锻炼,这是我长期需要坚持的一件事情,也是最好量化的事情。在新的一年里面,锻炼的次数需 35+ 。
为人父母,为人儿女。我们都有自己的那份责任,2026 年,希望自己更多的时间是回归家庭 - 去听听孩子的欢声笑语,去看看爸妈脸上的笑容,去体验大家聚在一起热热闹闹的氛围 and more
![]()
👆老爸生日,大姐,二姐大家的娃聚在一起👆
总结
2025 年,简简单单却忙忙碌碌👇:
在生活方面,欢迎二宝加入大家庭。这让我们接下来的一年时间里面,时间安排更加充实紧凑,更感受到当爹妈的不容易,感恩自己的父母在以前那年代含辛茹苦带大了我们三姐弟。在工作方面,没有太多想记录的东西,平平淡淡地打卡上下班。
展望 2026,还是给自己制定了锻炼次数的量化目标。在这个人工智能逐渐成熟的环境下,希望自己能够使用它提升工作效率和帮助自己成长。在 2026 年,自己的重心会放在家庭这边,去感受孩子的成长和家的氛围。
完成于中国广东省广州市
2025 年 12 月 22 日
跨域问题详解
引言:在一个前/后端分离的项目开发中,常常会出现前端向后端发送一个请求时,浏览器报错:
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'http://localhost:3000' 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.,也就是通常说的“跨域访问”的问题,由此导致前端代码不能读取到后端数据。
摘要:所谓“跨域问题”,本质上是浏览器在同源策略约束下,主动阻止 JavaScript 读取跨源请求响应的一种安全保护行为。解决跨域问题主要通过服务器端设置CORS(跨域资源共享)机制——浏览器放行跨域请求响应的数据;或者Nginx/网关的代理功能——跨域的请求实际由网关代发,浏览器端依旧是同源请求。
什么是跨域访问
跨域访问指的是:当前网页所在的“源(Origin)”去访问另一个“不同源”的资源,而该访问被浏览器安全策略所限制或拦截的情况。
在浏览器中一个“源”由三部分组成:协议(Protocol) + 域名(Host) + 端口(Port),只要有一个部分不一样就是跨源,也即跨域。例如:
| URL | 协议 | 域名 | 端口 | 是否同源 |
|---|---|---|---|---|
http://example.com |
http | example.com |
80 | 基准 |
http://example.com:8080 |
http | example.com |
8080 | 跨域(端口不同) |
https://example.com |
https | example.com |
443 | 跨域(协议不同) |
http://api.example.com |
http | api.example.com |
80 | 跨域(域名不同) |
这里需要强调:对“跨域访问”进行限制是浏览器的安全策略导致的,并不是前端或后端技术框架引起的。
为什么跨域访问请求“得不到”数据
这里就要展开说明为什么浏览器要对“跨域访问”进行限制,导致(尤其是)Web前端中发送HTTP请求会得不到数据,并在控制台报错。
出于安全性,浏览器会采用同源策略(Same-Origin Policy,SOP)限制脚本内发起的跨源 HTTP 请求,限制一个源的文档或者它加载的脚本如何与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。例如,它可以防止互联网上的恶意网站在浏览器中运行 JavaScript 脚本,从第三方网络邮件服务(用户已登录)或公司内网(因没有公共 IP 地址而受到保护,不会被攻击者直接访问)读取数据,并将这些数据转发给攻击者。
假设在没有同源限制的情况下:
- 用户已登录银行网站
https://bank.com(Cookie 已保存) - 用户同时打开一个恶意网站
https://evil.com -
evil.com的 JavaScript 可以:- 直接读取
bank.com的接口返回数据 - 发起转账请求
- 窃取用户隐私信息
- 直接读取
这是非常严重的安全灾难。
同源策略将跨源之间的访问(交互)通常分为3种:
- 跨源写操作(Cross-origin writes)一般是被允许的。例如链接、重定向以及表单提交。特定少数的 HTTP 请求需要添加
预检请求。 - 跨源资源嵌入(Cross-origin embedding)一般是被允许的,比如
<img src="...">、<script src="...">、<link href="...">。 - 跨源读操作(Cross-origin reads)一般是不被允许的。
再次强调:跨域限制是“浏览器行为”,不是后端服务器的限制。后端服务本身是可以接收来自任何来源的 HTTP 请求的。
比如前端访问fetch("https://api.example.com/data"),而当前页面来自http://localhost:8080,请求可以发出去,但浏览器会拦截响应,不让 JavaScript 读取。
要使不同源可以访问(交互),可以使用 CORS来允许跨源访问。CORS 是HTTP的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。
怎么解决跨域访问的“问题”
CORS机制
跨源资源共享(Cross-Origin Resource Sharing,CORS,或通俗地译为跨域资源共享)是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己(服务器)的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头(Header)。
对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是GET以外的 HTTP 请求,或者搭配某些MIME类型(多用途互联网邮件扩展,是一种标准,用来表示文档、文件或一组数据的性质和格式)的POST请求),浏览器必须首先使用OPTIONS方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(例如Cookie和HTTP 认证相关数据)。
一般浏览器要检查的响应头有:
-
Access-Control-Allow-Origin:指示响应的资源是否可以被给定的来源共享。 -
Access-Control-Allow-Methods:指定对预检请求的响应中,哪些 HTTP 方法允许访问请求的资源。 -
Access-Control-Allow-Headers:用在对预检请求的响应中,指示实际的请求中可以使用哪些 HTTP 标头。 -
Access-Control-Allow-Credentials:指示当请求的凭据标记为 true 时,是否可以暴露对该请求的响应给脚本。 -
Access-Control-Max-Age:指示预检请求的结果能被缓存多久。
如:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
可知,若使用CORS解决跨域访问中的问题要在服务器端(通常是后端)进行设置。以Spring Boot的后端为例:
-
局部的请求:在对应的
Controller类或指定方法上使用@CrossOrigin。如下@CrossOrigin( origins = "http://localhost:3000", allowCredentials = "true" ) -
全局使用:新建一个配置类并注入Spring框架中。如下:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins( "http://test.example.com" ) .allowedMethods("GET","POST","PUT","DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }
使用CORS 的优点:官方标准;安全、可控;与前后端分离完美匹配。缺点:需要服务端正确配置;初学者容易被预检请求困扰。
通过架构或代理手段
除了使用CORS的方式,还可以通过架构设计或代理的方式让跨域“变成”同源访问。
比如通过Nginx / 网关代理浏览器(前端)请求,再由Nginx或网关访问服务器获取数据。
浏览器 → 前端域名 → Nginx → 后端服务
这样的话在浏览器(前端)看到将始终是对当前网站(前端域名)的访问(即使打开开发者工具的网络选项,请求的url地址也是前端域名)。
一个Nginx的配置示例:
server {
listen 443;
server_name www.example.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
前端请求示例:axios.get('/api/user')。
这是通过Nginx或网关这样的中间件实现的,如果在开发阶段想要快速解决跨域访问问题,可以在相应的项目构建的配置中设置代理。这里以Vite为构建工具的Vue项目为例,在vite.config.js中添加如下的配置项:
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
然后请求的URL采用这样的方式axios.get('/api/user'),不在使用axios.get('http://localhost:8080/api/user')。
使用代理方式的优点:无跨域;性能好;适合生产环境。缺点:需要额外部署配置。
总结
跨域问题并不是请求被禁止,而是浏览器在同源策略约束下,出于安全考虑,限制前端 JavaScript 对跨源响应数据的访问行为。
跨域问题的根源是 浏览器实现的同源策略(Same-Origin Policy),而不是:
- HTTP 协议限制
- 后端服务器限制
- 前端框架(Vue / React)的问题
浏览器阻止的是JS 获取结果,而不是“阻止请求发送”——跨域请求可以被发出,服务器可以正常返回(比如预检请求响应),浏览器阻止JavaScript访问响应数据。
“跨域问题”只存在于浏览器环境,例如:
- Java / Node / Python 发 HTTP 请求——没有跨域问题
- Postman / curl ——没有跨域问题
- 微服务之间调用——没有跨域问题
因为这些环境不执行浏览器的同源策略。跨域问题是浏览器安全模型的一部分,本质上是对跨源资源访问的“读权限控制”,而非通信能力限制。
使用CORS 并不是“绕过”同源策略——浏览器的同源策略始终存在;CORS 是 同源策略的“例外机制”;本质是:服务器显式授权浏览器放行。换句话说:没有 CORS,就没有“合法的跨域读取”。
只要不产生跨域,就不会有跨域问题,所以可以使用代理或网关将请求进行转发,而不是由浏览器直接请求服务器端发生跨域问题。
你每天都在用的 JSON.stringify ,V8 给它开了“加速通道”
V8 如何把 JSON.stringify 性能提升 2 倍
JSON.stringify 应该是 JavaScript 里用得最多的函数之一了。
API 响应要序列化,日志要格式化,数据要存 localStorage,调试要打印对象……几乎每个项目都离不开它。
但说实话,用的时候很少会想"这玩意儿快不快"。反正就是调一下,能用就行。
V8 团队显然不这么想。V8 是 Chrome 和 Node.js 背后的 JavaScript 引擎,你写的每一次 JSON.stringify,最后都要靠它来跑。2025 年 8 月,他们发了篇博客,讲了怎么把 JSON.stringify 的性能提升到原来的 2 倍以上。
这篇文章拆解一下他们做了什么。
读者导航:不懂 V8 也能看
先记住三句话就够了:
- 绝大多数优化都在“走捷径”:先判断输入是不是“简单、可预测”的对象,是的话走更快的路径。
- 很多名词听着硬,其实都在做同一件事:减少检查、减少函数调用、让 CPU 一次干更多活、减少内存搬运。
-
你能做的配合也很简单:少用会触发副作用的写法(getter、
toJSON、格式化参数),保持数据对象“干净”。
下面遇到生词可以先跳过,看完“对开发者的启示”再回头补。
优化的前提:无副作用检测
JSON.stringify 慢在哪?
一个重要原因是它要处理各种边界情况:对象可能有 toJSON 方法,属性可能是 getter,可能有循环引用……这些都可能产生副作用,导致序列化结果不可预测。
V8 的第一步优化是:检测对象是否"干净"。
如果能确定序列化过程不会触发任何副作用,就可以走一条快速路径,跳过大量的安全检查。
"if we can guarantee that serializing an object will not trigger any side effects, we can use a much faster, specialized implementation."
这条快速路径用迭代替代了递归,好处有两个:
- 不用担心栈溢出(深层嵌套对象)
- 减少函数调用开销
字符串处理:双版本编译
JavaScript 字符串有两种内部表示:单字节(Latin-1)和双字节(UTF-16)。
以前 V8 用统一的方式处理,现在编译了两个特化版本的序列化器,分别针对这两种编码优化。可以简单理解成:如果字符串全是英文数字,就走“单字节快车道”;如果包含中文表情等,就走“UTF-16 车道”。
遇到混合编码的情况(比如一个对象里既有纯 ASCII 字符串,又有中文),会在执行时动态切换。
这种"按需特化"的思路在编译器优化里很常见,但用在 JSON 序列化上还是挺有意思的。
SIMD 加速字符扫描
序列化字符串时,需要扫描哪些字符需要转义(比如 \n、\t、")。
V8 用了两种硬件加速策略:
- SIMD 指令:对于较长的字符串,一次处理多个字符(你可以理解成“把 16 个字节打包一起扫一遍”)
- SWAR 技术:对于较短的字符串,用位运算在普通寄存器上并行处理(SIMD 的“轻量版”)
SWAR(SIMD Within A Register)是个挺老的技术,思路是把一个 64 位寄存器当成 8 个 8 位的"小寄存器"用,通过位运算实现并行。
举个例子,判断一个字节是否需要转义,可以这样:
// 伪代码示意
// 需要转义的字符:< 0x20 或 == 0x22(") 或 == 0x5C(\)
function needsEscape(byte) {
return byte < 0x20 || byte === 0x22 || byte === 0x5C;
}
用 SWAR 可以一次判断 8 个字节,只要用合适的掩码和位运算。
Hidden Class 标记
V8 内部用 Hidden Class(隐藏类)来优化对象属性访问。你可以把它理解成“对象结构的身份证”:同一类对象(同样的字段、同样的顺序)会复用同一个结构描述。
这次优化加了一个新标记:fast-json-iterable。
当一个对象的属性满足特定条件时,就给它打上这个标记。下次序列化同类对象时,直接跳过验证检查。
这就是典型的"用空间换时间"——在对象上多存一个标记,换来后续操作的加速。
数字转字符串:换算法
把数字转成字符串也是序列化的一部分。
V8 以前用 Grisu3 算法,现在换成了 Dragonbox。
你不需要理解算法细节,只要知道:Dragonbox 能更快、更稳定地把浮点数转成最短且精确的十进制表示。这个改动不只影响 JSON 序列化,所有 Number.toString() 都能受益。
内存管理:分段缓冲区
以前序列化大对象时,V8 用一块连续内存作为缓冲区。对象越大,缓冲区就要不断扩容,每次扩容都要重新分配和复制。
新实现用分段缓冲区(segmented buffer),多个小块链起来用,避免了昂贵的重分配。直觉上就是:不强求“一次申请一大块”,而是“够用就多挂一块”。
这个思路和 Linux 内核的 sk_buff 链表类似——不追求内存连续,换来分配效率。
快速路径的适用条件
不是所有 JSON.stringify 调用都能走快速路径。需要满足:
-
不传 replacer 和 space 参数
// 可以走快速路径 JSON.stringify(obj); // 不行 JSON.stringify(obj, null, 2); JSON.stringify(obj, ['name', 'age']);这里的
replacer/space分别是“过滤/改写字段的回调或白名单”和“为了好看而加的缩进”。它们会让序列化过程更复杂,所以很难走最激进的优化路径。 -
纯数据对象
- 没有
toJSON方法 - 没有 getter
- 没有 Symbol 属性
- 没有
-
字符串键
- 所有属性名都是字符串(不是数字下标)
-
简单字符串值
- 字符串值本身没有特殊情况
实际项目里,大部分序列化场景都满足这些条件。API 返回的纯 JSON 数据、配置对象、日志数据……基本都能用上。
什么时候能用?
Chrome 138 / V8 13.8 开始可用。
Node.js 的话,需要等对应的 V8 版本合入。目前最新的 Node.js 22 用的是 V8 12.x,还得等一等。
对开发者的启示
虽然优化是 V8 团队做的,但有几点可以参考:
1. 保持对象"干净"
避免在需要序列化的对象上加 getter 或 toJSON 方法。如果必须用,考虑在序列化前转换成纯数据对象。
// 不太好
class User {
get fullName() {
return this.firstName + ' ' + this.lastName;
}
}
// 更好
const user = {
firstName: 'John',
lastName: 'Doe',
fullName: 'John Doe' // 直接计算好
};
2. 大量序列化时考虑结构一致性
V8 的 Hidden Class 优化依赖对象结构一致。如果你要序列化大量对象,保持它们的属性顺序和类型一致。
// 好
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
];
// 不太好(age 类型不一致)
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: '30' }, // 字符串
{ name: 'Charlie', age: null }
];
3. 避免无意义的格式化
开发环境用 JSON.stringify(obj, null, 2) 看着舒服,但这会跳过快速路径。生产环境记得去掉。
// 开发环境
console.log(JSON.stringify(data, null, 2));
// 生产环境
console.log(JSON.stringify(data));
总结
- 分析热点:找到可以优化的场景(无副作用对象)
- 特化路径:针对常见情况走快速路径,边界情况走通用路径
- 硬件加速:用 SIMD 和 SWAR 提升字符处理速度
- 利用已有信息:通过 Hidden Class 标记避免重复验证
- 改进算法:Dragonbox 替代 Grisu3
- 优化内存:分段缓冲区避免重分配
这些技术单拿出来都不新鲜,但组合起来能把一个已经很成熟的 API 性能翻倍,还是挺厉害的。
对我们写代码的人来说,最大的收获可能是:写"干净"的代码不只是为了可读性,有时候还能让引擎更好地优化。
延伸阅读
如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:
Claude Code Skills(按需加载,意图自动识别,不浪费 token,介绍文章):
- code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)
- 5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活
- first-principles-skill - 第一性原理思考,适合架构设计和技术选型
全栈项目(适合学习现代技术栈):
- prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
- chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB
Node.js 原生功能狂飙,15 个热门 npm 包要失业了
AI Agent 介绍
前言
这周在组内做了一次关于 Agent 设计模式 的分享,主要介绍和讲解了 ReAct 模式 与 P&A(Plan and Execute)模式。我计划将这次分享拆分为三篇文章,对我在组会中讲解的内容进行更加系统和细致的整理。
在正式进入具体的 Agent 模式实现之前,有一个绕不开的问题需要先回答清楚:
什么是 AI Agent?它解决了什么问题?以及在近几年 AI 技术与应用快速演进的过程中,AI 应用的开发范式经历了哪些关键变化?
这一篇将不直接展开某一种 Agent 模式的实现细节,而是先回到更宏观的视角,从 AI 应用形态与工程范式的演进 入手,梳理 Agent 出现的技术背景与必然性。
需要说明的是,下文对 AI 应用演进阶段的划分,是一种以“应用开发范式”为核心的抽象总结。真实的技术演进在时间上存在明显重叠,但这种阶段化的叙述有助于我们理解:为什么 Agent 会在当下成为主流方向。
AI 应用的发展历程
第一阶段:提示词工程
2022 年 11 月,GPT-3.5 发布后,大模型开始从研究领域进入大众视野。对开发者来说,这是第一次可以在实际产品中直接使用通用语言模型。
这一阶段的 AI 应用形态非常简单,大多数产品本质上都是一个对话界面:用户输入问题 → 模型生成回答 → 结束。
很快,围绕 Prompt 的工程实践开始出现。由于模型对上下文非常敏感,系统提示词(System Prompt)成为当时最直接、也最有效的控制手段。常见的做法是通过提示词约束模型的角色、输出形式和关注重点,例如:
“你是一个资深的前端开发工程师,请严格以 JSON 格式输出结果……”
这类“身份面具”式的提示,本质上是通过上下文约束来减少模型输出的发散性,让结果更贴近预期。在这一阶段,也陆续出现了 Chain-of-Thought、Few-shot Prompting 等推理增强技巧,但它们依然属于单次生成模式:模型在一次调用中完成全部推理,过程中无法获得外部反馈,也无法根据中间结果调整策略。
第二阶段:RAG
当 AI 开始被用于真实业务场景时,很快暴露出两个问题:模型不了解私有知识,以及生成结果难以校验。以 GPT-3.5 为例,它的训练数据截止在 21 年左右,对于新技术以及企业内部文档、业务规则更是不了解,直接使用往往不可控。
RAG(Retrieval-Augmented Generation)是在这种背景下被广泛采用的方案。它的核心做法是:
- 将私有知识进行切分和向量化存储;
- 用户提问时,先进行相似度检索;
- 将命中的内容作为上下文提供给模型,再由模型完成生成。
通过这种方式,模型不需要记住所有知识,而是在生成时按需获取参考信息。
RAG 的价值不仅在于补充新知识,更重要的是带来了可控性和可追溯性:生成内容可以明确对应到原始文档,这一点在企业场景中尤为关键。
第三阶段:Tool Calling
如果说 RAG 让模型能够“查资料”,那么 Function / Tool Calling 则让模型开始能够“做事情”。
在这一阶段,开发者会把可用能力(如查询数据库、调用接口、执行脚本)以结构化的方式提供给模型,包括函数名、参数说明和功能描述。模型在理解用户意图后,可以返回一个明确的工具调用请求,再由程序完成实际执行。
这一能力的出现,标志着 AI 第一次在工程上具备了可靠调用外部系统的能力。它不再只是一个聊天机器人,而是一个可以触发真实世界动作的“控制器”,这也是后续 Agent 能够落地的关键技术支撑。
第四阶段:AI Workflow
当 RAG 能力和 Tool Calling 能力逐渐成熟后,开发者开始尝试把多个步骤组合起来,形成完整的业务流程。这催生了以 Dify、Coze 为代表的 AI Workflow 范式。
在 Workflow 模式下,一个 AI 应用会被拆解为多个固定节点,并按照预设顺序执行,例如:检索 → 判断 → 工具调用 → 汇总输出。
Workflow 的优势非常明显:
- 流程清晰,行为可预期;
- 易于测试和运营;
- 对非工程人员友好。
但问题也同样明显:流程完全由人设计,模型只是执行者。无论问题复杂与否,都必须走完整条路径。这种方式在应对高度动态或非标准任务时,灵活性有限。
第五阶段:Agent
在 Agent 出现之前,大多数 AI 应用仍然遵循一种典型模式:输入 → 单次/编排好的推理 → 输出。
而 Agent 的出现,本质上是将“任务编排”的控制权从人类手中交还给了 AI。在 Agent 架构下,AI 不再是被动执行一段代码,而是一个具备以下核心能力的闭环系统:
- 将复杂目标拆解为多个可执行步骤;
- 根据工具执行结果调整后续行动;
- 在失败时尝试修正策略;
- 在多步过程中维护上下文状态。
这些能力并不是一次模型调用完成的,而是通过多轮推理与执行形成闭环。也正是在这一点上,Agent 与前面的应用形态拉开了差距。
Agent 设计模式解决的问题
当 Agent 开始承担更复杂的任务时,问题也随之出现:
- 多步推理容易跑偏;
- 执行失败后缺乏统一的修正策略;
- 成本和稳定性难以控制。
Agent 设计模式的作用,就是把这些反复出现的问题抽象成可复用的结构。
无论是 ReAct,还是 Plan and Execute,它们关注的核心并不是“让模型更聪明”,而是:如何在工程上组织模型的推理、行动和反馈过程,使系统整体可控、可维护。
理解这些模式,有助于我们在构建 Agent 系统时少走弯路,而不是每一次都从零开始设计整套交互与控制逻辑。
结语
从最初基于 Prompt 的简单对话,到如今具备一定自主能力的 Agent,我们看到的不只是模型能力的提升,更是 AI 在实际使用方式上的变化。
回顾整个过程会发现,很多关键技术并不是最近才出现的。RAG 的核心思路早在几年前就已经被提出,ReAct 也并非新概念,只是在最近随着模型推理能力提升、工具链逐渐成熟,才真正具备了工程落地的条件。很多时候,并不是想法不存在,而是时机还没到。
理解这些演进背景,有助于我们判断哪些能力是短期噱头,哪些是长期方向。下一篇文章将聚焦 Agent 设计模式中最常见、也最实用的 ReAct 模式,结合实际实现,看看它是如何让 AI 在执行任务的过程中逐步思考、不断调整策略的。
参考资料
- 提示工程指南
- Chain-of-Thought Prompting Elicits Reasoning in Large Language Models.
- Language Models are Few-Shot Learners
- Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.
- Toolformer: Language Models Can Teach Themselves to Use Tools.
- A Survey on Large Language Model based Autonomous Agents.
为什么协程能让程序不再卡顿?——从同步、异步到 C++ 实战
nicegui 3.4.0 + sqlite3 做一个简单维修登记系统
HarmonyOS BLE 快速上手
【用户行为监控】别只做工具人了!手把手带你写一个前端埋点统计 SDK | 掘金一周 12.18
本文字数1100+ ,阅读时间大约需要 4分钟。
【掘金一周】本期亮点:
「上榜规则」:文章发布时间在本期「掘金一周」发布时间的前一周内;且符合各个栏目的内容定位和要求。 如发现文章有抄袭、洗稿等违反社区规则的行为,将取消当期及后续上榜资格。
一周“金”选
![]()
内容评审们会在过去的一周内对社区深度技术好文进行挖掘和筛选,优质的技术文章有机会出现在下方榜单中,排名不分先后。
前端
【AI 编程实战】第 2 篇:让 AI 成为你的前端架构师 - UniApp + Vue3 项目初始化 @HashTang
最终生成的代码不仅逻辑清晰,还处理了很多细节,比如 iOS 的样式适配。这就是 AI 辅助开发的威力:它不仅能写出跑通的代码,还能考虑到平台差异和边界情况。
【用户行为监控】别只做工具人了!手把手带你写一个前端埋点统计 SDK @不一样的少年_
想知道那些“黑科技”是如何拦截点击、统计 PV(页面浏览量)与 UV(独立访客数)、精确计算页面停留时长的吗?本文将从原理角度切入,手把手带你设计并实现一个轻量级、功能完备的用户行为监控 SDK
后端
基于Nacos的轻量任务调度方案 —— 从 XXL-Job 的痛点说起 @踏浪无痕
JobFlow 只是一个想法,一个技术探讨。它的核心不是技术细节,而是一个设计理念:中间件即业务。在云原生时代,调度能力不应该是一个独立部署、独立运维的"平台",而应该是内嵌在微服务体系中的能力模块。
Android
十分钟速览 Kotlin Flow 操作符 @RockByte
作为基于协程构建的响应式流 API,Kotlin
Flow让你可以用声明式的方式优雅地处理异步数据流。但要想真正发挥它的强大能力,关键在于熟练掌握各种操作符。
人工智能
✨TRAE SOLO + Holopix AI | 复刻 GBA 游戏-"🐛口袋妖怪 @coder_pig
GBA的图形芯片 (PPU) 没有 "加载图片" 的概念,它只能读取连续的显存块。开发者把所有角色动画帧拼成一张图,通过修改 UV坐标 (读取位置) 来切换帧。
5小时整理60页《Google Agent指南》,不懂Agent的包教包会 @大模型教程
现阶段来说,Tools是Agent真正的核心,而且Tools调用不准也是Agent架构最大的难点,当前我们在生产环境使用Skills技术 + 强意图也最多把准确率做到90%左右。
数据库AI方向探索-MCP原理解析&DB方向实战|得物技术 @得物技术
通过整合多模态数据(文本与二进制)资源使 AI 模型能访问私有或专属知识库(如企业内部文档)、实时外部 API 及系统动态信息,有效突破单一大模型数据孤岛。
AutoGLM 开源实测:一句话让 AI 帮我点个鸡排 @飞哥数智谈
Phone Agent是一个基于AutoGLM构建的手机端智能助理框架,它能够以多模态方式理解手机屏幕内容,并通过自动化操作帮助用户完成任务。
IOS
Flutter 官方正式解决 WebView 在 iOS 26 上有点击问题 @恋猫de小郭
新机制让开发者可以在 Dart 代码中直接指定 PlatformView 的手势拦截策略,而不是依赖全局配置或原生代码,根据不同场景配置不同的拦截处理机制,而
touchBlockingOnly就是全新的支持。
📖 投稿专区
大家可以在评论区推荐认为不错的文章,并附上链接和推荐理由,有机会呈现在下一期。文章创建日期必须在下期掘金一周发布前一周以内;可以推荐自己的文章、也可以推荐他人的文章。