阅读视图

发现新文章,点击刷新页面。

为什么我不建议普通前端盲目卷全栈?

Gemini_Generated_Image_s62j8ys62j8ys62j (1).png

周末,一个半年前从我们组离职去当 独立开发者 的小伙子,突然约我出来喝了顿大酒。

半年前他提离职的时候,眼里是有光的。当时他手里拿着一个用 Next.js + Node + MongoDB 拼凑出来的 AI 翻译 SaaS 雏形,满脸兴奋地跟我说,现在有了 AI 辅助,前端搞全栈简直易如反掌,他马上就要去赚美金、做数字游民了😊。

半年后的饭桌上,他头发肉眼可见地稀疏了,整个人透着一股被掏空的疲惫😢。

我问他产品跑得怎么样? 他倒了一堆苦水: 上线第二周,因为忘了配置 MongoDB 的白名单,数据库被黑产端了,留了个比特币勒索地址; 换了云数据库后,上个月被海外羊毛党用并发脚本刷爆了注册接口,由于 Node.js 端没做事务锁和限流,AI 服务的 Token 余额一夜之间被刷欠费了两千多刀。

他长叹一口气:老大,写后端真他妈不是人干的活😖😖😖。

这两年,前端圈有一股极其狂热的风气,大厂在逼着前端转全栈,各类博主在教你怎么用 Cursor 一键生成后端 API,似乎只要会写几句 JS,连上个数据库,你就能凭一己之力抗下整个商业闭环。

但作为一个写了 9 年代码、搞过出海独立站、也无数次给新人擦过屁股的老兵,我今天必须把这层窗户纸捅破: 绝大多数普通前端理解的全栈,根本就是个一戳就破的纸老虎。我不建议你盲目去卷全栈🤷‍♂️。


你以为的全栈,只是在写玩具后端

很多前端对后端的认知,还停留在用 Express 或者 NestJS 写一个 router.get('/api/user'),然后调用一下 ORM 查查数据库。代码能跑通,能返回 JSON,就觉得自己是全栈了。

这是巨大的错觉。

真实的后端工程,最难的从来不是业务逻辑(CRUD),而是高并发下的数据一致性、资源隔离与灾备防御。

举个最经典的例子。很多刚转全栈的前端,在处理 用户消耗积分调用 AI 这个逻辑时,代码往往是这么写的:

// 前端思维写出来的后端代码
app.post('/api/generate', async (req, res) => {
  const user = await User.findById(req.userId);
  
  // 判断余额
  if (user.points < 1) {
    return res.status(403).send('积分不足');
  }
  
  // 扣减积分并保存
  user.points -= 1;
  await user.save();
  
  // 调用 AI 接口...
});

本地单步调试,毫无问题。 但只要把它扔到线上,稍微遇到点网络延迟,或者有黑客同时发来 10 个并发请求。这 10 个请求会同时读到 user.points === 1,然后各自往下执行,最终用户的 1 个积分被成功扣减了 1 次,但你的 AI 接口被免费调用了 10 次。

在真正的后端视野里,这叫竞态条件(Race Condition)。解法是利用数据库层面的原子更新(比如 MongoDB$inc),或者是加分布式锁。

但很多前端根本不懂什么是事务隔离级别,什么是乐观锁悲观锁,什么是慢查询引发的连接池打满。他们拿着一套写 UI 的心智模型去搞后端,最后搭出来的系统,防得住正人君子,防不住任何一次稍微猛烈的流量冲击。


2026 年独立开发者真实生存状况

现在的年轻人动不动就想搞独立 SaaS,觉得有个好点子就能变现。 我带你看一眼 2026 年前端做独立开发者的真实时间线:

Gemini_Generated_Image_xi8phxi8phxi8phx.png

第一周: 激情澎湃,花 5 天时间用 Tailwind CSS 把落地页的动效调得丝滑无比,深色模式完美适配,觉得自己真是个产品天才。

