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

MERN项目商品列表跳转详情页出现404错误问题排查

问题描述

MERN项目中,从商品列表点击进入单个商品详情页时触发404错误,跳转后的URL为 http://localhost:3000/product/undefined;但直接在URL中输入合法商品ID(如http://localhost:3000/product/6376ae043f18b79b043c5b89),却能正常加载详情页。

相关代码文件

productScreen.js

import React, { useState, useEffect } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import {
  Row,
  Col,
  Image,
  ListGroup,
  Card,
  Button,
  Form,
} from "react-bootstrap";

import Rating from "../components/Rating";
import Message from "../components/Message";
import Loader from "../components/Loader";

import { listProductDetails } from "../actions/productActions.js";

const ProductScreen = () => {
  const { id } = useParams();
  const productId = `${encodeURIComponent(id)}`;
  const dispatch = useDispatch();
  const history = useNavigate();

  const [qty, setQty] = useState(1);

  const productDetails = useSelector((state) => state.productDetails);
  const { loading, error, product } = productDetails;

  useEffect(() => {
    dispatch(listProductDetails(productId));
  }, [dispatch, productId]);

  const addToCartHandler = () => {
    history(`/cart/${productId}?qty=${qty}`);
  };
  return (
    <>
      <Link className="btn btn-light my-3" to="/">
        Go Back
      </Link>
      {loading ? (
        <Loader />
      ) : error ? (
        <Message variant="danger">{error}</Message>
      ) : (
        <Row>
          <Col md={6}>
            <Image src={product.image} alt={product.name} fluid />
          </Col>
          <Col md={3}>
            <ListGroup variant="flush">
              <ListGroup.Item>
                <h3>{product.name}</h3>
              </ListGroup.Item>
              <ListGroup.Item>
                <Rating
                  value={product.rating}
                  text={`${product.numReviews} reviews`}
                />
              </ListGroup.Item>
              <ListGroup.Item>Price: ${product.price}</ListGroup.Item>
              <ListGroup.Item>
                Description: {product.description}
              </ListGroup.Item>
            </ListGroup>
          </Col>
          <Col md={3}>
            <Card>
              <ListGroup variant="flush">
                <ListGroup.Item>
                  <Row>
                    <Col>Price:</Col>
                    <Col>
                      <strong>${product.price}</strong>
                    </Col>
                  </Row>
                </ListGroup.Item>
                <ListGroup.Item>
                  <Row>
                    <Col>Status:</Col>
                    <Col>
                      {product.countInStock > 0 ? "In Stock" : "Out Of Stock"}
                    </Col>
                  </Row>
                </ListGroup.Item>

                {product.countInStock > 0 && (
                  <ListGroup.Item>
                    <Row>
                      <Col>Qty</Col>
                      <Col>
                        <Form.Control
                          as="select"
                          value={qty}
                          onChange={(e) => setQty(e.target.value)}
                        >
                          {[...Array(product.countInStock).keys()].map((x) => (
                            <option key={x + 1} value={x + 1}>
                              {x + 1}
                            </option>
                          ))}
                        </Form.Control>
                      </Col>
                    </Row>
                  </ListGroup.Item>
                )}

                <ListGroup.Item>
                  <Button
                    onClick={addToCartHandler}
                    className="btn-block"
                    type="button"
                    disabled={product.countInStock === 0}
                  >
                    Add To Cart
                  </Button>
                </ListGroup.Item>
              </ListGroup>
            </Card>
          </Col>
        </Row>
      )}
    </>
  );
};

export default ProductScreen;

productAction.js

import axios from 'axios';
import {
  PRODUCT_LIST_REQUEST,
  PRODUCT_LIST_SUCCESS,
  PRODUCT_LIST_FAIL,
  PRODUCT_DETAILS_REQUEST,
  PRODUCT_DETAILS_SUCCESS,
  PRODUCT_DETAILS_FAIL,
} from '../constants/productConstants.js';
export const listProducts = () => async (dispatch) => {
  try {
    dispatch({ type: PRODUCT_LIST_REQUEST });

    const { data } = await axios.get('/api/products');

    dispatch({
      type: PRODUCT_LIST_SUCCESS,
      payload: data,
    });
  } catch (error) {
    dispatch({
      type: PRODUCT_LIST_FAIL,
      payload:
        error.response && error.response.data.message
          ? error.response.data.message
          : error.message,
    });
  }
};

