如何正确配置Express服务器实现按路径分流静态资源与SPA路由?
问题背景
需要正确高效配置网站页面过渡效果,同时解决Express服务器路由配置问题,目标实现:
- 地址栏路径不以
admin开头时(如http://localhost/、http://localhost/ee/rewr、http://localhost/ee/rewr/rrrrr),服务器仅读取src文件夹; - 路径以
admin开头时(如http://localhost/admin、http://localhost/admin/ee/rewr、http://localhost/admin/ee/rewr/rrrrr),服务器仅读取admin文件夹。
当前项目结构

现有服务器代码方案一
import express from "express"; import path from "path"; import fs from "fs"; import adminRoutes from './routes/adminRoutes.js'; import clientRoutes from './routes/clientRoutes.js'; const __dirname = path.resolve(); const app = express(); app.use(express.json()); app.use('/', express.static(path.join(__dirname, `src`), { index: 'none' })); app.use('/', clientRoutes); app.use('/admin/', express.static(path.join(__dirname, `admin`), { index: 'none' })); app.use('/admin/', adminRoutes); app.listen(80, () => console.log(`Server is run on port 80`))
路由文件代码(./routes/adminRoutes.js & ./routes/clientRoutes.js)
clientRoutes.js
import express from "express"; import path from "path"; import fs from "fs"; const router = express.Router(); const __dirname = path.resolve(); router.get('/*', (req, res) => { console.log('============================', req.url); fs.readFile(path.join(__dirname, `src`, 'index.html'), 'utf8', (err, html) => { if (err) console.log(err, 'Error') res.send(html) }) }); export default router;
adminRoutes.js
import express from "express"; import path from "path"; import fs from "fs"; const router = express.Router(); const __dirname = path.resolve(); router.get('/admin/', (req, res) => { console.log('============================', req.url); fs.readFile(path.join(__dirname, `admin`, 'index.html'), 'utf8', (err, html) => { if (err) console.log(err, 'Error') res.send(html) }) }); export default router;
遇到的问题
将clientRoutes.js中路由从router.get('/', (req, res) => {改为router.get('/*', (req, res) => {后:
- 服务器能加载CSS但无法加载JS,仅页面刷新后恢复正常;
- 访问多级路径(如
http://localhost/sss/5555)时,资源完全加载失败(截图:
、
、
); - admin路由完全无法工作。
另一种服务器代码方案
import express from "express"; import path from "path"; import fs from "fs"; const __dirname = path.resolve(); const app = express(); app.use(express.json()); app.use('/', express.static(path.join(__dirname, `src`), { index: 'none' })); app.use('/admin', express.static(path.join(__dirname, `admin`), { index: 'none' })); app.get(`/*`, (req, res) => { let mainHtmlPath; if (req.url.startsWith('/admin')) { mainHtmlPath = path.join(__dirname, `admin`, 'index.html'); } else { mainHtmlPath = path.join(__dirname, `src`, 'index.html'); } fs.readFile(mainHtmlPath, 'utf8', (err, html) => { if (err) console.log(err, 'Error') res.send(html) }) }); app.listen(80, () => console.log(`Server is run on port 80`))

问题本质与解决方案
问题本质
- 资源加载失败:前端资源引用使用了相对路径,在多级路由下会被浏览器错误解析。例如访问
/sss/5555时,js/main.js会被解析为/sss/5555/js/main.js,但实际资源存放在/js/main.js,导致404。 - admin路由失效:方案一中先挂载了客户端的
/*路由,Express中间件按顺序匹配,所有请求(包括/admin开头的)都会被客户端路由拦截,admin相关的中间件永远不会执行。 - 文件读取不规范:手动使用
fs.readFile处理HTML文件,未完善错误处理,且Express提供了更可靠的res.sendFile方法。
正确规范实现方案
第一步:修复前端资源路径
将src/index.html和admin/index.html中的资源引用改为绝对路径:
- 客户端:将
<script src="js/main.js"></script>改为<script src="/js/main.js"></script> - Admin端:将
<script src="js/main.js"></script>改为<script src="/admin/js/main.js"></script>
或在HTML头部添加base标签统一路径:
- 客户端:
<base href="/"> - Admin端:
<base href="/admin/">
第二步:优化服务器路由配置
方案一:分路由版本(模块化)
// 服务器主文件 import express from "express"; import path from "path"; import adminRoutes from './routes/adminRoutes.js'; import clientRoutes from './routes/clientRoutes.js'; const __dirname = path.resolve(); const app = express(); app.use(express.json()); // 优先处理admin相关请求,避免被客户端路由拦截 app.use('/admin', express.static(path.join(__dirname, 'admin'), { index: 'none' })); app.use('/admin', adminRoutes); // 处理客户端请求 app.use('/', express.static(path.join(__dirname, 'src'), { index: 'none' })); app.use('/', clientRoutes); app.listen(80, () => console.log(`Server is running on port 80`))
// ./routes/adminRoutes.js import express from "express"; import path from "path"; const router = express.Router(); const __dirname = path.resolve(); // 匹配/admin下所有路径 router.get('/*', (req, res) => { res.sendFile(path.join(__dirname, 'admin', 'index.html'), (err) => { if (err) res.status(500).send(err); }); }); export default router;
// ./routes/clientRoutes.js import express from "express"; import path from "path"; const router = express.Router(); const __dirname = path.resolve(); // 匹配根路径下所有非admin的路径 router.get('/*', (req, res) => { res.sendFile(path.join(__dirname, 'src', 'index.html'), (err) => { if (err) res.status(500).send(err); }); }); export default router;
方案二:合并路由版本(简洁版)
import express from "express"; import path from "path"; const __dirname = path.resolve(); const app = express(); app.use(express.json()); // 静态文件服务:admin优先级高于客户端 app.use('/admin', express.static(path.join(__dirname, 'admin'), { index: 'none' })); app.use('/', express.static(path.join(__dirname, 'src'), { index: 'none' })); // 处理所有HTML请求 app.get('/*', (req, res) => { const htmlPath = req.path.startsWith('/admin') ? path.join(__dirname, 'admin', 'index.html') : path.join(__dirname, 'src', 'index.html'); res.sendFile(htmlPath, (err) => { if (err) { console.error(err); res.status(500).send('Internal Server Error'); } }); }); app.listen(80, () => console.log(`Server is running on port 80`))
内容的提问来源于stack exchange,提问作者Air
相关产品推荐
相关产品推荐

