FDE·技术基础
基础目录
速查版 · 一屏看懂
①请求旅程 ②读懂monorepo ③数据库存东西 ④并发锁事务 ⑤前端为什么能跑 ⑥读懂AI代码
深入版 · 原理到能排障
①请求旅程 ②monorepo ③数据库 ④并发事务 ⑤前端渲染 ⑥读懂AI代码

基础 18 · 前端为什么能跑起来

速查版:一屏看懂前端到底在干什么、浏览器拿到文件之后经历了什么、React 凭什么能把页面画出来,以及最关键的一件事——为什么你的主站爬虫只拿到 3120 字节空壳,而 /fde/ 静态页能被爬到。

项目锚点

这个对比是你全项目最值钱的一条前端事实:
主站 memberhandbook.cn:纯 React 单页应用(React 19 + TS + Vite + Tailwind),84 个 URL 给爬虫只返回 3120 字节空壳 HTML——内容全在 <div id="root"></div> 这个空壳里,靠 JS 执行后才有。
你自己这个 /fde/kb/ 知识库:纯静态 HTML,不依赖任何 JS 就有正文,所以能被爬到、能被收录。

同一个域名、同一个 nginx、同一套服务器,收录结果天差地别。差别不在 SEO 技巧,只在架构。这一篇就讲清这个差别是怎么来的。

顺便一个已经验证的正面案例:geo.memberhandbook.cn 和 /xiaosenlin/ 用「预渲染 + llms.txt」已经成功被收录,证明这条路在你的服务器上是走得通的。

01

一句话:前端在干什么

前端就是「发一堆文件给浏览器,让它自己把页面画出来」。

没有「前端」这回事的时候,服务器给浏览器的是已经画好的成品——一张报纸。现在不是了:服务器给的是图纸(HTML 骨架 + CSS 样式 + JS 指令),浏览器在你这台机器上把图纸变成实物。

这个变化的代价
好处
改一个数字不用重发整页,页面切换几乎瞬间。用户点导航时,不重新下载 HTML,只换一块内容。
这个变化的代价
坏处
内容要等 JS 执行完才出现。所以它天生对爬虫不友好——爬虫拿到的是图纸,不是实物。这就是 3120 字节空壳的来源。
所以「前端框架好不好」这个问题问错了。该问的是:我要的东西(内容/收录/首屏)必须在 JS 之前还是在 JS 之后?必须之前的,就该用静态页或预渲染;必须之后的(比如后台系统要交互),才用 SPA。
02

浏览器五步曲(打开一个网页实际发生的事)

浏览器拿到 index.html 之后,不是「显示出来」这一下,而是五个阶段。每个阶段都在干一件事。

第 1 步
解析 HTML → 得到一棵 DOM 树(骨架)
浏览器把 HTML 从第一行读到最后一行,每读到一个标签就往内存里建一个节点,最后得到一棵树(DOM,全称 Document Object Model,文档对象模型)。这棵树是骨架,此刻还没有任何样式、也没有任何内容。
大白话:拿到一张「只有骨架、没有装修和家具的毛坯房图纸」。
第 2 步
解析 CSS → 得到样式(穿什么衣服)
CSS 文件可能是另一个请求(也可能内联在 HTML 里)。浏览器把它也读成树,然后给骨架上每个节点套上样式:颜色、字号、间距、宽高。这一步还只是「算出来」,没画到屏幕上。
大白话:量好了每件衣服,但还没穿上身。
第 3 步
遇到 <script> → 停下来,下载并执行 JS
这是最关键的一步。浏览器读到 <script src="..."> 时,会暂停对 HTML 的继续解析,去下载那个 JS 文件,下载完立刻执行。JS 执行完,才继续往下解析 HTML。
⚠️ 这就是「JS 为什么会卡住页面」的根源。
第 4 步
JS 执行 → 往空壳里塞内容、改 DOM
React 干的就是这一步。主站的 <div id="root"></div> 在 HTML 里是空的,React 这个 JS 执行之后,才一句一句把标题、段落、按钮创建出来、塞进那个空壳里。这套东西叫 DOM 操作。
第 5 步
布局 + 绘制 → 你终于看到页面
浏览器把「算好的样式」真正落到屏幕上:先算每个元素该占多宽多高(这一步叫 Layout / 布局),再一个像素一个像素画出来(这一步叫 Paint / 绘制)。只有走完这一步,屏幕上才有东西。
关键点:第 1 步到第 4 步之间有一个「白屏期」。骨架有了(所以不会完全没有页面),但内容要等 JS 下载+ 执行完才出现。你在主站看到的那一闪,就是这个白屏期。而爬虫如果在这一步就放弃了,它拿到的就是骨架——3120 字节的空壳 HTML。
为什么 /fde/ 没有这个问题?因为它的正文就写在 HTML 里。第 1 步就已经有内容了,不依赖第 3、4 步的 JS。这就是「静态页 vs 单页应用」最实际的差别。
03