第二周: 开始搭后端环境。在 Docker、Nginx 配置、SSL 证书续签里痛苦挣扎。为了省几十块钱服务器钱,买了个廉价 VPS,每天提心吊胆怕宕机。

第四周: 产品终于上线了。发到 Product HuntV2EX 上,迎来了 500 个独立访客。

第五周: 被俄罗斯或者印度的 Bot 盯上了。恶意脚本疯狂轰炸你的登录接口,你那单节点的 Node.js 进程直接 CPU 飙到 100% OOM 死机。你大半夜爬起来看日志,临时去搜 Node.js 怎么做 IP 频控。

第二个月: 热情耗尽,服务器吃灰,域名到期不续费🤷‍♂️。。。

这才是赤裸裸的真相。 很多前端做独立开发,90% 的精力消耗在了配环境、查后端 Bug、修服务器配置上,真正花在打磨核心产品功能和做营销推广上的时间,连 10% 都不到。

你以为你是产品 CEO,其实你只是个免费的初级兼职运维。


普通前端该怎么破局?学会借力,而不是造轮子

说了这么多,难道前端就只能老老实实切图,彻底告别独立开发和全栈了吗?

错❌❌❌。

我的核心观点是:放弃传统后端的玩法,拥抱 Serverless 和 BaaS(后端即服务)。

2026 年了,前端的护城河绝对不是去学怎么配置 K8s 集群,也不是去死磕如何调优 MySQL 索引。你的核心价值是 极速交付业务逻辑

要做全栈,就把脏活累活全甩给成熟的云基础设施。 比如这两年我在搞出海项目时,几乎抛弃了所有传统的自建 Node 服务器部署 (比如 Render, fly.io),全盘转向了 Cloudflare Workers + D1(Serverless SQLite) 或者 Supabase

不用管服务器运维,不用管 Nginx 负载均衡,自带企业级防 DDOS,把代码推到边缘节点(Edge),全球毫秒级生效。

给你看一眼在 Cloudflare Workers 里,如何用极简的代码实现极其硬核的 IP 频控(Rate Limit),这在传统后端里要搭一套 Redis 才能搞定:

// 基于 Cloudflare 的现代前端全栈玩法
export default {
  async fetch(request, env) {
    const ip = request.headers.get('cf-connecting-ip');
    
    // 调用平台自带的限流服务,一行代码解决防刷问题
    const { success } = await env.RATE_LIMITER.limit({ key: ip });
    if (!success) {
      return new Response('请求过于频繁,请稍后再试', { status: 429 });
    }

    // 处理核心业务逻辑...
    return new Response('业务处理成功');
  }
};

发现了吗?这种工程维度的跨越,才是前端走向全栈的正确姿势。 你不需要去理解底层的流量网关是怎么实现的,你只需要站在巨人的肩膀上,把 API 串起来,把精力留在如何优化用户的产品体验上。


别被技术焦虑绑架

很多技术社区都在制造焦虑,好像你不懂点微服务、不懂点高并发,你就不配做一个现代的前端。

但真实的世界是:没有任何一个商业项目,是因为用了多牛逼的后端架构才成功的;绝大部分死掉的项目,都是因为产品根本没人用,或者在早期就被无意义的基础设施消耗拖垮了团队🤔。

如果你是一个前端,有极强的业务嗅觉,想自己做点东西。 那就用熟你手里的 VueReact,用好 Tailwind 快速构建 UI,把后端托管给 Supabase 或者 Firebase,把边缘逻辑交给 Cloudflare。用两周时间把 MVP(最小可行性产品)跑通,直接推向市场验证🫡。

不要去盲目卷传统后端。 把时间留给产品,留给用户,留给真正的商业闭环。这才是 2026 年,一个有独立思考能力的前端老兵,最该具备的技术品味。

你们说是不是?😊

Suggestion (2).gif

