用 Vue、React 或类似框架搭建的站点,页面切换靠前端路由完成,用户点起来很顺,但换到蜘蛛的视角,可能整站只看到一张 HTML。URL 发现的前提是页面上存在能被点开、能被解析的链接,单页应用恰好最容易在这个环节掉链子。
先分清 hash 模式和 history 模式
前端路由常见的两种写法,对 URL 发现的影响完全不同。
- hash 模式:地址形如 example.com/#/news/123。井号后面的部分不会作为独立 URL 向服务器发起请求,蜘蛛即使拿到这个地址,抓到的也只是首页那份 HTML,后面那段路由信息基本等于没有。
- history 模式:地址形如 example.com/news/123,是真实、可请求的 URL。只要服务器做了回退配置,让未匹配的路径返回应用入口,蜘蛛就能直接拿到页面。
所以如果内容页还在用 hash 路由,优先把它改成 history 模式,这是最基础的一步。
首屏 HTML 里有没有链接
换成 history 模式只是让 URL 变真,接下来还要回答一个问题:服务器返回的这份 HTML 里,有没有下一层内容的入口?
不少单页应用的入口 HTML 只有一个空容器,导航和列表全部由 JS 挂载。蜘蛛虽然越来越能执行 JS,但执行有预算、有超时,指望它把整站渲染一遍并不划算。更稳的做法是让入口 HTML 本身就带链接。
可以这样自查
- 用命令行请求一个栏目页,看看返回的源码里有没有指向内容页的 href。
- 在浏览器里关掉 JavaScript,看看还能不能点到下一层。
- 对照服务端日志,确认内容页的 URL 是否被单独请求过。
判断标准很简单:把 JS 关掉,如果页面上还能顺着点下去,URL 发现基本就没有大问题。
几种务实的处理方式
服务端渲染或预渲染
对导航、栏目列表这类关键路径做服务端渲染或预渲染,输出的 HTML 里直接带 a 标签。改造范围不用铺满全站,先把主干走通即可。
首屏先输出一批真实链接
列表页不必等接口返回全部数据再渲染。首屏可以先输出十几条真实链接,剩下的再用前端路由挂载。
路由懒加载别影响链接本身
按需加载组件没问题,但链接的 href 应该在 HTML 阶段就写好,而不是点击时才用脚本跳转。用 click 事件代替 a 标签,蜘蛛拿不到入口。
用站点地图兜底
服务端渲染成本较高时,至少让 XML 站点地图覆盖全部内容 URL,作为一条独立的发现通道。它不能替代内链,但能保证内容页不至于完全没人带路。
几个常见坑
- 所有路由都返回 200。不存在的详情页也返回应用外壳,等于给蜘蛛递了一堆内容相同的页面。
- 路由参数被无限拼接。筛选、排序、分页参数组合出来的 URL 数量可能远超真实内容。
- 把导航写死在 JS 数组里。链接存在,但不出现在 HTML 中。
- 只在页面底部放一个“加载更多”按钮,却不给对应的分页 URL。
单页应用不是不能被发现,而是要把“依赖 JS 才存在”的链接,变回“HTML 里就存在”的链接。这一层理顺了,抓取和收录才有继续讨论的余地。