React 到底在干什么(大白话)

你只管说页面长什么样,我负责同步到屏幕上。

这是 React 最准的一句话翻译。它的核心承诺是:你不用手动去改那个空壳,你只描述「现在页面应该是什么样子」,状态一变,它自己把屏幕同步过来。

三个必须认识的词

词大白话在 /fde/ 这个知识库里对应什么
组件
Component
可复用的积木。一块积木是一个组件,页面是很多积木拼起来的。积木之间可以互相嵌套每个知识页里的 .card 卡片、每个 .sec-head 标题条,都可以看成一个「组件」在复用
状态
State
会变的数据。「现在订单是空的」「用户已登录」「弹窗开着」——这些都是状态页面上「已读/未读」的标记、当前展开的章节,都是状态
渲染
Render
状态变了 → 自动把用到这块状态的地方重画一遍。你不用通知它你点开一章,那一章的正文从折叠变成展开——不是你手动画的,是状态变了自己重画的

一个最小 React 例子

// 这就是 React 的全部核心思想
import { useState } from 'react'

function Counter() {
  // 状态:初始值 0
  const [n, setN] = useState(0)

  return (
    <button onClick={() => setN(n + 1)}>
      点了 {n} 次
    </button>
  )
}

这一段里最值钱的是「状态变了会自动重画」这件事:你调 setN(n+1) 之后,没有一行代码手动去改按钮上的文字——但屏幕上就是从「点了 0 次」变成了「点了 1 次」。

为什么这件事值钱?因为手动改界面是前端 bug 的最大来源:改了 A 处忘了改 B 处、页面状态和数据对不上。React 把「改界面」这件事收走了,你只剩一件事:改状态。数据只有一份、界面跟着数据走,就不可能不一致。
红线:组件里如果直接读一个会变的外部变量、绕开状态直接改 DOM,React 这套保证就失效了。你项目里如果见到有人手写 document.getElementById(...).innerHTML去改React 管的区域,那就是两套系统在抢同一块屏幕——出 bug 的时候你会怀疑人生。
04

CSR vs SSR vs 预渲染 vs SSG(这一页的核心对比)

这四个词被混用得厉害,但它们的差别只在一件事上:HTML 是什么时候被变出来的。

方式什么时候生成内容爬虫能不能读到正文你项目里的例子
CSR
纯客户端渲染
浏览器执行 JS 之后才生成❌ 读不到(除非它肯等 JS)主站 React SPA——84 个 URL 给爬虫只返回 3120 字节空壳
SSR
服务端渲染
服务器每次收到请求时现场生成✅ 能你项目里没有纯 SSR 的服务(这是事实,也说清它为什么不是你现在的选择)
预渲染构建时就生成好静态 HTML,服务器只递文件✅ 能你已经用预渲染让 geo.memberhandbook.cn 和 /xiaosenlin/ 成功收录了
SSG
静态生成
构建时生成,每个 URL 一个真实 HTML 文件✅ 能/fde/ 就是这个——你打开这个页面时,正文已经在文件里了
怎么一眼分清这四个:SSR 和 SSG/预渲染都是「服务器给的是成品」,区别只是成品是每次现做,还是提前做好。CSR 是「服务器给的是图纸,浏览器自己造」。所以 SSR/SSG/预渲染对爬虫都友好,只有 CSR 不友好。
SSR 的隐藏成本:SSR 每次请求都要现算,会占服务器 CPU 和内存,而且要多一套 Node 进程。你的内容本来就不需要「每次都不一样」——知识库页面、工具页、产品页都是固定的。内容固定却付出 SSR 的代价,是不划算的;预渲染/SSG 拿到了 SSR 的全部好处(爬虫能读),却没有它的代价。
05