别再用 JSON.parse 深拷贝了,聊聊 StructuredClone

临近下班,我们业务线出了一个极度无语的线上 Bug。

产品侧反馈,在一个非常核心的财务表单里,用户明明选择了 2026-04-14 作为结算日期,但点击提交后,整个页面直接白屏崩溃。

我打开错误监控看了一眼日志,立刻就把组里那个刚入职不久的小伙子叫了过来。 原因极其经典:他在把表单的原始状态同步给历史快照时,为了图省事,顺手写了一段几乎所有前端都写过的代码:

// 模拟用户表单数据
const formData = {
  amount: 1000,
  date: new Date("2026-04-14"), // 用户选的结算日期(Date对象)
};

// 深拷贝
const snapshot = JSON.parse(JSON.stringify(formData));

console.log("原始:", formData.date, typeof formData.date); 
// Date object

console.log("快照:", snapshot.date, typeof snapshot.date); 
// "2026-04-14T00:00:00.000Z" string

// 后续业务代码
function calcSettlementTime(data) {
  // 这里默认 date 是 Date 对象
  return data.date.getTime();
}

// 页面直接崩溃😢
try {
  const time = calcSettlementTime(snapshot);
  console.log("时间戳:", time);
} catch (err) {
  console.error("页面崩溃:", err);
}

他满脸委屈:老大,大家平时深拷贝不都是这么写的吗?🤷‍♂️

我让他自己把这段代码在控制台跑一遍。 当他看到表单里原本好好的 Date 对象,经过这一进一出,硬生生变成了一串 ISO 格式的字符串,导致后面调用 snapshot.date.getTime() 直接抛出 TypeError 时,他自己也沉默了。

作为前端老油条,这种因为 JSON.parse(JSON.stringify()) 引发的血案,我见过太多了。 它不仅会把 Date 变成字符串,还会把 MapSet 变成空对象 {},会把 undefinedSymbol 以及函数直接活生生抹除,更别提遇到循环引用时,它会当场抛出异常让你的主线程直接崩溃。

以前,我们为了解决这个破事,不得不在每个项目里老老实实 npm install lodash,然后引入那个笨重的 cloneDeep

但现在是 2026 年了。浏览器早就原生内置了完美的终极解药——structuredClone

今天咱们不聊虚的架构,就花三分钟,把这个原生 API 的底层逻辑讲清楚。


它是怎么解决历史遗留问题的?

structuredClone 不是什么语法糖,它是浏览器底层暴露出来的 结构化克隆算法(Structured Clone Algorithm)。这就意味着,它在 C++ 引擎层面的处理逻辑,远比 JS 业务层面的递归拷贝要深得多。

看一下原生 API 的用法:

const original = {
  date: new Date(),
  set: new Set([1, 2, 3]),
  map: new Map([['key', 'value']]),
  regex: /hello/i,
  buffer: new Uint8Array([1, 2, 3]).buffer,
};

// 制造一个循环引用
original.self = original;

// 一行代码,原生搞定
const cloned = structuredClone(original);

console.log(cloned.date instanceof Date); // true
console.log(cloned.set instanceof Set);   // true
console.log(cloned.self === cloned);      // true 完美处理循环引用!

发现没有?它不仅完美保留了所有的内置对象类型,连 JSON.parse 绝对搞不定的循环引用,它都处理得游刃有余。由于是在引擎底层运行,不需要像 Lodash 那样在 JS 运行时里疯狂压栈递归,它的执行效率在大部分复杂场景下都具有压倒性优势👍👍👍。


零拷贝转移 (Transferable Objects)

如果你以为 structuredClone 只是为了少引入一个 Lodash,那你就太小看浏览器的底层野心了。

它藏着一个 90% 的前端都不知道的极其硬核的功能:内存转移(Transfer)

