SPA 路由 fallback 机制 + 304状态码 + 示例
介绍
SPA 路由 fallback 机制
SPA(单页应用)路由 fallback 机制,是为解决单页应用路由特性与服务器静态资源访问逻辑不匹配问题而设计的方案。由于 SPA 通常只有一个 HTML 入口文件(如 index.html),其内部路由由前端框架通过 History API 或 Hash 模式管理,并非服务器上真实存在的物理路径。
当用户发送请求或者刷新页面时,服务器会因找不到对应物理文件而返回 404 错误。此时 fallback 机制会生效,将所有无法匹配到具体静态资源的请求,统一重定向或转发到 SPA 的入口 index.html,让前端框架接管路由解析,确保页面能正常渲染对应的内容,避免 404 错误。
304 状态码
304 状态码是 HTTP 协议中用于实现缓存机制的重要状态码,全称为 “Not Modified”,表示客户端请求的资源自上次获取后未发生变化,服务器无需再次返回完整的资源内容,仅需告知客户端可直接使用本地缓存的资源。
其工作流程通常基于客户端与服务器的缓存协商:首次请求资源时,服务器会在响应头中携带 Last-Modified
(资源最后修改时间)或 ETag
(资源唯一标识)等信息;
当客户端再次请求该资源时,会在请求头中通过 If-Modified-Since
(对应 Last-Modified
)或 If-None-Match
(对应 ETag
)携带上次获取的标识信息;服务器对比这些信息后,若判断资源未修改,则返回 304 状态码并且不携带资源主体,客户端收到后直接使用本地缓存。
这一机制能显著减少网络数据传输量,降低服务器负载,同时提升了客户端资源加载速度。
发现问题
如果我们使用前端发送请求时,没有写明路径和端口,例如:
const response = await fetch(`/api/chat?message=${encodeURIComponent(messageToSend)}`)
那么前端会默认把请求发给 “当前前端服务的地址”
这里测试发现输出的不符合预期,并不是中文,而是一个HTML 文件内容
分析问题与解决
在当前浏览器页面按F12打开浏览器开发者工具,发现状态码为304,且控制台没有任何信息:
因为没有指定后端端口号,接口不会接收到请求,前端也没有报错。
那么我们加入下面代码,指定不要用缓存:
// 调用后端API,发送用户消息const response = await fetch(`/api/chat?message=${encodeURIComponent(messageToSend)}`, {cache: 'no-cache',headers: {'Cache-Control': 'no-cache','Pragma': 'no-cache'}})
或者直接在浏览器勾选禁用缓存:
Ctrl+F5刷新
接着测试:
依旧响应HTML,但是状态码变成了200:
控制台依然没信息
后端接口当然也没接收到请求,因为被前端开发服务器直接处理了。
首先后端没有接收请求,后端配置跨域是没有用的,因为请求没有到达后端接口。
- 没有
@CrossOrigin
时:前端发请求到localhost:8080/api/chat
,后端会正常处理,但浏览器收到响应后,发现没有 “允许跨域” 的头信息(Access-Control-Allow-Origin
),会直接丢弃响应,提示 “跨域错误”; - 有
@CrossOrigin
时:后端会在响应头中添加Access-Control-Allow-Origin: http://localhost:5173
,浏览器看到这个头,就会允许前端接收响应,不再拦截。
@CrossOrigin
只能解决 “浏览器是否允许接收响应” 的问题,它不能改变请求的发送地址
前端发送请求时如果不写完整域名,直接写相对路径(如 /api/ai
),请求会默认发送到当前前端页面所在的 “源”(协议 + 域名 + 端口),也就是前端开发服务器或生产环境中前端资源托管的服务地址。
为什么返回 200 还是 HTML?
这是前端服务的 “SPA 路由 fallback 机制” 导致的,和后端无关:
前端服务的核心职责是托管静态资源:前端服务只认识 “自己项目里的静态文件”(如
index.html
、src/main.js
、public/xxx.png
等),不认识/api/chat
这种 “后端接口路径”,因为前端项目里根本没有对应的静态文件(没有public/api/chat.html)
。fallback 机制:找不到资源就返回 index.html。为了支持单页应用(SPA)的 “前端路由”,前端服务会配置 “fallback”:当请求的路径找不到对应的静态资源时,默认返回项目的
index.html
。最终结果:请求
localhost:5173/api/chat
时,前端服务找不到这个路径的静态资源,触发 fallback,返回index.html
(Content-Type 为text/html
);又因为禁用了缓存,是全新请求,所以状态码是 200(而非 304)—— 整个过程和后端完全无关,后端自然收不到请求。