export const listProductDetails = (id) => async (dispatch) => {
  try {
    dispatch({ type: PRODUCT_DETAILS_REQUEST });

    const { data } = await axios.get(
      `/api/products/${id}`
    );

    dispatch({
      type: PRODUCT_DETAILS_SUCCESS,
      payload: data,
    });
  } catch (error) {
    dispatch({
      type: PRODUCT_DETAILS_FAIL,
      payload:
        error.response && error.response.data.message
          ? error.response.data.message
          : error.message,
    });
  }
};

productRoutes.js

import express from 'express';
import Product from '../models/productModel.js';
import asyncHandler from 'express-async-handler';
import {ObjectId} from 'mongoDB';
import mongoose from 'mongoose';
const router = express.Router();

// @desc fetch all products
// @droute  GET /api/products
// @access public

router.get(
  '/',
  asyncHandler(async (req, res) => {
    const products = await Product.find({});

    return res.json(products);
  })
);

// @desc fetch oneproducts
// @droute  GET /api/products/:id
// @access public

router.get(
  '/:id([0-9a-fA-F]{24})',
  asyncHandler(async (req, res) => {
    const id = mongoose.Types.ObjectId(req.params.id.trim());
    const product = await Product.findById(id);
    if (product) {
      return res.json(product);
    } else {
      return res.status(404).json({ message: 'Product not found' });
      //throw new Error('Product not found');
    }

    //return res.json(product);
  })
);

export default router;

App.js

import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Container } from 'react-bootstrap';
import Header from './components/Header';
import Footer from './components/Footer';
import HomeScreen from './screens/HomeScreen';
import ProductScreen from './screens/ProductScreen';
import { CartScreen } from './screens/CartScreen';
const App = () => {
  return (
    <Router>
      <Header />
      <main className='py-3'>
        <Container>
          <Routes>
            <Route path='/product/:id' element={<ProductScreen />} />
            <Route path='/cart' element={<CartScreen />} />
            <Route path='/' element={<HomeScreen />} />
          </Routes>
        </Container>
      </main>

      <Footer />
    </Router>
  );
};

export default App;

商品列表页截图

问题根源与解决方案

核心问题

跳转时传递的商品ID为undefined,导致URL路径错误。问题出在商品列表页(HomeScreen)的跳转链接上,你可能错误引用了不存在的字段(比如用product.id而非MongoDB默认的product._id)。

修复步骤

  1. 修正HomeScreen中的跳转链接
    找到商品列表项的跳转代码,将ID字段改为_id:
    错误写法(示例):

    <Link to={`/product/${product.id}`}>查看详情</Link>
    

    正确写法:

    <Link to={`/product/${product._id}`}>查看详情</Link>
    

    若后端返回的商品ID字段不是_id,则替换为实际字段名。

  2. 验证参数传递有效性
    在ProductScreen中添加日志验证ID是否正确接收:

    const { id } = useParams();
    console.log('接收的商品ID:', id);
    

    若打印结果为undefined,说明跳转时确实未传递有效ID。

  3. 后端路由优化(可选)
    无需手动转换ObjectId,findById可直接处理合法的ID字符串,简化路由代码:

    router.get('/:id', asyncHandler(async (req, res) => {
      const product = await Product.findById(req.params.id);
      if (product) {
        return res.json(product);
      } else {
        return res.status(404).json({ message: 'Product not found' });
      }
    }));
    
  4. 添加跳转防护(可选)
    在列表页对ID做非空判断,避免无效跳转:

    {product._id && <Link to={`/product/${product._id}`}>查看详情</Link>}
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 08:45:28