在前端处理音视频、WebGL、或者读取几十 MB 的大文件时,我们经常会生成巨大的 ArrayBuffer。如果你用传统的深拷贝,内存瞬间翻倍,几十兆的内存分配极容易引起页面的掉帧卡顿。

structuredClone 提供了一个极其变态的第二个参数配置:{ transfer }

// 假设这是一个极大的 50MB 数据内存块
const u8Array = new Uint8Array(1024 * 1024 * 50);
const hugeBuffer = u8Array.buffer;

// 传统的深拷贝:内存翻倍,耗时极长
// const badCopy = lodash.cloneDeep(hugeBuffer); 

// 直接内存转移
const fastClone = structuredClone(hugeBuffer, { transfer: [hugeBuffer] });

console.log(fastClone.byteLength); // 52428800 (50MB 完美转移)
console.log(hugeBuffer.byteLength); // 0 (原对象的内存地址被转移)

这段代码的核心在于:它压根没有复制数据。 它直接在内存层面,把这块 50MB 数据的所有权,从 hugeBuffer 强行转移给了 fastClone。原对象被彻底掏空(变成了 detached 状态)。

这种零拷贝机制,在结合 Web Worker 处理复杂后台计算时,是打破性能瓶颈的绝对神器。这是任何第三方 JS 库都做不到的底层API。


一些坑要讲清楚🤔

既然这么牛,是不是以后项目里所有的拷贝闭着眼睛用它就行了? 作为一个踩过无数坑的老兵,我必须点出它的几个致命死角。如果你在真实的业务架构里滥用,下场比用 JSON.parse 还要惨。

对于函数和 DOM 节点的处理

JSON.parse 遇到函数,它会默默地忽略掉,至少不报错。 但 structuredClone 很直接。只要你的对象树里藏着一个方法,或者藏着一个 DOM 节点的引用,它会直接给你抛出 DataCloneError

const objWithFunc = {
  data: 123,
  onClick: () => console.log('click')
};

// 只要带有函数,直接抛同步错误
// DOMException: () => console.log('click') could not be cloned.
const copy = structuredClone(objWithFunc); 

这就意味着,如果你要拷贝的是一个 Vue/React 的响应式组件实例,或者是带有业务方法的数据模型,绝对不能用它👋。

原型链的断裂

不管你原本是一个通过 class 实例化的多么高级的业务对象,经过 structuredClone 的洗礼后,它都会变成一个普通的纯对象(Plain Object)。

class User {
  constructor(name) { this.name = name; }
  sayHi() { console.log('hi'); }
}

const user = new User('前端');
const cloneUser = structuredClone(user);

console.log(cloneUser instanceof User); // false 
cloneUser.sayHi(); // TypeError: cloneUser.sayHi is not a function

原型链上的所有方法全部丢失。它只关心纯粹的数据,不关心你的面向对象架构。‘


需要时收藏起来⭐⭐⭐

这几年,前端的工具链卷得飞起,大家的 package.json 越来越臃肿。遇到数组去重找库,遇到时间格式化找库,遇到深拷贝也要找库。

如果你只是单纯地处理一些后端传过来的嵌套数据,或者表单的复杂配置结构,完全可以直接把 structuredClone 敲在你的代码里。不用担心兼容性,目前主流浏览器(包括 Node.js)的支持率早就达到了工业级使用的标准了。

image.png

下次 Code Review 时,别再让我看到满屏的 JSON.parse 了 (玩笑😁😁😁)。

分享完毕,谢谢大家🙌

Suggestion.gif

做中后台业务,为什么我不建议你用 Tailwind CSS?

1___f27S-qQF2CAASt5bOwqg.png

大家好,我又来了😁

最近我接手了一个隔壁组转过来的中后台重构项目。

交接的时候,对方的技术负责人特意跟我强调,说这个项目采用了最新的技术栈,全面拥抱了 Tailwind CSS,开发体验极其丝滑。

