客户端路由 History 模式
解读
在国内前端工程化场景里,Vue/React 单页应用(SPA)上线后,如果启用 history 模式(去掉 # 号),首次刷新或直接输入二级路径时,浏览器会向服务器发起真实 GET 请求。PHP 作为后端入口,必须能“兜底”返回 index.html,否则 404 会直接抛到用户面前。面试官问“PHP 怎么处理 history 模式”,核心想确认:
- 你是否理解 history 模式的运行原理与陷阱;
- 能否用最轻量的 PHP 代码解决“刷新 404”问题;
- 是否知道性能、SEO、安全、运维层面的国内落地细节(Nginx + PHP-FPM、宝塔、CDN、微信 JSSDK 白名单等)。
知识点
- history 模式 vs hash 模式:浏览器行为差异、URL 规范、SEO 友好度。
- 单页应用路由 fallback(回退)策略:所有非静态资源请求全部重写到入口文件。
- PHP 在 Apache/Nginx 中的两种角色:
- 做“静态文件守卫”——只负责转发;
- 做“入口聚合”——index.php 直接吐出 html,顺便注入初始数据。
- 国内主流服务器软件:
- Nginx(宝塔、LNMP 一键包、云厂商镜像)占 90% 以上,.htaccess 基本无用,必须写 location。
- 安全:
- 禁止把 /api、/uploads、*.php 路由重写到 SPA;
- 防止 open redirect、XSS、路径穿越。
- 性能:
- 入口文件加 Cache-Control: no-cache,静态资源保留哈希指纹 + 强缓存;
- CDN 边缘节点也要配置“404 回源重写”,否则刷新依旧 404。
- SEO:
- 如果要做服务端渲染(SSR)或预渲染,PHP 可结合 prerender.io 或自写 Puppeteer 中间层;
- 百度 MIP、微信 JSSDK 分享对路由锚点敏感,history 模式更容易过白名单。
答案
【场景假设】
项目目录:
/var/www/
├─ index.html // SPA 唯一入口
├─ static/ // 打包后的 js/css/img
└─ api/ // 真实 PHP 业务接口
【Nginx 方案】(国内最常用,建议优先答)
server {
listen 80;
server_name example.com;
root /var/www;
# 1. 静态资源直接响应,不经过 PHP
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# 2. 真实后端接口,交给 PHP-FPM
location ^~ /api/ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi.conf;
}
# 3. 其余所有路由全部回退到 index.html
location / {
try_files $uri $uri/ /index.html;
}
}
【Apache 方案】(老旧虚拟主机,补充回答即可)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^api/ - [L] # 接口跳过
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
【纯 PHP 兜底】(极端场景,无 Nginx 权限,index.php 当入口)
<?php // 只允许 GET,禁止直接访问 *.php 文件 if ($_SERVER['REQUEST_METHOD'] !== 'GET') { http_response_code(405); exit; } // 白名单过滤 $pathname = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); if (preg_match('#^/(api|uploads|static)/#', $pathname)) { http_response_code(404); exit; } // 输出 SPA 外壳 header('Content-Type: text/html; charset=utf-8'); header('Cache-Control: no-cache, no-store, must-revalidate'); readfile(__DIR__ . '/index.html'); 运维提醒: 1. 宝塔面板请在“网站→伪静态”里粘贴 Nginx 配置,不要勾选“强制 HTTPS”后忘记改 80 配置; 2. 阿里云 OSS + CDN 做静态托管时,把“默认 404 页面”设为 index.html,也能达到同样效果; 3. 微信网页授权目录填写 https://example.com/,history 模式不会带 #,更容易通过审核。 ## 拓展思考 1. 大型项目往往把前端、后端、管理后台拆成三个域名,history 模式 404 问题只在 www 域名出现,是否还需要统一入口? → 可以做多级 location 拆分,或者让前端直接走对象存储 + CDN,彻底解耦。 2. 如果要做灰度发布,PHP 如何在回退阶段按 Cookie 或 Header 返回不同版本的 index.html? → 在 location / 阶段把请求 proxy_pass 到内部 PHP,PHP 根据灰度规则 readfile 对应目录的 html,实现“前端灰度”。 3. SSR 预渲染与 history 模式共存时,PHP 如何识别爬虫并返回直出 HTML? → 在 Nginx map 里匹配 UA,爬虫流量反向代理到 Node 直出服务;普通用户继续返回静态 index.html,兼顾首屏与 SEO。