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

Express Router被调用两次的原因排查及解决

问题:Express路由中间件被触发两次的原因及解决方法

问题描述

运行server.js后,通过路由调用index.js,其中router.use(async (req, res, next) => {})内的console.log(req.path)会输出两次"/",但仅手动发起了一次页面请求。浏览器网络面板显示localhost:3001/被请求两次,且index.js仅加载一次,实际是router.get('/')被触发两次。


相关代码

server.js

const express = require('express');
const categoryService = new CategoryService(path.join(__dirname, 'data', 'categories.json'));
const routes = require('./routes/index')({ categoryService });
const app = express();

app.use((request, response, next) => {
  response.locals.homeVar = 'Home';
  next();
});

app.use((request, response, next) => {
  cookieSession({
    name: 'session',
    keys: ['ABC', 'DEF'],
    maxAge: 1 * 60 * 60 * 1000, // 24 hours
  })(request, response, next);
});

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.locals.siteName = 'Together Mart';
app.use(routes);

app.use((request, response, next) => {
  next(createError(404, 'File not found'));
});

app.use((err, req, res) => {
  res.status(err.status || 500);
  res.locals.error = err.message;
  res.locals.errorStatus = err.status;

  res.locals.errorStack = err.stack || '';

  if (err.status === 404) {
    res.locals.error = `The requested file '${req.originalUrl}' could not be found`;
  }

  if (err.name === 'CssSyntaxError') {
    res.locals.error += `\n\nError in file '${err.filename}', line ${err.line}, column ${err.column}:\n${err.reason}`;
  }

  if (err.view) {
    res.locals.error += `\n\nError rendering '${err.view}'`;
  }

  console.error(err);

  res.render('error');
});

index.js

const express = require('express');
const router = express.Router();
let categoryData;

module.exports = (params) => {
  console.log('Inside index.js'); 
  let categoryList = null;

  router.use(async (req, res, next) => {
    console.log(req.path);
    if (!categoryList) {
      try {
        const { categoryService } = params;
        categoryList = await categoryService.getNames();
        categoryData = await categoryService.getListShort();
      } catch (err) {
        next(err);
      }
    }

    res.locals.categoryList = categoryList;
    res.locals.categoryData = categoryData;
    next();
  });

  router.get('/', (req, res) => {
    res.render('layout/index', { pageTitle: 'Home Page', template: 'index' });
  });

  router.get('/category/:name/subcategories', async (req, res, next) => {
    try {
      const { categoryService } = params;
      const subcategories = await categoryService.getSubCategoriesForCategory(
        req.params.name.toString()
      );
      console.log(req.params.name);
      console.log(subcategories);
      res.json(subcategories);
    } catch (err) {
      next(err);
    }
  });

  return router;
};

原因分析

1. 浏览器自动请求favicon.ico

绝大多数浏览器会自动向网站根路径请求/favicon.ico作为网站图标。如果服务器未配置专门处理该请求的路由,且路由匹配规则存在疏漏,这个请求可能会被错误导向到根路径的处理逻辑,导致看起来根路径被请求两次。

2. 页面模板资源路径错误

如果渲染的layout/index模板中,存在资源引用(如图片、脚本、样式)的src/href属性错误指向根路径/,浏览器会将其视为需要加载的资源,从而再次发起根路径请求。


解决方法

方法1:处理favicon.ico请求

在app.use(routes)之前添加专门的图标请求处理逻辑:

// 直接返回204无内容,避免触发后续路由
app.get('/favicon.ico', (req, res) => res.status(204).end());

或者使用官方中间件提供图标:

const favicon = require('serve-favicon');
const path = require('path');
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

方法2:检查页面模板资源引用

打开layout/index模板文件,逐一检查所有src、href属性,确保没有错误指向根路径/。例如:

  • 错误写法:<img src="/">
  • 正确写法:<img src="/assets/images/logo.png">(根据实际资源路径调整)

方法3:验证请求差异

在路由中间件中打印完整请求信息,明确两次请求的区别:

router.use(async (req, res, next) => {
  console.log(`路径: ${req.path}, 方法: ${req.method}, 客户端标识: ${req.get('User-Agent')}`);
  // 原有逻辑
  next();
});

通过User-Agent可区分是页面请求还是图标请求(图标请求的标识通常包含favicon关键词)。


内容的提问来源于stack exchange,提问作者Bharath

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 21:38:11