我当时心里还挺期待,毕竟这两年 Tailwind 的风刮得太大了,各种国内外大佬都在疯狂带货。结果周末我抽空把代码拉下来,打开 VSCode 准备梳理一下业务主流程。盯了屏幕不到十分钟,我感觉自己的眼睛快瞎了。

光说理论没意思,我直接给你们截取一段真实的、承载了各种表单校验和状态联动的业务侧边栏组件。你们自己品鉴一下所谓的极致开发体验👇:

const OrderCard = ({ order, isAdmin, isExpanded }) => {
  return (
    <div 
      className={`flex flex-col w-full p-5 mb-4 border rounded-lg shadow-sm transition-all duration-300 ${
        isAdmin ? 'bg-red-50 border-red-200' : 'bg-white border-gray-200'
      } ${
        isExpanded ? 'max-h-[800px]' : 'max-h-24 overflow-hidden'
      } hover:shadow-md cursor-pointer`}
    >
      <div className='flex items-center justify-between pb-3 mb-3 border-b border-gray-100'>
        <span className='text-sm font-semibold text-gray-800 truncate w-[60%]'>
          {order.id}
        </span>
        <span 
          className={`px-2 py-1 text-xs rounded-full ${
            order.status === 'PAID' 
              ? 'bg-green-100 text-green-700' 
              : 'bg-orange-100 text-orange-700'
          }`}
        >
          {order.statusText}
        </span>
      </div>
      {/* 内部极其复杂的业务字段渲染... */}
    </div>
  );
}

代码跑得通吗?当然跑得通。UI 还原度高吗?也挺高。 但是作为接下来要维护这个项目的组长,我只觉得一阵头皮发麻。

很多前端新人,甚至是做惯了 C 端 独立开发的兄弟,对 Tailwind 简直是顶礼膜拜。因为它不用取名字,不用在 JSCSS 文件之间来回切换,写起来确实有快感。

但作为趟过无数中后台项目的深水区,我今天必须给这股跟风热潮泼一盆冷水: 在绝大多数重型中后台业务场景里,Tailwind CSS 并不是什么神兵利器,而是给后期维护带来不方便。

来,咱们拿真实代码说话,看看它到底是怎么摧毁中后台工程的👇。


彻底掩盖你的业务主线?

做中后台系统,最难的从来不是画 UI,而是处理极度复杂的数据状态流转。

上面那段代码最大的问题在于信噪比极低。作为一个接手代码的前端,我点开这个文件,首先想看的是:这个组件在不同权限、不同展开状态下,业务逻辑是怎么走的?

但在 Tailwind 的体系下,我的视线全被 flexp-5mb-4max-h-[800px] 这种毫无业务价值的视觉原子类给强暴了。如果退回到老古董的 CSS Modules 方案,这段代码在 JS 侧应该长什么样?咱们对比一下:

import classNames from 'classnames';
import styles from './OrderCard.module.less';

const OrderCard = ({ order, isAdmin, isExpanded }) => {
  return (
    <div 
      className={classNames(styles.orderCard, {[styles.adminMode]: isAdmin,
        [styles.expanded]: isExpanded
      })}
    >
      <div className={styles.cardHeader}>
        <span className={styles.orderId}>{order.id}</span>
        <span 
          className={classNames(styles.statusBadge, {
            [styles.statusPaid]: order.status === 'PAID',
            [styles.statusPending]: order.status === 'PENDING'
          })}
        >
          {order.statusText}
        </span>
      </div>
      {/* 业务字段一目了然 */}
    </div>
  );
}

发现区别了吗?重构后的 JSX 变得极其纯粹。我只通过 adminModeexpanded 这种类名,就极其清晰地传达了业务语义。至于那个订单编号到底占百分之几十的宽度,那是 UI 层 该关心的事情,它安静地待在 .less 文件里,绝不会来污染我的业务主逻辑。


跟现成的组件库水土不服

