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

React开发咖啡馆网站使用fs写入JSON文件遇浏览器兼容性错误

React前端使用fs模块报错的解决方案

问题背景

我用React开发小型咖啡馆网站,需要实现订单提交后在管理员面板显示的功能,编写相关代码后,添加saveOrder()函数时控制台出现以下错误:

Uncaught Error: Module "fs" has been externalized for browser compatibility. Cannot access "fs.readFileSync" in client code.

原代码如下:

import React from 'react';
import { OrderType } from './types';
import { Check } from 'lucide-react';
import fs from 'fs';
import path from 'path';

const ORDERS_FILE = "orders.json";

export function saveOrder(order2: OrderType): void {
  let orders: OrderType[] = [];
  
  const data = fs.readFileSync(ORDERS_FILE, 'utf8');
  orders = JSON.parse(data);
  orders.push(order2);

  fs.writeFileSync(ORDERS_FILE, JSON.stringify(orders, null, 2));
}

type OrderConfirmationProps = {
  order: OrderType;
  onClose: () => void;
};

export function OrderConfirmation({ order, onClose }: OrderConfirmationProps) {
  saveOrder(order);
  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4">
      <div className="bg-white rounded-lg p-6 max-w-md w-full">
        <div className="flex items-center justify-center mb-6">
          <div className="bg-green-100 rounded-full p-2">
            <Check className="w-8 h-8 text-green-600" />
          </div>
        </div>
        <h3 className="text-xl font-semibold text-center mb-4">Order Confirmed!</h3>
        <p className="text-center mb-4">Your order ID is: <br />
          <span className="font-mono bg-gray-100 px-2 py-1 rounded">{order.id}</span>
        </p>
        <div className="border-t border-b py-4 my-4">
          <h4 className="font-semibold mb-2">Order Summary:</h4>
          {order.items.map((item) => (
            <div key={item.id} className="flex justify-between text-sm">
              <span>{item.quantity}x {item.name}</span>
              <span>${(parseFloat(item.price) * item.quantity).toFixed(2)}</span>
            </div>
          ))}
        </div>
        <div className="flex justify-between font-semibold mb-6">
          <span>Total:</span>
          <span>${order.totalPrice.toFixed(2)}</span>
        </div>
        <button
          onClick={onClose}
          className="w-full bg-amber-700 text-white px-4 py-2 rounded hover:bg-amber-800 transition-colors"
        >
          Close
        </button>
      </div>
    </div>
  );
}

问题原因

fs是Node.js专属的核心模块,仅能在服务器环境运行,浏览器没有这个模块的实现,所以直接在React前端组件中调用fs的方法会触发兼容性错误。前端代码无法直接读写服务器或本地文件,必须通过后端接口完成文件操作。

修复步骤

1. 改造前端组件,通过接口提交订单

移除前端的fs相关导入和saveOrder函数,改用fetch向后端发送订单数据:

import React, { useEffect } from 'react';
import { OrderType } from './types';
import { Check } from 'lucide-react';

type OrderConfirmationProps = {
  order: OrderType;
  onClose: () => void;
};

export function OrderConfirmation({ order, onClose }: OrderConfirmationProps) {
  // 组件挂载时发送订单到后端
  useEffect(() => {
    const submitOrder = async () => {
      try {
        await fetch('/api/save-order', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(order),
        });
      } catch (error) {
        console.error('订单提交失败:', error);
      }
    };
    submitOrder();
  }, [order]);

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4">
      <div className="bg-white rounded-lg p-6 max-w-md w-full">
        <div className="flex items-center justify-center mb-6">
          <div className="bg-green-100 rounded-full p-2">
            <Check className="w-8 h-8 text-green-600" />
          </div>
        </div>
        <h3 className="text-xl font-semibold text-center mb-4">Order Confirmed!</h3>
        <p className="text-center mb-4">Your order ID is: <br />
          <span className="font-mono bg-gray-100 px-2 py-1 rounded">{order.id}</span>
        </p>
        <div className="border-t border-b py-4 my-4">
          <h4 className="font-semibold mb-2">Order Summary:</h4>
          {order.items.map((item) => (
            <div key={item.id} className="flex justify-between text-sm">
              <span>{item.quantity}x {item.name}</span>
              <span>${(parseFloat(item.price) * item.quantity).toFixed(2)}</span>
            </div>
          ))}
        </div>
        <div className="flex justify-between font-semibold mb-6">
          <span>Total:</span>
          <span>${order.totalPrice.toFixed(2)}</span>
        </div>
        <button
          onClick={onClose}
          className="w-full bg-amber-700 text-white px-4 py-2 rounded hover:bg-amber-800 transition-colors"
        >
          Close
        </button>
      </div>
    </div>
  );
}

