根本原因是CSS中url()的相对路径以CSS文件实际位置为基准,开发时服务器模拟根路径掩盖问题,构建后CSS与图片目录分离导致路径解析错误。

本地开发能显示、打包后背景图全 404,根本不是图片丢了,而是浏览器按 css 文件最终所在位置去解析 url() 里的相对路径——而构建工具把 CSS 和图片分开放了,路径基准一变,../img/bg.png 就指向了错误目录。
为什么开发正常、上线就 404?
CSS 中的 url() 路径永远以 CSS 文件自身位置为基准,不是 HTML 页面,也不是源码结构。开发时 Vite/Webpack dev server 会模拟“所有资源都在根下”,掩盖了路径问题;但构建后,CSS 可能被抽到 /css/app.css,图片却在 /assets/img/bg.png,而你写的 url(../assets/img/bg.png) 就会被解析成 /css/../assets/img/bg.png → /assets/img/bg.png ——这看似对,可一旦部署在子路径(如 /my-app/)或 CDN 域名下,实际请求地址就变成 https://example.com/assets/img/bg.png,而真实资源其实在 https://example.com/my-app/assets/img/bg.png。
- Chrome DevTools 的 Computed 面板里点开
background-image,能看到浏览器最终拼出的完整 URL,直接复制粘贴进地址栏验证是否真能访问 - Network 面板里看到请求是
404且路径明显少了一级(比如缺my-app/),基本就是publicPath或部署路径没对齐 - 如果路径显示为
file:///,说明你在本地双击 HTML 打开,没走 HTTP 服务,部分相对路径会被浏览器拦截
用 public/ 目录 + 绝对路径最稳
适合 logo、banner、favicon 这类不需哈希、不常变动的资源。构建工具完全不处理 public/ 下的文件,路径绝对可控。
- 把图片放进
public/assets/bg.png - CSS 或 SCSS 中写
background-image: url('/assets/bg.png');(开头必须有/) - 确认
vite.config.ts中base配置匹配部署路径:比如部署在/my-app/,就要设base: '/my-app/',否则最终 URL 会漏前缀 - Vue CLI 项目同理,确保
process.env.PUBLIC_URL注入正确,并在 CSS 中统一用url('/assets/bg.png')
需要哈希或动态切换?改用 JS 导入
url() 是纯字符串,构建工具默认不解析它里面的路径;只有 JS 层面的 import 才能触发哈希、压缩、路径重写等处理。
立即学习“前端免费学习笔记(深入)”;
- 在组件中写
import bg from '@/assets/bg.png' - Vue 模板里:
<div :style="{ backgroundImage: `url(${bg})` }"> <li>React 里:<code><div style="{{" backgroundimage:> <li>全局 mixin 或 SCSS <code>@mixin里不能import?那就把已import的变量作为参数传进去 -
url(./assets/bg.png):构建后 CSS 被抽离,.基准变成dist/css/,不再是源码结构 -
url('~@/assets/bg.png'):Webpack 旧版 hack,Vite 不支持,Vue CLI 5+ 已弃用 -
background: url('/my-app/src/assets/bg.png'):源码路径在构建后根本不存在,纯属幻想 - 用
http://或//开头的路径:url(https://cdn.example.com/logo.png)构建工具完全不接管,哈希、压缩、路径重写全失效
别踩这些“看似能跑”的坑
很多写法本地能过,上线立刻挂,因为它们依赖未声明的构建行为或环境假设。
真正麻烦的从来不是写法本身,而是路径基准和构建输出结构之间的错位——只要没盯住 CSS 文件最终落哪儿、图片最终放哪儿、浏览器按谁算 ../,就一定会在某个部署环境里突然失效。


