中后台业务是不可能脱离 Ant DesignElement Plus 这类重型组件库的。而组件库的本质,是封装好了一整套内部的 DOM 结构和 ClassName 规范。

这就带来了一个极其致命的冲突:当你用 Tailwind 去覆盖 Ant Design 的内部样式时,你会写出极其恶心的 Hack 代码😖。

比如产品经理要求:在这个特定页面里,把 Ant Design 表格的表头背景色改成浅蓝色,单元格的 padding 改小一点。

正常的 Less 做法是用样式穿透,精确打击:

/* 样式文件覆盖 */
.myCustomTable {
  :deep(.ant-table-thead > tr > th) {
    background-color: #f0f8ff;
    padding: 8px 16px;
  }
}

你知道那个拥抱 Tailwind 的小伙子是怎么写的吗?为了不写 CSS 文件,他强行使用了 Tailwind v3+ 的任意变体语法:

// Tailwind 强行覆盖组件库内部样式
<Table 
  className='[&_.ant-table-thead>tr>th]:bg-blue-50[&_.ant-table-thead>tr>th]:py-2 [&_.ant-table-thead>tr>th]:px-4 [&_.ant-table-tbody>tr>td]:text-gray-600[&_.ant-table-tbody>tr>td]:py-2' 
  dataSource={data} 
  columns={columns} 
/>

这段代码合进主分支的时候,我都替后续维护的兄弟感到悲哀😢。

这玩意儿连换行都没有,密密麻麻挤在一起。未来如果要做主题切换,或者升级 Ant Design 导致内部类名变了,谁敢去动这坨连正则都极难匹配的字符串?

不仅没有提高开发效率,反而为了强行凑 Tailwind 的语法,写出了一堆极难维护的代码。


负边距引发的问题

一行代码被写出来的成本,远远低于它在未来三年里被维护的成本。

Tailwind 本质上就是披着 ClassName 外衣的行内样式。它把所有的样式固化在了 HTML 结构上。

设想真实的维护场景,前任开发为了让一个按钮和旁边的输入框对齐,极其随意地写了一个向左偏移负间距的类名:

<Button className='-ml-2 mt-1'>提交</Button>

半年后你接手这个需求,产品要求在这俩元素中间加一个 Icon。你看着这个 -ml-2mt-1 会陷入极其痛苦的挣扎:他当时为什么要写负边距?是因为外层父元素加了错的 padding 导致的?还是为了抵消 Button 内部自带的 margin?🤷‍♂️

在传统 CSS 中,我们往往会留有注释说明抵消输入框自带的右侧留白。但在 Tailwind 里,没有注释的容身之所。

为了保证不出线上 Bug,你绝对不敢删掉那个 -ml-2。你会选择在它后面再打个补丁,加个 pl-4 试图把它顶回来。 第二年,另一个接手的同事遇到了错位,又在后面补了一个 mt-[-5px]🤣🤣🤣。

日积月累,HTML 标签上的类名越来越长,死代码和冲突代码全堆在 DOM 上,最终变成一座没人敢碰的屎山💩。


别瞎搞,先认清你的场景🫡

说了这么多,难道 Tailwind 真的就是垃圾吗?当然绝对不是。

如果你在做偏 C端 的炫酷落地页、做 SaaS 官网,或者你是独立开发者,没有沉重的历史包袱,不需要配合复杂的重型组件库,那 Tailwind 绝对是神作。它自带极其优秀的设计规范,能让你极速堆出好看的界面。

但咱们讨论的是中后台。中后台是干嘛的?团队十几个人来回交接,动辄几百个页面,充斥着极其复杂的表单联动和权限校验,生命周期长达五年甚至十年。

在这种重型项目中,保持业务逻辑的纯粹性,分离关注点,远比你少写几行 CSS 要重要一万倍。

好了,今天分享到这,谢谢大家😁

谢谢大家.gif

❌