为什么 SPA 会被爬虫「漏掉」(讲因果,不讲技巧)

这不是「SEO 技巧没做好」,而是架构选择必然带来的结果。因果链只有三步:

原因 1
爬虫很多不执行 JS,或者不执行到底
早期爬虫只读 HTML 文本。有些 AI 爬虫会把JS 跑一下,但不保证等它跑完,也不保证它能正确执行现代打包后的代码。它们的行为不是你能控制的。
原因 2
不执行 JS,就只有那个 3120 字节的空壳
爬虫拿到的是 <div id="root"></div> ——正文一个字都没有。它看到的是「这个页面上没有内容」,于是判定这个页面没有价值,收录就差。
原因 3
收录差 → 没有排名 → 更没人来
收录差的第一层损失是页面根本没进搜索结果。第二层损失更隐蔽:有些爬虫会据此判断你整站质量低,连你的其他页面也不一起收录。
所以这件事没有「纯技术上的优化技巧」可以绕过。你不能靠调 meta、改 title、加 keywords 来解决——那些都在空壳里,爬虫本来就能读到。真正能解决的只有一件事:把内容从 JS 里搬到 HTML 里。
还有一个架构层面的原因(在深入版展开):因为主站是客户端路由,磁盘上真的只有那一个 index.html,所以 nginx 必须靠 try_files $uri $uri/ /index.html 兜底——它不是不知道你在哪个路由,它是把同一个文件递给了所有 84 个 URL。
你已经验证过两条路可行:①预渲染——构建时用无头浏览器把每个 URL 跑一遍,把结果抓下来存成 HTML(geo.memberhandbook.cn 和 /xiaosenlin/ 就是这么成功收录的);② 直接写静态页——就像 /fde/ 这样。两条的本质是一样的:让爬虫一拿到文件就有正文。
还有一个更狠的对照:LumenX 前端是 Next.js 静态导出,部署在 lumenx.memberhandbook.cn,但整站被 Basic Auth 挡住(401)。静态导出做得再对也没用——爬虫拿到的是登录页,不是正文。这就是为什么必须用真实浏览器 UA 去实测字节数和内容采样,返回 200 完全不等于内容对。
06

本地搜索 fuse.js 是干什么的

前端搜索 = 把所有内容下载到浏览器,然后在本地查。不问服务器。

你 2026-07-05-task-72/geo-learning 用的就是这条路:React 19 + Vite 8 + Tailwind 4 + fuse.js + react-markdown + oxlint。fuse.js 是一个「模糊搜索」库——用户搜「洗浴」也能命中「淋浴」,因为它算的是相似程度而不是精确相等。

做法数据在哪搜索在哪算适合
前端搜索
(fuse.js)
打包进JS,全部下载到用户浏览器浏览器本地,输入即出结果,零等待几千条以下。你这个知识库、工具箱完全够用
后端搜索在服务器数据库里发请求到服务器,等返回上万条,或者内容不能公开(比如订单、客户资料)
为什么 geo-learning 用 fuse.js 是对的?因为它要搜的内容(几十到几百篇知识文档)本来就是公开的静态内容。全量下载一次之后每个用户都能瞬间搜,不需要服务器、不会因为搜索把服务器打挂、离线也能用。
什么时候必须换后端搜索?两个信号:①条目上到万级(全量打包会让 JS 变大、首屏变慢);②内容不该全量给客户端(比如会员手机号、订单金额——打进去就等于泄露)。你的 FDE 工具箱里有 RFM 分析这类涉及客户数据的,就属于第二种:数据必须在服务器,浏览器只拿到算完的结果。
07

