Vue3+Vite构建/预览后页面空白无报错求助
Vue 3 + Vite + TypeScript 构建后页面空白无报错问题
使用Vue.js 3 + Vite + TypeScript开发前端应用时,npm run dev启动开发服务一切正常,但执行npm run build后部署或npm run preview预览时,页面始终空白,浏览器控制台无任何报错。
细节说明
- 访问根路由
/和公开路由/login均出现此问题 - 构建流程为
npm run build→cd dist→ 用serve命令启动服务 npm run preview及构建后启动服务的控制台均无异常日志
版本信息
- VITE v3.2.7
- Vue v3.2.41
- Node v18.17.1
- NPM v9.6.7
- Serve v14.2.1
诊断结果
通过在生产/预览模式下启用Chrome Vue Devtools发现:开发模式下<Root>下的<RouterView>能正常渲染组件,但生产/预览模式下无法渲染。Devtools中显示的路由、I18N资源、Vuex等其他资源在两种模式下一致,页面标题和图标能正常加载。
相关项目文件
// vite.config.ts // Plugins import vue from '@vitejs/plugin-vue' import vueI18n from '@intlify/vite-plugin-vue-i18n' import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' // Utilities import { defineConfig } from 'vite' import { fileURLToPath, URL } from 'node:url' import { resolve, dirname } from 'node:path' export default defineConfig(({ mode }) => ({ plugins: [ vue(), vueI18n({ include: resolve(dirname(fileURLToPath(import.meta.url)), './src/locales/**'), }), VueI18nPlugin({ /* options */ }), ], define: { 'process.env': {}, __VUE_PROD_DEVTOOLS__: mode !== 'production' }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, extensions: [ '.js', '.json', '.jsx', '.mjs', '.ts', '.tsx', '.vue', ], }, server: { port: 3000, host: true, }, test: { globals: true, environment: 'jsdom', exclude: [ '**/node_modules/**' ] } }))
// package.json "name": "my-frontend-project", "version": "0.0.0", "private": true, "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && vite build --mode development", // "--mode development" has been added for devtools as said above, the bug happend before I added it. "preview": "vite preview", "lint": "eslint . --fix --ignore-path .gitignore", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook", "test": "vitest", "coverage": "vitest run --coverage" }, [...]
// index.html <!DOCTYPE html> <html lang="fr-BE" class="h-full"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/png" href="/images/tenants/compagny/favicon-180x180.png" sizes="180x180" /> <link rel="icon" type="image/png" href="/images/tenants/compagny/favicon-96x96.png" sizes="96x96" /> <link rel="icon" type="image/png" href="/images/tenants/compagny/favicon-48x48.png" sizes="48x48" /> <link rel="icon" type="image/png" href="/images/tenants/compagny/favicon-32x32.png" sizes="32x32" /> <link rel="icon" type="image/png" href="/images/tenants/compagny/favicon-16x16.png" sizes="16x16" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>compagny - Home</title> </head> <body class="h-full"> <div id="app" class="h-full"></div> <script type="module" src="/src/main.ts"></script> </body> </html>
// src/main.ts // Components import App from './App.vue' import MasonryWall from '@yeger/vue-masonry-wall' import VueTailwindDatepicker from "vue-tailwind-datepicker" import store from '@/core/store' import './index.css' import { i18n } from '@/locales/i18n.configuration' // Composables import { createApp } from 'vue' // Plugins import { registerPlugins } from '@/plugins' export { i18n }; const app = createApp({ extends: App }) registerPlugins(app) app.use(i18n) app.use(MasonryWall) app.use(VueTailwindDatepicker) app.use(store) app.mount('#app')
// src/plugins/index.ts /** * plugins/index.ts * * Automatically included in `./src/main.ts` */ // Plugins import { loadFonts } from './webfontloader' import router from '@/router' // Types import type { App } from 'vue' export function registerPlugins (app: App) { loadFonts() app .use(router) }
// src/App.vue <template> <router-view /> </template> <script lang="ts" setup> // </script>
// src/router/index.ts // Composables import { createRouter, createWebHistory } from 'vue-router' import LocalAuthenticationService from "@/core/services/auth/LocalAuthentication.service"; // Routes import coreRoutes from '@/core/router' const routes = [ ...coreRoutes, ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }) router.beforeEach((to, from, next) => { if (to.meta.requiresAuth) { if (LocalAuthenticationService.isLoggedIn()) { // User is authenticated, proceed to the route next(); } else { // User is not authenticated, redirect to login next({ name: "login" }); } } else { // Non-protected route, allow access next(); } }); export default router
// src/core/router.ts const BasicLayout = () => import("./views/layouts/BasicLayout.vue"); const ViewBasicLogin = () => import("./views/auth/BasicLogin.vue"); const TemporaryHome = () => import("./views/temporary/TemporaryHome.vue"); export default [ { path: "/", component: BasicLayout, children: [ { path: "", name: "home", component: TemporaryHome, } ], }, { path: "/login", name: "login", component: ViewBasicLogin, } ] [...]
已尝试无效方案
- 在vite.config.ts的defineConfig中添加
base: './'或base: '/' - 为路由添加
path: '/:pathMatch(.*)*'的全局重定向路由
可能的解决方法
1. 修改App实例创建方式
main.ts中使用extends: App的写法不符合Vue 3常规用法,可能导致生产环境渲染异常,改为直接传入组件:
// src/main.ts const app = createApp(App)
2. 移除冲突的I18n插件
vite.config.ts中同时启用了@intlify/vite-plugin-vue-i18n和@intlify/unplugin-vue-i18n/vite,存在插件冲突风险,注释其中一个后重新构建:
// vite.config.ts plugins: [ vue(), vueI18n({ include: resolve(dirname(fileURLToPath(import.meta.url)), './src/locales/**'), }), // VueI18nPlugin({ /* options */ }), // 注释此插件 ],
3. 调整构建模式配置
package.json中build命令添加了--mode development,尝试移除该参数,使用默认生产模式构建:
// package.json "build": "vue-tsc --noEmit && vite build"
4. 测试同步组件加载
将路由中的异步组件改为同步导入,排查是否为异步组件加载问题:
// src/core/router.ts import BasicLayout from "./views/layouts/BasicLayout.vue"; import ViewBasicLogin from "./views/auth/BasicLogin.vue"; import TemporaryHome from "./views/temporary/TemporaryHome.vue"; export default [ // 路由配置不变 ]
内容的提问来源于stack exchange,提问作者Josef
相关产品推荐
相关产品推荐

