You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确配置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)时,资源完全加载失败(截图:资源加载失败1、资源加载失败2、多级路径失败);
  • 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`))

方案差异


问题本质与解决方案

问题本质

  1. 资源加载失败:前端资源引用使用了相对路径,在多级路由下会被浏览器错误解析。例如访问/sss/5555时,js/main.js会被解析为/sss/5555/js/main.js,但实际资源存放在/js/main.js,导致404。
  2. admin路由失效:方案一中先挂载了客户端的/*路由,Express中间件按顺序匹配,所有请求(包括/admin开头的)都会被客户端路由拦截,admin相关的中间件永远不会执行。
  3. 文件读取不规范:手动使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 12:22:03