拓冰建站拓冰建站
首页 / 资讯中心 / 正文

H5真机联调index.html踩坑:从Vite构建到Hexo部署全解析

前阵子把一个 H5 项目从开发环境搬到真机去联调结果一个 index.html 差点把我耗到半夜。平时在电脑浏览器上跑得好好的页面一上手机就各种幺蛾子构建打包报 could not resolve entry module index.html真机访问直接 404最无语的是 hexo 博客的 public 目录里连 index.html 的影子都没有。这篇分享不是讲什么高深架构纯粹是记录我在实际开发中踩过的三个“真机才有的坑”。仔细想了一下这三个坑分别对应三个阶段构建期、运行期、部署期。构建期的坑是 Vite/Rollup 找不到 index.html 入口运行期的坑是路由回退和缓存导致页面 404 或白屏部署期的坑是 Hexo 生成后 public 目录里没有 index.html。每一个坑单看都不难但它们凑到一起时绝对能让你怀疑人生。如果你是前端开发、H5 联调新手或者是一个用 Hexo 搭个人博客的朋友这篇内容应该能帮你省下几个小时的排查时间。1. 坑一构建期——rollup 找不到 index.html1.1 报错现场could not resolve entry module index.html 如何一步步复现事情要从一个很“常规”的项目结构说起。当时我的目录长这样my-h5/ ├── src/ │ ├── pages/ │ │ └── home/ │ │ └── index.html │ └── main.js ├── vite.config.js └── package.json在 src/pages/home 下面放了 index.html因为我想让页面入口跟着业务模块走。本地执行npm run dev一切正常页面能开交互也没问题。于是我开始执行npm run build准备打个包放到服务器上给手机访问。结果命令刚跑起来直接红了[Error] Could not resolve entry module index.html. error during build: RollupError: Could not resolve entry module index.html.我当时的第一反应是开什么玩笑index.html 明明就在那里你凭什么说解析不了于是我检查了文件路径、大小写、文件权限全都没问题。甚至把 index.html 复制到根目录再试报错居然消失了但一旦放回 src/pages/home 下又报同样的错。这时候我才意识到问题不在于“文件是否存在”而在于“Vite 认为项目的根目录在哪里”以及“入口配置是否告诉了 Rollup 去哪里找这个文件”。1.2 根因分析Vite 的入口设计root 与 rollupOptions.input 的关系Vite 和传统的 Webpack 有一个很大的不同Webpack 一般直接以 JS 文件作为入口而 Vite 默认把 index.html 当作整个应用的入口。在 Vite 的构建流程里它会先找到根目录下的 index.html解析里面的script typemodule src...然后根据脚本引用关系把整个依赖图交给 Rollup 打包。这里就牵扯到一个关键概念root。Vite 默认的 root 是process.cwd()也就是你执行 npm 命令的当前目录。如果你的 index.html 不在这个 root 下Rollup 在解析入口时就会报Could not resolve entry module index.html。尤其是当你在项目里手动配置了 root比如把 root 指向了 srcexport default { root: src }那么 Vite 会在src/下找 index.html如果src/index.html不存在构建同样失败。还有一种情况是我们喜欢在build.rollupOptions.input里手动指定入口。例如build: { rollupOptions: { input: src/index.html } }看起来好像没问题但要注意这个路径是相对 root 解析的。如果 root 恰好也是 src那 Rollup 实际去找的是src/src/index.html自然找不到。反之如果你的目录结构是 src/pages/home/index.html而 input 写的是src/index.html同样找不到。所以根因就是一个路径解析问题但因为它涉及 root、base、input 多个配置项并且不同版本 Vite 对路径的处理也有细微差异排查起来就会很恼火。1.3 正确的修法三处配置一次改对我最后是靠三处配置的重新梳理解决了问题。第一处也是最推荐的做法把 index.html 放回项目根目录保持 Vite 默认的入口逻辑不去折腾 root。my-h5/ ├── index.html # 放这里 ├── src/ │ ├── pages/... │ └── main.js ├── vite.config.js第二处如果你的确有特殊结构一定要用自定义 root那就把 root 和 input 写清楚并且使用绝对路径来避免歧义import path from path import { defineConfig } from vite export default defineConfig({ root: path.resolve(__dirname, src), build: { rollupOptions: { input: path.resolve(__dirname, src/index.html) } } })第三处如果项目是多页应用各页面的 index.html 分散在不同目录可以在 build.rollupOptions.input 里写一个对象build: { rollupOptions: { input: { main: path.resolve(__dirname, index.html), about: path.resolve(__dirname, src/about/index.html) } } }这样 Rollup 会生成对应的多个 HTML 文件而不会只认一个入口。改完配置后再执行npm run buildepoch 报错就消失了。1.4 为什么这个错非得到真机阶段才蹦出来这个问题看起来像是构建期的问题为什么我标题会说“真机才有的坑”因为我实际遇到的情况是在开发模式下Vite Dev Server 有非常强的容错能力它会自动把某些路径请求映射到项目根目录的 index.html即使入口配置有瑕疵只要你能通过浏览器访问到某个页面就不会察觉。而且不少同学和我一样平时开发根本不会先 build 一次再上真机都是直接npm run dev然后用手机访问 dev server。也就是说只要你在本地没有执行过 build这个 RollupError 就永远不会暴露。等到你真机上需要稳定的静态包时通常第一次打包就炸了。再加上报错信息比较晦涩很多人会先去查网络、查手机端完全没想到问题出在构建配置上。另一个连带场景是本地 build 成功生成了 dist但查看 dist 目录时发现只有 assets 和 devtools 之类的东西根本没有 index.html。这种情况多半是构建输出的文件名或者目录结构被改过导致服务器上入口缺失。所以我的建议是每次 build 之后先看一眼 dist 根目录有没有 index.html再继续真机调试这个习惯能帮你把构建问题挡在手机之外。2. 坑二真机访问——页面白屏、404、路由回退与缓存2.1 先解决“手机看不到页面”dev server 必须监听局域网地址构建问题解决后我接着把打包好的静态文件放到一台内网服务器上用手机访问。手机打开http://localhost:5173当然不行。这个地址在手机浏览器里指向的是手机自己压根没有服务。正确的做法是通过电脑的局域网 IP 访问比如http://192.168.1.100:5173。问题来了Vite Dev Server 默认绑定的是127.0.0.1也就是只监听本机回环地址。你从手机访问电脑的局域网 IP 时请求到达了电脑的网卡但是 Node 进程根本没有监听那个网卡于是连接失败。在电脑上一切正常是因为浏览器和 Node 在同一台机器上走的回环路径。解决办法很简单在 vite.config.js 里设置server: { host: 0.0.0.0, port: 5173 }或者启动的时候加上--host 0.0.0.0。这样 Vite 会监听所有网络接口手机和电脑在同一个 Wi-Fi 下就能通过http://192.168.1.100:5173访问了。如果你用了 8080、3000 这些端口也要注意防火墙放行。Windows 上经常出现“手机能 ping 通电脑但浏览器打不开页面”的情况多半就是 Windows 防火墙或第三方安全软件拦截了 Node 进程的入站连接。值得注意的一点是手机访问 dev server 和访问静态打包产物的体验不同。dev server 会有热更新通过 WebSocket 和浏览器保持连接而手机端经常因为网络波动断连你需要确认 Vite 的clientPort配置是否和访问端口一致否则 HMR 会连不上但页面本身还是可以打开的。2.2 刷新子路由404history 路由需要服务端回退到 index.html把 dev server 跑通之后我又在真机上遇到了新问题从首页进入点击导航到/user/profile页面正常但是一旦在手机浏览器里直接刷新这个子路由就 404。这个经典问题很多人应该都遇到过。原因很简单你的前端框架用了 History 模式路由比如 Vue Router 的createWebHistory()、React Router 的BrowserRouter。这种模式下的 URL 是https://example.com/user/profile浏览器直接发起请求时服务器会在文件系统里寻找user/profile文件当然找不到。开发环境里 Vite Dev Server 会自动做 history fallback所以刷新也没事。但部署到 nginx、Apache 或任何静态文件服务器上服务器不会自动跳回 index.html于是 404 就在真机上出现了。解决方式有几类。如果你用 nginx最经典的配置是location / { try_files $uri $uri/ /index.html; }意思就是优先找真实文件找不到就回退到 index.html交给前端路由处理。如果只是本地起了一个静态服务器做测试可以使用sirv-cli --single或http-server --history-fallback这类工具。如果你不太想折腾服务端配置也可以直接把前端路由改成 Hash 模式。Hash 模式下的 URL 是https://example.com/#/user/profile#后面的内容不会被发送到服务器所以刷新时服务器只会去请求根路径拿到 index.html。这个方案在真机调试时最省事代价是 URL 不够美观分享链接时也会多一个#。我在实际项目中更推荐的做法是开发时用 History 模式方便查看真实路由部署时如果服务器自己控制不了就统一改成 Hash 模式避免 404。这样最大程度降低线上事故率。2.3 真机浏览器缓存index.html 万年不变新代码不执行接着我又踩了一个更隐蔽的坑。代码改了包也重新打了服务器文件也更新了但手机访问还是旧页面。一开始我怀疑是 CDN 缓存上去刷新了很多次也没用。后来用电脑无痕模式访问发现是新版本再用手机普通模式访问依然是旧版本。这明显是手机浏览器的缓存策略比桌面浏览器更激进。仔细一看响应头发现服务器对index.html也返回了强缓存头浏览器直接把旧 HTML 缓存住了。HTML 文件一旦被缓存里面引用的新旧 JS 资源文件名就都是旧的自然不会去加载新代码。正确的缓存策略应该是带 hash 的静态资源JS/CSS/图片使用强缓存文件名变了 URL 就变不带 hash 的 index.html 必须使用 no-cache确保每次请求都回源检查是否更新。在 nginx 里可以这样配置location /index.html { add_header Cache-Control no-cache, no-store, must-revalidate; } location /assets/ { add_header Cache-Control public, max-age31536000, immutable; }加了这段配置以后手机端刷新页面就能拿到最新的 index.html然后通过新文件名的 URL 加载新资源。如果你用的是 CDN还需要检查 CDN 的缓存规则确保 HTML 的 TTL 为 0 或者设置为不缓存。这里有个小技巧真机调试时我一般会在地址栏直接手动修改 URL比如加一个?v日期参数来绕过缓存但这个方法治标不治本还是要在服务端把响应头配置对。另外有些低端安卓浏览器对 no-cache 的处理并不完美可能需要配合meta标签但我个人更建议优先保证服务器响应头正确因为meta标签在部分浏览器里根本不管用尤其是 HTTP 响应头已经存在时浏览器优先信任响应头。2.4 部署到静态服务器的路径问题base 配置决定一切最后一个运行期坑是路径问题。打包完 dist 目录后我直接在电脑上用vite preview访问页面完全正常。然后我把 dist 整个文件夹放到服务器/h5/子目录下让手机访问http://服务器IP:8080/h5/结果整个页面白屏打开 devtools 一看JS 资源请求的是/assets/index.js而不是/h5/assets/index.js。这就是 Vite 的base配置搞的鬼。默认情况下Vite 打包出的 HTML 中资源引用是绝对路径/assets/xxx.js这意味着资源永远指向域名的根目录。如果你部署在根路径没问题但如果部署在子目录就要告诉 Vite 资源的公共路径是什么。解决办法是设置 baseexport default { base: /h5/ }如果完全不确认部署路径也可以用相对路径export default { base: ./ }但相对路径也有坑如果你的前端路由启用了 History 模式子路由下的相对路径可能会算错。所以最稳妥的方式是明确知道部署路径然后写死 base。这个坑是在真机上才显眼的因为你在电脑上通常有某种服务器环境或插件帮你修正了路径手机浏览器可没有这个待遇它只会按 HTML 里的引用来请求资源。3. 坑三Hexo 博客——public 下没生成 index.html3.1 现象本地预览正常真机/服务器找不到 index.html第三个大坑来自我的 Hexo 博客。某天我更新了一篇文章执行了hexo clean hexo generate本地跑hexo server预览首页完全正常。于是我把生成的public文件夹部署到服务器想着手机上访问一下看效果。结果手机访问域名直接 404。登录服务器一看网站的目录结构里居然没有index.html。更奇怪的是本地明明有public/index.html怎么上传之后就不见了后来才搞清楚不是上传丢了而是我在服务器上执行的部署脚本里用了rsync同步目录某个环节把 source 目录底下的旧文件清掉了而没有重新生成。但真正的根源还是 Hexo 本身没有正确生成根目录下的 index.html。3.2 最常见原因Front-matter 缺失导致页面没被渲染Hexo 不是简单地拿 markdown 文件转成 HTML它会根据文件头部---包裹的 YAML Front-matter 来决定如何处理这个文件包括使用哪个 layout、生成的路径等。如果一篇 markdown 文件头部没有---块或者是纯文本没有任何 meta 信息Hexo 在渲染时可能会跳过它甚至直接把它当作不需要处理的静态文件复制过去。我之前遇到过的一个案例是首页的源文件source/index.md内容如下这是一个自动生成的首页没有 title没有 date没有任何 Front-matter。Hexo 在 generate 的时候就不会把这个文件当作需要渲染的页面因此 public 下缺少 index.html。补上 Front-matter 后--- title: 我的首页 layout: index --- 这是一个自动生成的首页重新执行hexo generateindex.html 就出现了。当然layout: index并不是每个主题都适用具体要看主题支持的 layout 名称。但关键是当你发现自己生成出来的 public 目录里没有 index.html 时第一件事就是检查首页源文件是否有合法、完整的 Front-matter。3.3 Hexo 目录与生成器陷阱不是所有 index.md 都会变成 public/index.html很多人会误以为只要新建一个 index.mdHexo 就会在 public 根目录生成 index.html。其实不然。Hexo 的目录结构有明确分工source/_posts下面存放文章它们会在public下生成类似2025/01/01/文章标题/index.html的归档路径source/index.md通常是自定义首页模板的源文件是否会生成public/index.html取决于主题和生成器。我踩过一次很深的坑我写了一个source/_posts/index.md然后指望它成为博客首页结果它只是被当成了文章生成的页面是public/2025/xx/xx/index.html根目录的 index.html 照样没有。直到我明白博客首页的 index.html 是由hexo-generator-index这个插件把最新文章列表渲染成 index 页面而不是由 index.md 本身生成的。如果你发现文章都正常但首页不出来检查 package.json 里是否安装了hexo-generator-index。有些精简主题或自定义配置会把默认生成器去掉导致博客没有列表页入口。安装它npm install hexo-generator-index --save然后重新执行hexo clean hexo generate。另外还有一种情况是source里存在名为index.html的文件但它被当作静态文件直接拷贝没有参与渲染。这样 public 下可能会有一个空白 index.html你也看不出来到底是不是通过 Hexo 生成的。检查一下文件的生成时间以及里面是否有主题模板的内容就能分辨。3.4 处理流程从 clean 到 generate 的排查清单为了让你们少走弯路我总结了一套排查流程可以按顺序执行。第一步在项目根目录运行hexo clean它会清空 public 以及 db.json 缓存。第二步运行hexo generate --debug注意看输出里有没有Processing ...相关日志以及是否有文件被跳过。第三步打开 public 目录确认根目录是否有 index.html。如果没有回到 source 目录检查首页相关的 md 或 html 文件是否存在、格式是否正确、layout 是否匹配主题。如果 public 里有 index.html但服务器上访问 404那么问题可能出在同步部署环节。最常见的是把文件上传到了错误的目录或者使用了 CDN 缓存导致服务器根目录一直用的是旧文件。这时候可以先用服务器上的 curl 直接请求http://你的域名/index.html看返回的是 200 还是 404。如果是 404再排查路径如果是 200 但手机访问还是 404检查 CDN 配置和 DNS 解析。最后还有一个容易被忽略的问题真机访问 Hexo 本地预览时同样会遇到 host 绑定问题。默认hexo server监听的是 0.0.0.0但如果你手动设置了-i 127.0.0.1手机就访问不到。建议直接使用hexo server -i 0.0.0.0 -p 4000同时在手机浏览器里访问http://电脑IP:4000才能看到本地预览。不然你折腾半天还以为页面坏了其实就是没监听局域网地址。4. 我的排查顺序与三个避坑习惯说实话这三个坑单拎出来都不难难的是它们会同时出现。我那次 debug 的顺序非常乱先以为路由问题改了 Hash 路由没用又以为是缓存清了缓存还是旧版最后才发现是构建时入口配置错误导致 dist 根本没有 index.html。教训很深。我现在固定一套流程先跑npm run build检查 dist 里有没有 index.html再起一个本地静态服务器模拟服务器环境访问最后再用真机去访问局域网 IP。任何一步没通过都不急着让手机上场。这样可以把“真机才有的坑”提前暴露在电脑上。另外一个习惯是真机联调前先在手机上强制清缓存或开无痕模式避免 index.html 强缓存干扰判断。如果还是旧页面直接在 devtools 网络面板看 index.html 的响应头判断是不是 Cache-Control 的问题。最后一个小技巧使用 Vite 构建时如果你想快速在真机上测试而不想每次构建可以先启动 dev server 同时设置server.host: 0.0.0.0然后手机直接访问http://IP:端口。这样避开了打包、静态服务器路径这些环节能快速排查是前端代码问题还是部署问题。等确认代码没问题再去做 build 和部署。我后来基本都这么干省了不少时间。一个 index.html 的坑背后牵出了构建入口、静态服务器、路由回退、浏览器缓存、Hexo 生成机制整整五层问题。每次都以为是环境坏了最后发现都是自己对工具链某个默认行为理解不到位。把这些记录下来也是想提醒自己下次再遇到真机上的诡异问题先从最基础的 index.html 开始查别上来就怀疑玄学。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门