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

Rails 7中React组件本地正常但Heroku生产环境不显示问题排查

问题:Rails 7 + esbuild环境下React组件在Heroku生产环境不加载

问题背景

我有一个Rails 7应用(使用esbuild,无webpacker),包含多个React组件,大部分在本地和Heroku生产环境都能正常工作。但QuizResultsDashboard组件在Heroku上完全不显示,即使简化组件代码后问题依然存在,甚至连初始的console.log输出都没有。

已确认的排查点

  • React已正确安装且正常工作,应用中至少10个其他组件在两种环境下均正常运行
  • 页面本身无问题,同一视图渲染其他组件时本地和Heroku均正常
  • 生产环境资源预编译无错误
  • 代码本身无重大问题,本地可正常渲染

代码实现

application.js中的组件初始化代码

// For Quiz Results Dashboard
import QuizResultsDashboard from './components/QuizResultsDashboard';
document.addEventListener('DOMContentLoaded', () => {
  const quizResultsDashboardContainer = document.getElementById('quizResultsDashboard');
  if (quizResultsDashboardContainer) {
    const userId = quizResultsDashboardContainer.getAttribute('data-user-id');
    const root = createRoot(quizResultsDashboardContainer);
    root.render(<QuizResultsDashboard userId={userId} />);
  }
});

视图中的容器代码

<div class="fc-container">
    <div id="quizResultsDashboard" data-user-id="<%= current_login.user.id %>" ></div>
</div>

调试异常信息

  • heroku logs -t未输出有效错误信息
  • 在application.js中添加的console.log在本地正常输出,但Heroku生产环境完全无输出
  • 生产环境打包后的js文件中找不到QuizResultsDashboard的代码
  • 已运行assets:clobber和assets:precompile,并更新了package.json,问题仍未解决

相关代码片段

初始组件代码

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