Tailwind 为什么你 15 个项目都在用

不写一堆自定义 CSS,直接在 HTML 里写类名(class="flex items-center"),样式由框架按需生成。

你的真实分布是:React 85 处、Vue 74 处、Vite 8(多版本)、Tailwind 3 和 4 都有——Tailwind 在你 15 个项目里都是主力样式方案。这不是个人偏好,是它对「一个人做很多项目」这个场景特别合适。

好处

好处具体是什么意思
改样式不用翻 CSS 文件颜色、间距、字号就写在用到它的地方。想改成圆角,搜 rounded 就找到全部了。不存在「这个类在哪定义的」这种问题
不可能有类名冲突普通 CSS 里你自己起 .header,别人起一个 .header,两个就撞了、样式互相覆盖。Tailwind 的类名自带约束语义(px-4 就是「上下左右各 1rem内边距」),两个同名类含义必然一样,不会打架
删组件能删干净用普通 CSS 时,删掉一个组件,它的样式还留在文件里(没人敢删,怕别处在用)。Tailwind 的样式跟着组件一起被删走
不用维护设计系统一个人做十几个项目,没时间先建一套设计规范再开工。Tailwind 相当于「先用起来,规范以后再说」

代价(必须承认)

代价说明
类名长class="flex items-center justify-between px-4 py-2 text-sm text-slate-700" —— 一行里塞了七八个类名,视觉上是「噪音」
初学者不直观新同学问「这个 flex 是啥意思」,得答「它在传统 CSS 里叫 display:flex」。门槛比手写 CSS 高
没有语义化类名HTML 里全是 div + 一堆工具类名,从标签上看不出这块是导航还是正文。所以你的知识库才需要 .card .hero .note 这类自定义类来补语义
结论:Tailwind 不是「更高级的 CSS」,是一种取舍——用「类名不好看」换「改样式不用翻文件 + 不冲突 + 能删干净」。对一个人维护十几个项目,这个交换是划算的;对一个要交接给团队、要严格视觉规范的场景,可能不划算。
08

常见坑 / 红线

坑 1 · 把关键内容只放在 JS 里

这是你主站 84 个 URL 只返回 3120 字节空壳的直接原因。判断方法很简单:把 JS 关掉,看这个页面还有没有内容?没有内容,爬虫就一定读不到。所有要对外被读到的文字,都必须在 HTML 里。

坑 2 · 图片和字体没设尺寸,页面加载时跳一下

浏览器读到图片时还不知道它多宽多高,就先按「不知道」布局,等图片下载完才撑开——页面会突然往下移一截。这叫 CLS(布局偏移)。你在 /fde/ 页里嵌的摩恩广告视频就是典型:不写 width/height,它加载时页面就跳。对策很简单:所有 <img>、<video> 都写死宽高或写比例占位。

坑 3 · 不做代码分割,首屏慢

一个把所有页面打成一个巨大 JS 的包,用户为了看第一屏,要先下载整个大文件。代码分割就是「把不常用的拆出去,用到才下载」。反过来也提醒一句:分割不能拆太碎,几十个碎片来回请求反而更慢。

坑 4 · 把 CSS 打进 JS,顺序错了样式全乱

有些项目为了减少请求数,把 CSS 塞进 JS 里。如果 JS 加载失败或者被延后执行,样式就来不及生效——页面会短暂显示成「完全没样式」的样子。关键样式不要依赖 JS 执行才生效。

红线 · 以为 200 就等于内容对

这是你交过学费的一条。返回 200 只说明「请求成功了」,不代表返回的是正文。三个真实的例子:①主站 200 + 3120 字节空壳;②LumenX 200 但整站 Basic Auth(401),爬虫拿到登录页;③预渲染 如果抓早了,抓到的也是空 HTML。所以验收必须做两件事:看字节数 + 采样正文里有没有你写的那些字。

✓