2. 搭建后端接口处理订单存储

用Node.js+Express编写后端接口,负责读写订单文件:

// 后端示例文件:server.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

// 解析JSON请求体
app.use(express.json());

// 订单文件路径,确保路径正确
const ORDERS_FILE = path.join(__dirname, 'orders.json');

// 保存订单接口
app.post('/api/save-order', (req, res) => {
  const newOrder = req.body;
  let orders = [];

  try {
    // 检查文件是否存在,存在则读取现有订单
    if (fs.existsSync(ORDERS_FILE)) {
      const data = fs.readFileSync(ORDERS_FILE, 'utf8');
      orders = JSON.parse(data);
    }
    // 添加新订单
    orders.push(newOrder);
    // 写入文件
    fs.writeFileSync(ORDERS_FILE, JSON.stringify(orders, null, 2));
    res.status(200).json({ message: '订单保存成功' });
  } catch (error) {
    console.error('保存订单出错:', error);
    res.status(500).json({ message: '保存订单失败' });
  }
});

// 获取所有订单接口(供管理员面板使用)
app.get('/api/get-orders', (req, res) => {
  try {
    if (fs.existsSync(ORDERS_FILE)) {
      const data = fs.readFileSync(ORDERS_FILE, 'utf8');
      const orders = JSON.parse(data);
      res.status(200).json(orders);
    } else {
      // 文件不存在则返回空数组
      res.status(200).json([]);
    }
  } catch (error) {
    console.error('获取订单出错:', error);
    res.status(500).json({ message: '获取订单失败' });
  }
});

// 启动后端服务
const PORT = 3001;
app.listen(PORT, () => {
  console.log(`后端服务运行在 http://localhost:${PORT}`);
});

3. 配置前端代理解决跨域

如果前端和后端运行在不同端口,需要配置代理让前端请求转发到后端:

  • Vite项目:在vite.config.js中添加代理配置:
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
      },
    },
  },
};
  • Create React App项目:在package.json中添加一行:
"proxy": "http://localhost:3001"

4. 管理员面板获取订单数据

管理员面板通过后端接口获取订单列表:

import React, { useState, useEffect } from 'react';
import { OrderType } from './types';

export function AdminOrders() {
  const [orders, setOrders] = useState<OrderType[]>([]);

  useEffect(() => {
    const fetchOrders = async () => {
      try {
        const response = await fetch('/api/get-orders');
        const data = await response.json();
        setOrders(data);
      } catch (error) {
        console.error('获取订单失败:', error);
      }
    };
    fetchOrders();
  }, []);

  return (
    <div className="p-6">
      <h2 className="text-2xl font-bold mb-6">所有订单</h2>
      <div className="space-y-4">
        {orders.map((order) => (
          <div key={order.id} className="border border-gray-200 rounded-lg p-4">
            <div className="flex justify-between items-center mb-3">
              <span className="font-semibold">订单ID: {order.id}</span>
              <span className="text-lg font-bold">${order.totalPrice.toFixed(2)}</span>
            </div>
            <div className="space-y-2">
              {order.items.map((item) => (
                <div key={item.id} className="flex justify-between text-sm">
                  <span>{item.quantity}x {item.name}</span>
                  <span>${(parseFloat(item.price) * item.quantity).toFixed(2)}</span>
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:24:54