← 学习路线

第 2 篇 · 让页面被看见 · 第 4 课

为什么搜索引擎看不到你的网站

浏览器里看得到的内容,为什么搜索引擎和分享卡片看不到?做一个要被人搜到的网站,该怎么选?

课型:权衡·约 25 分钟·7 个术语

先看出事的那一刻

2026 年 9 月,这个网站已经有 25 篇文章,还挂着一个几十页的自动驾驶知识库。在浏览器里打开,一切正常:页面好看,切换顺滑。

直到有人换了一种方式去"看"它:不用浏览器,而是在终端里用 curl 请求一篇文章。返回的 HTML 里只有一个标题 Felix Xiao,和一个空盒子 <div id="root"></div>。正文一个字都没有。

这节课要回答的是:浏览器里看得到的内容,为什么搜索引擎和分享卡片看不到?做一个要被人搜到的网站,该怎么选?

装备几个词

为什么要懂

AI 替你做了

一句“帮我做个博客”,AI 就能搭好项目、写好页面、配好部署,页面在浏览器里完美运行。

留给你的

判断“这个网站是给谁看的”。只给登录用户用的工具,和要被陌生人搜到的内容站,该用的渲染方式完全不同。这个问题 AI 不会主动问你。

不懂的代价

文章写了几个月,搜索引擎一篇都没收录,分享出去的链接只有网址没有摘要。等发现时,往往要换一套渲染方式重做。

同一张网页,三种送法

把网页想成一道菜:服务器是厨房,浏览器是在家等着吃的人。

  • 单页应用像寄一个料理包加一张菜谱:服务器送来一张几乎空白的 HTML 和一大包 JavaScript,浏览器照着菜谱现做,才有了你看到的页面。
  • 静态生成像中央厨房:上线前就把每道菜做好装盒,谁点就直接递过去。
  • 服务端渲染像点单后现炒:每来一个请求,服务器就现场把这一页做出来。

浏览器会开火,所以在你眼里,三种送法没有区别。区别出现在那些不开火的访客身上:搜索引擎的爬虫、社交平台生成分享卡片的程序、越来越多的 AI 爬虫。它们大多只拆开第一次收到的那个包看一眼。

单页应用 静态生成 服务端渲染
第一次返回的 HTML 几乎空白 完整正文 完整正文
页面在什么时候生成 每次在浏览器里 构建时,一次 每次请求时,在服务器上
需要一直运行的服务器 不需要 不需要 需要
最适合 登录后才用的工具、后台 博客、文档、课程 因人而异或实时变化的页面
深潜谷歌到底会不会执行 JavaScript+

会,但别把希望押在这上面。谷歌的爬虫先抓 HTML,再把页面放进一个渲染队列,轮到时才用浏览器内核运行 JavaScript,看渲染后的结果。这一步要排队,也不是每个页面都能等到。

更重要的是,谷歌之外的访客大多不做这一步:生成分享卡片的程序、不少其他搜索引擎、多数 AI 爬虫,通常只读第一次返回的 HTML。

所以结论很简单:把正文放进第一次返回的 HTML 里,是唯一不用赌的做法。

爬虫第一眼看到什么

"第一次返回的 HTML"是判断这一切的起点,验证只需要一行命令:

curl -s https://你的网址/某篇文章 | grep "文章里的一句话"

能搜到,说明正文在 HTML 里;搜不到,说明正文是 JavaScript 后来画上去的。

同一张 HTML 的 <head> 里,还有专门给程序看的说明:标题、描述、分享图,也就是页面元信息。单页应用的另一个常见问题是全站只有一套元信息:本站迁移前,25 篇文章的标题全是 Felix Xiao,分享任何一篇,卡片都是首页的样子。

还有一个容易被忽略的细节:为了让浏览器里的路由正常工作,单页应用通常把所有网址都指向同一个 HTML,于是连不存在的网址也会返回"成功"。这是第 5 课的主角。

深潜为什么 AI 起项目时,默认给你单页应用+