自查清单

  • ✓我能背出浏览器五步曲:解析 HTML → 解析 CSS → 执行 JS → 改 DOM → 布局绘制
  • ✓我知道白屏期在「JS 执行完之前」,这就是主站那一闪的原因
  • ✓我能用大白话解释 React:你只管说页面长什么样,我负责同步到屏幕上
  • ✓我认得组件 / 状态 / 渲染这三个词,并知道「状态变了自动重画」是最值钱的一条
  • ✓我能背出 CSR / SSR / 预渲染 / SSG 的对比表,并知道差别只在「什么时候把 HTML 变出来」
  • ✓我知道主站 84 个 URL 只返回 3120 字节空壳是架构后果,不是 SEO 技巧问题
  • ✓我知道预渲染已经在 geo.memberhandbook.cn 和 /xiaosenlin/ 上验证成功
  • ✓我知道本地搜索(fuse.js)适合几千条以下,上万条或涉及敏感数据必须换后端
  • ✓我能说出 Tailwind 的两个好处(不用翻 CSS、不会冲突)和两个代价(类名长、不直观)
  • ✓我知道200 不等于内容对,验收必须「看字节数 + 采样正文」
面试怎么答

题目:你项目的前端为什么爬虫收录不好?你是怎么解决的?

根因是架构,不是技巧。我的主站 memberhandbook.cn 是纯 React 单页应用,84 个 URL 给爬虫返回的都只是 3120 字节的空壳 HTML——所有正文都要等 JS 执行后才被创建到 <div id="root"> 里。很多爬虫(尤其部分 AI 爬虫)不执行 JS,或者不执行完就抓,抓到的就是没有内容的壳,收录自然差。这是选 React 单页架构必然带来的结果,不是 SEO 技巧能绕过的。

我做了三层对比验证:①主站 React SPA——84 URL / 3120 字节空壳,收录差;②预渲染的子站——geo.memberhandbook.cn 和 /xiaosenlin/,用构建时无头浏览器跑一遍把每个 URL 的结果抓下来存成 HTML,再配 llms.txt,已经成功被收录;③纯静态页——/fde/ 这个知识库,正文直接写在 HTML 里,不依赖任何JS,所以能被爬到。同一个域名、同一套 nginx,结果天差地别,说明差别只在架构。

方案上我选了预渲染/SSG,而不是 SSR。因为我的内容是固定的(工具页、产品页、知识页),不需要每次请求现算。预渲染拿到了 SSR 的全部好处(爬虫能读正文),却没有 SSR 的代价(每次请求占服务器 CPU、要多一套 Node 进程)。本质只有一个:把内容从 JS 里搬到 HTML 里。

我踩过的坑我也说清楚:预渲染不是抓下来就成功。有两个坑——一是JS 里读 URL 参数时可能还没渲染完,抓早了存下来还是空壳;二是抓完必须校验字节数下限,别以为抓到文件就等于抓到正文。所以我的验收标准是看字节数 + 采样正文里有没有那些关键文字,而不是只看返回 200。LumenX 就是反面例子:Next.js 静态导出做得再对,整站被 Basic Auth 挡住(401),爬虫拿到的是登录页。

另外我也清楚 SPA 的代价不只体现在收录上:首屏有一个白屏期,因为要等 JS 下载执行完才出内容;图片和视频不写宽高会造成布局跳(CLS)——/fde/ 里的摩恩广告视频就必须写 width/height。这些都不是玄学,是渲染流水线决定的。所以我的原则是:内容/收录/首屏必须在 JS 之前的,用静态或预渲染;必须靠交互才出来的(后台系统),才用单页应用。

🔗 想看原理细节

上面是速查版,只讲「是什么、怎么用」。DOM 到底是什么、渲染流水线五阶段、JS 阻塞与 defer/async、客户端路由为什么让 84 个 URL 其实是同一个 index.html、预渲染的三个坑、虚拟 DOM + diff、状态该放哪里、Vite 构建做了什么、Lighthouse 怎么排查都在深入版:

深入版 18 · 前端渲染深入 →