const QuizResultsDashboard = ({ userId }) => {
  console.log("QuizResultsDashboard rendered");

  const [categoriesData, setCategoriesData] = useState([]);
  const [measurementsData, setMeasurementsData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(`/api/v1/construct_measurements`)
      .then(response => {
        if (!response.ok) {
          console.error('API response not ok:', response.status, response.statusText);
          throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);
        }
        return response.json();
      })
      .then(data => {
        console.log('API response:', JSON.stringify(data, null, 2));
        setCategoriesData(data.categories);
        setMeasurementsData(data.measurements);
        setLoading(false);
      })
      .catch(error => {
        console.error('Fetch error:', error);
        setError(error.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div className="quiz-results-dashboard">
      {categoriesData.map(category => (
        <div key={category} className="fc-content-box mb-8">
          <QuizResultsCategoryChart
            category={category}
            measurements={measurementsData.filter(m => m.category === category)}
          />
        </div>
      ))}
    </div>
  );
};

export default QuizResultsDashboard;

简化后的组件代码

import React from 'react';

const QuizResultsDashboard = ({ userId }) => {
  console.log("QuizResultsDashboard rendered", { userId });
  return <div>Quiz Results Dashboard for user {userId}</div>;
};

export default QuizResultsDashboard;

调试用的application.js代码

// For Quiz Results Dashboard
import QuizResultsDashboard from './components/QuizResultsDashboard';
console.log("THE APPLICATION JS IS HAPPENING.")
document.addEventListener('DOMContentLoaded', () => {
  console.log("IT IS HITTING INSIDE THE DOCUMENT LISTENER.")
  const quizResultsDashboardContainer = document.getElementById('quizResultsDashboard');
  console.log(quizResultsDashboardContainer);
  if (quizResultsDashboardContainer) {
    const userId = quizResultsDashboardContainer.getAttribute('data-user-id');
    const root = createRoot(quizResultsDashboardContainer);
    root.render(<QuizResultsDashboard userId={userId} />);
  }
});



// For MessageBoard
import MessageBoard from './components/MessageBoard';
document.addEventListener('DOMContentLoaded', () => {
  const messageBoardContainer = document.getElementById('messageBoard');
  console.log("WE'RE IN THE MESSAGE BOARD")
  if (messageBoardContainer) {
    const userId = messageBoardContainer.getAttribute('data-user-id');
    const root = createRoot(messageBoardContainer); 
    root.render(<MessageBoard userId={userId} />);
  }
});

package.json内容

{
  "name": "app",
  "private": true,
  "dependencies": {
    "@fortawesome/fontawesome-free": "^6.5.2",
    "@hotwired/stimulus": "^3.2.2",
    "@hotwired/turbo-rails": "^8.0.4",
    "@rails/actioncable": "^7.1.3-2",
    "@tailwindcss/forms": "^0.5.7",
    "autoprefixer": "^10.4.19",
    "axios": "^1.6.8",
    "chart.js": "^4.4.3",
    "esbuild": "^0.20.2",
    "esbuild-plugin-jsx": "^1.0.1",
    "lodash": "^4.17.21",
    "postcss": "^8.4.38",
    "react": "^18.2.0",
    "react-actioncable-provider": "^2.0.0",
    "react-chartjs-2": "^5.2.0",
    "react-datepicker": "^7.3.0",
    "react-dom": "^18.3.1",
    "react-infinite-scroll-component": "^6.1.0",
    "react-intersection-observer": "^9.10.2",
    "react-markdown": "^9.0.1",
    "react-window": "^1.8.10",
    "tailwindcss": "^3.4.3"
  },
  "scripts": {
    "build": "esbuild app/javascript/*.* app/javascript/**/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx --loader:.ttf=file --loader:.woff2=file --external:@rails/actioncable",
    "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify"
  },
  "devDependencies": {
    "@babel/preset-react": "^7.24.1",
    "esbuild-plugin-copy": "^2.1.1"
  }
}

排查方向建议

  • 检查esbuild打包路径匹配:当前build命令使用app/javascript/*.* app/javascript/**/*.*,确认组件文件路径是否被正确覆盖,比如组件是否在app/javascript/components/目录下,同时注意文件名大小写(Heroku基于Linux,区分大小写,本地Mac可能不区分)
  • 本地模拟生产打包验证:执行npm run build生成生产环境打包文件,检查app/assets/builds/application.js中是否包含QuizResultsDashboard的代码及调试用console.log,如果本地打包也缺失,说明是打包配置问题
  • 替换DOMContentLoaded事件:由于使用了Turbo Rails,DOMContentLoaded事件可能不会触发,替换为turbo:load事件,或同时监听两个事件:
    function initQuizResultsDashboard() {
      const quizResultsDashboardContainer = document.getElementById('quizResultsDashboard');
      if (quizResultsDashboardContainer) {
        const userId = quizResultsDashboardContainer.getAttribute('data-user-id');
        const root = createRoot(quizResultsDashboardContainer);
        root.render(<QuizResultsDashboard userId={userId} />);
      }
    }
    
    document.addEventListener('turbo:load', initQuizResultsDashboard);
    document.addEventListener('DOMContentLoaded', initQuizResultsDashboard);
    
  • 检查Heroku构建日志:执行heroku logs --source build查看构建过程,确认npm run build是否被正确执行,有没有打包报错或步骤遗漏
  • 确认组件导入路径:检查import QuizResultsDashboard from './components/QuizResultsDashboard';的路径是否正确,若组件后缀为.jsx,确认esbuild的--loader:.js=jsx配置是否能正确处理,或尝试在导入时添加后缀
  • 验证生产环境Asset配置:确认Rails生产环境配置中config.assets.compile是否为true,避免静态资源未被正确编译
  • 清除浏览器缓存:在Heroku页面强制刷新(Ctrl+Shift+R),查看network标签下的application.js是否加载最新版本,排除缓存问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 16:05:55