让 AI "做一个网站",它最常见的起手式是 Vite + React:这是它见过最多的模板,文档多、例子多、跑起来快。

这个默认值对工具类产品很合适,对内容站不合适。但 AI 通常不会停下来问你"这个网站需要被搜到吗",它只会给你最常见的答案。所以这个问题,得由你在一开始就提出来。

找茬

下面是本站迁移前的真实代码(节选并略作简化):首页 HTML、托管配置和文章页组件。站长的需求只有一句:文章要能被搜到,分享出去要有卡片。

这段 HTML / JSON / JSX 里埋了 6 处问题。点击你觉得有问题的行,至少找出 3 处再揭晓。

查看代码与答案(6 处问题)
 1  <!-- index.html:访问任何网址,服务器都返回这一个文件 -->
 2  <head>
 3    <meta name="description" content="Felix Xiao — 独立开发者,构建 AI 原生产品与工具" />
 4    <meta property="og:title" content="Felix Xiao" />
 5    <title>Felix Xiao</title>
 6  </head>
 7  <body>
 8    <div id="root"></div>
 9    <script type="module" src="/src/main.jsx"></script>
10  </body>
11  
12  // vercel.json(托管配置)
13  { "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
14  
15  // BlogDetail.jsx(文章页组件,节选)
16  const markdownModules = import.meta.glob("@/content/posts/*.md", { query: "?raw", eager: true })
17  const raw = markdownModules[`/src/content/posts/${slug}.md`]
18  return <ReactMarkdown>{raw}</ReactMarkdown>
  • 第 4 行 · 中危 · 分享卡片永远是首页的样子:og:title 全站只有一个值,也没有 og:image。把任何一篇文章发到社交平台,卡片上都只有网站名,没有文章标题和配图。 该问的话:把这篇文章的链接发到微信,卡片上会显示什么?
  • 第 5 行 · 中危 · 每一页都是同一个标题:所有网址都返回这一个文件,所以 25 篇文章的标题都是 Felix Xiao:搜索结果里分不清是哪一篇,浏览器标签页也一样。 该问的话:每个页面都有自己的 title 吗?它是在哪里生成的?
  • 第 8 行 · 高危 · 正文所在的位置是空的:这是整张 HTML 里唯一放正文的地方,里面什么都没有。不运行 JavaScript 的访客,读到的就是这个空盒子。 该问的话:不运行 JavaScript 的话,这张 HTML 里有正文吗?
  • 第 13 行 · 中危 · 所有网址都改写到首页:为了让前端路由生效,任何路径都返回 index.html 和 200,包括根本不存在的地址——这就是软 404。Cloudflare Pages 在没有 404 页面时,也会这样兜底。 该问的话:访问一个不存在的网址,返回的状态码是多少?
  • 第 16 行 · 中危 · 读一篇文章,要先下载全部文章:eager: true 把 25 篇文章的全文都打进同一个 JavaScript 包。读者只想看一篇,浏览器却要先下载所有文章。 该问的话:打开一篇文章时,浏览器额外下载了多少用不到的内容?
  • 第 18 行 · 高危 · 正文在浏览器里才被渲染出来:正文是浏览器里的 JavaScript 把 Markdown 转成 HTML 画上去的,服务器发出的 HTML 里没有它。这是爬虫看不到正文的根本原因。 该问的话:这篇文章的正文,是构建时写进 HTML 的,还是在浏览器里渲染出来的?

快测

1. 迁移前,在终端里执行 curl -s https://space.felixwithai.com/blog/loop-engineering,拿到的 HTML 里有什么?

查看选项与答案
  • A. 完整的文章正文,浏览器里能看到的都在——浏览器里看得到,就以为服务器发来的就是这些。其实浏览器还运行了 JavaScript,curl 不会。
  • B. 一个标题,和一个空的 root 盒子(正确)——单页应用的服务器对所有网址都返回同一个空壳,正文要等 JavaScript 运行之后才出现。
  • C. 404,因为服务器上没有这个文件——托管配置把所有路径都改写到了 index.html,所以不会 404——而这恰恰是另一个问题,第 5 课会讲。

判断一个页面能不能被搜到,先看不运行 JavaScript 时,第一次返回的 HTML 里有什么。

2. 文章页加载完之后,用 JavaScript 把 document.title 改成文章标题。分享卡片会显示文章标题吗?

查看选项与答案
  • A. 会,标题已经改过来了——浏览器标签页确实会变。但生成分享卡片的程序通常只读第一次返回的 HTML,不会等你的 JavaScript 运行。
  • B. 多半不会:生成卡片的程序不运行 JavaScript,看到的还是首页标题(正确)——给程序看的信息,要在服务器发出 HTML 的那一刻就写好。
  • C. 会,只要再加一个 keywords 标签——keywords 标签早就不被主流搜索引擎使用了,也和分享卡片无关。

标题、描述、分享图,要在第一次返回的 HTML 里就在,而且每页不同。

3. 下面哪一种网站,最适合做成单页应用?

查看选项与答案
  • A. 公开的技术博客——博客要被陌生人搜到,第一次返回的 HTML 里必须有正文。
  • B. 登录后才能用的数据看板(正确)——看板不需要被搜索引擎收录,交互又多,单页应用的顺滑正好用得上。
  • C. 课程和文档站——和博客一样,内容站更适合静态生成。

选渲染方式之前先问一句:这个页面需要被不运行 JavaScript 的访客读到吗?

判断时刻

你的博客是 AI 搭的 React 单页应用,已经写了 25 篇文章。某天你发现搜索引擎一篇都没收录,分享出去的链接也没有卡片。你让 AI 修,它给了三个方案。

你会先选哪一个?

三个选项各自的代价
  • A. 在 index.html 里多加一些关键词和描述标签——考察治标还是治本:改得最快,但正文依然是空的,全站也依然只有一套元信息。搜索引擎要的是正文,不是关键词。这个方案几乎不起作用,还会让你以为问题已经处理过了。
  • B. 保留 React,加一个预渲染插件,构建时先把每个页面渲染成 HTML——考察改动成本与效果:能解决“第一次返回的 HTML 是空的”,改动也小。代价是每个页面的标题、描述要自己一一配置,路由和数据加载要适配插件。网站越长越大,这层补丁越难维护。
  • C. 迁移到静态生成框架,逐页截图比对,确保外观不变——考察一次性投入与长期收益:工作量最大:页面和组件都要改写,还要证明外观没变。本站最后选的就是这条路:迁到 Astro,逐页像素比对,首页和列表页差异 0.000%,每页都有了完整正文和独立的分享卡片。

三个方案没有绝对的对错,差别在于你愿意为“被搜到”付出多少、在什么时候付。先用 curl 确认问题出在哪一层,再决定改哪一层。A 看起来最省事,恰恰是因为它没有碰到问题所在的那一层。

带走

下次让 AI 做这件事时,问它

  1. 这个网站需要被搜索引擎收录吗?如果需要,每个页面第一次返回的 HTML 里有没有完整正文?
  2. 每个页面有自己的 title、description 和分享图吗?它们是构建时写进 HTML 的,还是在浏览器里用 JavaScript 改的?
  3. 访问一个不存在的网址,服务器会返回什么状态码?
  4. 有没有自动生成 sitemap.xml,并在 robots.txt 里声明它?

自己验证

  • curl -s 你的网址 | grep "正文里的一句话":能搜到,才说明正文在 HTML 里。
  • 右键“查看网页源代码”(不是开发者工具里的元素面板,那里显示的是 JavaScript 运行之后的样子),看 <head> 里的标题和描述是不是这一页自己的。
  • curl -I 你的网址/一个不存在的路径:第一行应该是 404。

浏览器里看得到,不等于别人拿得到。先问一句:第一次返回的 HTML 里有什么?

延伸阅读