
做前端最憋屈的是什么不是需求改来改去而是你花三年练出来的组件设计、交互手感、性能优化在面试官眼里可能不如一个会写CRUD的人值钱。这两年圈子里天天喊前端已死我反倒觉得是某些前端自己把自己做成了页面拼接工。2025年如果还只盯框架源码和面试八股确实会错过一个很实际的东西AI应用开发。而且这件事不一定非得学Python。先说结论一个熟练的前端转型做AI应用开发比后端转过来有天然优势。因为AI应用层拼的根本不是算法而是场景设计、流程编排和交互体验——这恰恰是前端的看家本领。而工具链上Next.js加LangChain.js已经可以把AI应用的开发成本压得很低低到什么程度低到一个会React的人两周就能做出一个能演示、能上线、能拿去面试的AI产品。这篇文章就把我从零搭建Next.js LangChain.js应用的过程、踩过的坑、用下来的体感全部摆出来。内容不是给AI工程师看的就是给前端同行看的。你会搞清楚这些技术各自在干什么、怎么串起来、为什么这条路低成本以及怎么靠这类项目把自己从CRUD选手里摘出来。1. 前端转AI的窗口期为什么应用层需要前端思维1.1 CRUD的终点是成本竞争AI应用的终点是体验竞争我给自己的定位一直是在业务里解决问题的前端。这几年做了不少后台管理系统表格、表单、权限、图表一个月能重复造三个。这些东西做到后面完全没有技术积累因为CRUD的核心问题是怎么让开发更快、更便宜这拼的是工程化能力和人力成本拼不出个人溢价。当每一家公司都能用现成的UI库在中后台项目里快速交付时你靠这点技能拿到的薪水上限就被钉死了。AI应用不一样。2025年的AI产品底层模型的能力已经严重同质化GPT和Claude、国内各家大模型差的不是聪明程度而是谁把这些能力包装成用户愿意用的产品。一个AI应用能不能留住用户取决于那个对话界面是否顺手、流式输出是否顺畅、思考过程展示是否直观、错误处理是否让人不焦虑。这些细节全部要靠前端来解决。举个最直接的例子同一款RAG助手后端接口能力一样A团队做的聊天窗口全程转圈B团队做了逐字流式输出加引用来源用户的体感就是天壤之别。而转圈还是流式这件事就是前端的技术活。所以我一直觉得AI应用层的核心岗位缺口不是算法工程师而是懂AI能力边界、能把模型输出打磨成产品体验的前端工程师。1.2 Next.js把全栈门槛压到了前端已知范围以前前端要做全栈得学一门后端语言Python的FastAPI、Node的Express再加上部署、鉴权、数据库内容一下就炸了。Next.js最聪明的地方是让前端程序员用已经掌握的React和JavaScript同一套代码里直接写后端逻辑。具体到AI应用开发它的价值有三条API Route就是你的后端。在app/api/xxx/route.ts里写一个函数导出POST方法就是一个接口。不用单独起一个服务不用配CORS前端fetch同源请求连跨域问题都省了。前后端用一套TypeScript类型。LangChain.js返回的对象、你自定义的数据结构前端直接用类型出错在编译期就暴露。部署链路极短。Vercel一键部署不需要买服务器配Nginx对前端特别友好。这些特性放在AI应用里更关键因为AI应用需要频繁调整接口逻辑、拼消息格式、改提示词如果前后端分开两个项目改光联调就累死。1.3 LangChain.js是胶水不是框架很多人一听LangChain就发怵以为又是什么重量级框架。我想先把它在项目里的角色说清楚它就是个胶水帮你把AI应用里的常见环节——连模型、组提示词、调函数、管历史、做输出解析——串成一条流水线。它本质上是一堆封装好的工具函数你拆开看每个都不复杂。LangChain.js还有一层价值是模型无关。今天你接OpenAI明天你想换通义千问、换DeepSeek、换国内的模型只要改一行配置指向对方的API地址代码基本不用动。这也是我在真实项目里越来越依赖它的原因——大模型这行变化太快今天便宜好用的模型明天可能就变了你不能把应用锁死在某一家上。对于前端来说LangChain.js最大的意义是它也是TypeScript生态。我们不用切语言、不用学Python那套解释器和虚拟环境npm install完就干活这就是低成本三个字最实在的地方。2. 从零搭建Next.js LangChain.js的环境与项目结构2.1 Node版本与包安装的兼容性实际动手之前先说环境很多人的第一关不是代码是依赖装不上。我的建议Node.js版本至少18.17以上20 LTS更稳妥。Next.js 14、LangChain.js在Node 18下都能跑但你要是想用Next.js 15推荐Node 20。包管理器可以用npm也可以上pnpm。我的经验是pnpm安装速度快占磁盘小但在某些CI环境下yarn兼容性更好。个人项目随意团队项目统一就行。装依赖时有个容易踩的坑LangChain生态的包名非常碎langchain是核心库模型接入要按厂商装各自的包比如langchain/openai、langchain/anthropic工具库还需要langchain/core。很多人一把梭装langchain就以为全有了结果跑起来找不到模块。标准操作是先装核心再按你实际用的模型装对应的接入包npm install langchain langchain/core langchain/openai如果你后面要写工具调用、结构化输出大概率还会用到zod做参数校验这个也装上npm install zod2.2 用App Router组织AI接口初始化项目用官方脚手架就可以npx create-next-applatest ai-frontend-project创建时TypeScript选Yes、ESLint选Yes、App Router选Yes。Tailwind选不选看个人我建议选上后面写聊天界面不用再配样式工具。项目结构我的习惯是这样的app/ page.tsx # 聊天交互页 api/ chat/ route.ts # AI对话接口 lib/ model.ts # 模型实例的集中配置 prompts.ts # 提示词模板 tools.ts # 工具函数定义app/api/chat/route.ts是Next.js的API Route导出的函数名对应HTTP方法// app/api/chat/route.ts import { NextResponse } from next/server; export async function POST(request: Request) { const body await request.json(); // 这里后续接LangChain.js return NextResponse.json({ message: ok }); }我最开始犯过一个错在page.tsx里直接调用模型SDK。浏览器里调用大模型接口不仅会暴露API Key而且会撞上服务商的跨域限制。所以记住一条铁律所有模型调用必须放在API Route里前端只和API Route通信。2.3 环境变量与密钥管理模型API Key放在项目根目录的.env.localOPENAI_API_KEYsk-xxxx OPENAI_BASE_URLhttps://...这个文件默认被.gitignore忽略不会提交到GitHub。但你得留个心眼如果你用create-next-app初始化项目默认的.gitignore是包含.env*的别手贱去改它。读环境变量的方式是process.env.OPENAI_API_KEY注意Next.js里变量名以NEXT_PUBLIC_开头会暴露到浏览器API Key一定别加这个前缀。服务端代码API Route、Server Component里可以用任意环境变量。这点我见过很多新手搞混一旦把Key打包进了前端代码网站一上线别人看源码就能扒走。3. 理解LangChain.js三件套模型、提示词、工具调用3.1 ChatModel所有交互的第一入口在LangChain.js里一切从模型实例开始。集中在一个文件里管理方便换模型// lib/model.ts import { ChatOpenAI } from langchain/openai; export const chatModel new ChatOpenAI({ model: gpt-4o-mini, temperature: 0.7, apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL, });ChatOpenAI这个类封装了一个对话模型的能力。你不需要自己拼HTTP请求、处理鉴权、管重试只需要喂给它消息数组格式就是{ role: user | assistant | system, content: string }做过聊天功能的都熟。基础调用方式有这么几种// 最简单的一次问答 const response await chatModel.invoke(给我讲个程序员笑话); console.log(response.content); // 多轮对话 const messages [ { role: system, content: 你是一个旅游规划助手 }, { role: user, content: 推荐一个成都三天的行程 }, ]; const reply await chatModel.invoke(messages);invoke返回完整的回复结果适合不需要展示中间过程的场景。但它有一个缺点等模型全部生成完才返回耗时可能好几秒用户看着一点反馈没有体验很差。所以真实聊天应用要用流式这个到第4节详细讲。3.2 Prompt模板别在代码里拼字符串很多人写Prompt就是字符串拼接比如写你是一个擅长${topic}的助手。项目小的时候无所谓一旦提示词超过20行拼接维护就是噩梦。LangChain.js提供PromptTemplate用模板变量的方式组织提示词// lib/prompts.ts import { PromptTemplate } from langchain/core/prompts; const systemTemplate PromptTemplate.fromTemplate( 你是一个{profession}专家助手。 你的风格要求{style} 请基于以下知识回答问题如果不知道就明确说不知道{context} ); const prompt await systemTemplate.format({ profession: 前端工程化, style: 简洁直接先说结论再给理由, context: 用户的问题是关于构建工具的, });用模板的好处是提示词的结构和业务代码解耦。你调System Prompt时不用动代码逻辑改模板文件就行。更进一步你可以把提示词版本号也写进模板里做A/B测试的时候特别有用。3.3 工具调用让模型操作你的代码工具调用Tool Calling是LangChain.js里最让前端觉得爽的能力。模型本身不执行代码但它能根据用户需求输出一个结构化指令告诉你我需要调用某函数参数是什么。这就是AI Agent的基础。比如你做一个法律问答机器人用户问今年劳动法关于加班费怎么算你可以让模型先调用你的getLegalRegulation()函数拿到真实的法律条文再基于条文回答。这样模型不会凭空编造法条。LangChain.js里定义工具的标准做法// lib/tools.ts import { tool } from langchain/core/tools; import { z } from zod; const getWeather tool( async ({ city }) { // 这里写真实的天气查询逻辑 return 北京的天气是晴温度25度; }, { name: get_weather, description: 查询指定城市的实时天气, schema: z.object({ city: z.string().describe(城市名如北京、上海), }), } );然后绑定到模型上const modelWithTools chatModel.bindTools([getWeather]); const res await modelWithTools.invoke(北京今天天气咋样); // 模型的输出里会带上tool_calls字段里面是函数名和参数的JSON console.log(res.tool_calls);你拿到tool_calls后在自己的代码里执行对应的函数再把结果返回给模型模型才能基于真实数据生成最终回复。第一次跑通这个链路时我是真的有点兴奋——原来让AI动手干活就这么简单。前端开发这不就成了AI应用的话事人吗4. 亲手做一个流式问答应用4.1 API Route里的流式实现理论讲完直接上实战。我做一个最简单的聊天接口前端发来消息数组后端调用模型流式返回前端逐字渲染。首先改造app/api/chat/route.tsimport { chatModel } from /lib/model; export const runtime nodejs; export const dynamic force-dynamic; export async function POST(request: Request) { const { messages } await request.json(); // 通过stream方法拿到模型输出的异步迭代器 const stream await chatModel.stream(messages); // 包装成Web ReadableStream const readableStream new ReadableStream({ async start(controller) { for await (const chunk of stream) { const content chunk.content; if (typeof content string content.length 0) { controller.enqueue(new TextEncoder().encode(content)); } } controller.close(); }, }); return new Response(readableStream, { headers: { Content-Type: text/plain; charsetutf-8, Transfer-Encoding: chunked, }, }); }这里有几个关键点必须说明export const runtime nodejs流式调用依赖Node的IO能力别写成edge。我之前为了省启动时间换过Edge Runtime结果模型流的兼容性一堆问题后来老实退回Node。export const dynamic force-dynamic告诉Next.js这个路由不要做静态优化否则打包时可能被尝试预执行导致报错。chunk.content的类型有可能不是字符串。我用typeof content string做了保护因为某些模型的chunk内容是数组格式多模态。聊天文本场景按字符串处理就够了。TextEncoder把字符串转成Uint8Array是为了符合Web Stream的规范。如果用NextResponse包装流有时反而会因为JSON序列化搞乱格式直接用原生Response最干净。4.2 前端的流式接收与渲染后端返回的是流前端用fetch配合ReadableStream读取。页面组件代码use client; import { useState } from react; export default function ChatPage() { const [input, setInput] useState(); const [answer, setAnswer] useState(); async function handleSend() { setAnswer(); const res await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ messages: [{ role: user, content: input }], }), }); const reader res.body!.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; const text decoder.decode(value, { stream: true }); setAnswer((prev) prev text); } } return ( div textarea value{input} onChange{(e) setInput(e.target.value)} / button onClick{handleSend}发送/button div{answer}/div /div ); }这段代码的核心在reader.read()循环。每读取一段就用TextDecoder解码一次然后增量追加到状态里。注意decoder.decode(value, { stream: true })这个stream: true参数它表示当前数据块可能只是多字节字符的一部分让解码器内部缓存处理否则中文可能偶尔出现乱码。这是流式渲染中文的细节。用setAnswer频繁触发React重渲染性能上有点浪费。如果流式块很小很频繁可以改用ref直接操作DOM或者用useReducer批量更新。我自己的做法是模型流式输出的频率其实没你想象的那么高setAnswer这种方式在普通项目里完全够用先跑通再优化。4.3 三个流式输出的坑写流式问答时我踩过的坑这里直接列出来省得你再趟一遍。坑一部署到Vercel后流式失效浏览器一直转圈直到超时。原因是Vercel的Serverless函数默认有最大执行时间限制免费套餐是10秒且早期的版本对流式响应支持不完善。解决方法是使用Vercel的官方标识export const maxDuration 60把函数执行时间拉长到60秒。如果你是自己买服务器部署限制就不那么紧但也要确认反向代理配置了X-Accel-Buffering: no响应头否则Nginx会先缓冲完再发给浏览器用户感觉就不是流式而是等待后再一次性弹出。坑二Next.js开发模式下热更新时模型实例被重复创建。代码改动后热更新会重新执行模块级代码new ChatOpenAI()每次都重新走一次鉴权握手慢不说有时还会把连接数打满。这个倒不是大问题生产环境不存在但开发时调试接口会莫名慢半拍。我的习惯是在模块里先判断是否已有实例用全局缓存包一层const globalForModel globalThis as unknown as { chatModel?: ChatOpenAI }; export const chatModel globalForModel.chatModel ?? new ChatOpenAI({...}); globalForModel.chatModel chatModel;坑三前端只拿到一半内容就中断。这个一般不是LangChain的问题而是你直接把res.body给了某个JSON解析工具或用了res.json()去解析流式响应。记住流式响应的Content-Type是text/plain或text/event-stream不是application/json前端必须用getReader()循环读取。5. 从Demo到可上线记忆、缓存与成本控制5.1 多轮记忆的轻量实现按第4节的写法每次请求只把当下这一条用户消息传给模型模型是没有上一轮上下文的。你要做一个真正能用的聊天机器人至少得让模型记住最近几轮对话。网上很多教程讲LangChain里的Memory模块但LangChain.js 0.2以后老的BufferMemory、ConversationBufferWindowMemory被逐步移除了官方现在推荐的是LangGraph里的MemorySaver或者干脆自己维护消息列表。我自己用过一段LangGraph觉得是有学习成本的用不上低成本这句话。所以我推荐最务实的方式前端把历史消息数组一直带着后端原样转发给模型。具体做法是在前端维护一个messages数组每次发新消息前先追加用户消息连同之前的所有消息一起POST给后端const messages [ { role: system, content: systemPrompt }, ...history, // 之前的所有问答 { role: user, content: input }, ];后端Route里把这一整包消息直接传给chatModel.stream(messages)就行。模型天然支持多轮对话关键是历史问答带上这个动作。有一点要提醒模型上下文窗口有限无限堆历史会把Token数撑爆。简单的处理是只保留最近10轮对话再老的消息截断掉。或者做个摘要——每20轮对话就请模型把前面的内容浓缩一段放进System Prompt。第二种方式效果好但代码量多项目初期用截断策略就够。5.2 用缓存省掉重复调用AI应用的账单是按Token算的同一个问题被用户刷新两遍你就得付两遍钱。所以我上线前做了一个极简的缓存层用用户消息内容做Key把模型的回复存到内存或Redis里。下次来了一模一样的问题直接命中缓存返回不调用模型。这个缓存的粒度要看场景。聊天类应用问题带点随机性缓存命中率不高但如果你做了AI客服、文档问答这类大量重复问题的场景缓存能省掉一半以上的模型调用钱。实现方式很简单写一个带缓存的Stream调用const cache new Mapstring, string(); export async function getCachedStream(messages: any[]) { const key JSON.stringify(messages); if (cache.has(key)) { return cache.get(key)!; // 以纯文本返回 } const stream await chatModel.stream(messages); // 注意流读一次就没了所以边读边缓存边返回 ... }这里有个小麻烦你返回的是流同一份缓存内容不能被两个客户端同时消费。简单处理是先把模型完整回复缓存成字符串然后需要流式的话就从字符串重新制造一个流。牺牲一点首字延迟换来的是代码简单。5.3 粗算一笔Token账成本估算这事很多前端一开始没概念。我按目前常用的模型来算一笔账方便心里有数。大模型的计费单位是Token不是字数。简单估算经验英文一个单词大约1.3个Token中文一个字大约1到2个Token口语化文字偏高假设用户提一个问题约200字系统提示词约500字历史对话5轮共约2000字加起来一次请求消耗的输入Token大约3000到4000 Token。模型的回复按800字算输出Token约1200到1600。以目前GPT-4o-mini的公开价格为参考输入0.15美元/百万Token输出0.6美元/百万Token一次问答的成本大概是输入: 3500/1000000 * 0.15 ≈ 0.000525美元 输出: 1400/1000000 * 0.6 ≈ 0.00084美元 单次合计 ≈ 0.0014美元 ≈ 1分钱人民币一万次问答就是上百块人民币。如果换成国内更便宜的模型成本能再砍一个量级。说实话个人项目和中小应用的模型成本真没你想的那么可怕真正花钱的是你开发时反复调试、反复调用烧掉的Token。所以调试的时候我习惯用temperature 0并且开日志记录每次调用的Token数避免大模型在测试阶段悄悄烧钱。6. 前端进AI赛道的进一步打算6.1 两个星期足够做什么经常有人问我零基础要学多久才能做AI应用。我的答案很直接如果你已经会Next.js或React两个星期就够了。我的路线建议第一周把LangChain.js文档里的Quick Start过三遍核心就是模型调用、提示词模板、工具调用三件事。不用研究Agent、记忆、Graph这些进阶话题先聚焦一个能回答问题的流式接口。第二周做一个带场景的真实应用。我建议做RAG检索增强生成方向因为它是目前企业需求最大的AI场景。具体来说就是上传一个PDF文档把内容切块存入向量数据库用户提问时先做向量检索把相关片段塞进提示词再让模型基于片段回答。Next.js生态里upstash/vector、vercel/postgres这些包都能快速接上数据库选一个托管服务不用自己运维。两周下来你手里就有两个能上线的项目一个流式聊天助手一个文档问答机器人。这两个项目足够应付大部分AI应用开发岗位的面试了。6.2 作品集和面试怎么讲面试讲AI项目最大的误区是只讲我用了什么。面试官想听的是你怎么定义问题、怎么拆解流程、怎么处理边界。我给你一个思路按这个逻辑讲项目含金量会明显不一样从需求出发用户的核心痛点是什么比如文档太长没人读或客服回复慢方案选型为什么用Next.js全栈而非前后端分离为什么用LangChain.js而非直接调SDK实现路径说清楚你的消息流转全链路从用户输入、到预处理、到模型调用、到工具调用、再回到用户。难点和取舍比如流式输出怎么做的、上下文怎么管理的、Token成本怎么控制的、模型遇到乱答怎么兜底。这个结构不是为了装而是AI应用项目确实容易一跑通就完了你得主动展示你做过工程化思考。面试官只要一听你能讲清楚模型输出不可靠时你的产品怎么兜底立刻就会把你和只会调API的人区分开。6.3 前端做AI项目的独家优势最后说点掏心窝的话。我见过不少后端出身的人写AI应用接口设计、并发处理都很强但最常翻车的是两件事第一聊天界面做得像调试工具用户根本不想用第二流式输出的体验处理不到位前端动不动白屏卡顿。反过来前端做AI项目的优势恰恰体现这些地方咱们天生对这个界面好不好看、交互顺不顺手敏感而且前端工程化的积累能直接迁移到AI应用的前端复杂度管理上。AI应用的用户界面会比传统后台复杂得多——对话流、流式文本、工具调用进度、引用来源展示、流式渲染的性能优化这些都是前端的主场。所以别觉得AI两个字有多高不可攀底层模型是别人训练的算法是别人研究的但最终给到用户手里的产品是我们这个工种造的。这条路现在还在窗口期再过几年各路人员挤进来门槛一定会抬高。趁现在用Next.js和LangChain.js把一个能跑的AI产品做出来比焦虑地刷面试题有用得多。\u003c/en\u003e