Vue项目本地正常,部署Vercel时出现$store读取未定义错误
问题解决:Vuex部署到Vercel时出现"Cannot read properties of undefined (reading '$store')"
核心原因分析
- 导航栏组件混合了
<script setup>和Options API两种写法,导致$store注入逻辑混乱:<script setup>环境下没有this上下文,模板中直接用this.$store会引发错误; - 部署构建流程比本地开发更严格,若
main.js中未正确显式注册store,本地热重载可能侥幸工作,但生产环境会直接报错; - 路由中直接同步导入并访问store状态,可能在客户端store初始化完成前就执行了判断逻辑。
解决方案
步骤1:确保main.js正确注册Vuex Store
这是所有问题的基础,必须保证Vue实例全局注入store:
import { createApp } from 'vue' import App from './App.vue' import store from './store' import router from './router' // 先注册store,再注册路由 createApp(App) .use(store) .use(router) .mount('#app')
步骤2:统一导航栏组件写法
方案A:纯Options API写法(适合原有代码风格)
去掉多余的<script setup>块,将导入移到Options API的script中,同时移除模板里的this.前缀(Options API模板可直接访问$store):
<template> <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #f7dfc2"> <div class="container"> <router-link class="nav-link" :to="{name: 'Landing'}"><img src="@/assets/navschedoo.png" height="40"></router-link> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" style="position: absolute; top: 10px; right: 100px;"> <span class="navbar-toggler-icon"></span> </button> <span class="navbar-text"> <div v-if="$store.state.user" class="profile pButton" ref="profile" @click="toggle" style="position: absolute; top: 7px; right: 30px;"> {{$store.state.profileInitials}} <div class="profile-menu" id="profileMenu" hidden> <div class="info"> <p class="initials">{{$store.state.profileInitials}}</p> <div class="right"> <p>{{$store.state.profileFirstName}} {{$store.state.profileLastName}}</p> <p>{{$store.state.profileUsername}}</p> <p>{{$store.state.profileEmail}}</p> </div> </div> <div class="options"> <div class="option"> <router-link class="option" :to="{name: 'Settings'}" style="color: black; text-decoration: none;" > <userIcon class="icon"/> <p style="padding-top: 10px">Profile</p> </router-link> </div> <div @click="signOut" class="option"> <router-link class="option" to="#" style="color: black; text-decoration: none" > <p>Sign Out</p> </router-link> </div> </div> </div> </div> </span> <div v-if="$store.state.user" class="collapse navbar-collapse" id="navbarSupportedContent" style="height: 40px"> <ul class="navbar-nav mx-auto"> <li class="nav-item"> <router-link class="nav-link" :to="{name: 'Calendar'}" style="color: black; text-decoration: none;" >My Calendar</router-link> </li> <li class="nav-item"> <a class="nav-link active" href="#">Insights</a> </li> <li class="nav-item"> <a class="nav-link active" href="#">Focus</a> </li> </ul> </div> <div v-if="!$store.state.user" class="collapse navbar-collapse" id="navbarSupportedContent" style="height: 40px"> <ul class="navbar-nav mx-auto"> <li class="nav-item"> <a class="nav-link active" @click="toAbout">About Us</a> </li> </ul> <span class="navbar-text" style="right: 5%"> <router-link class="nav-link" :to="{name: 'Login'}" style="color: black; text-decoration: none;" >Login</router-link> </span> </div> </div> </nav> </template> <script> import { auth } from '@/firebase/firebaseInit'; import { RouterLink } from 'vue-router'; export default { name: "Nav", data() { return { hidden: true, loggedin: false, } }, methods: { toggle() { this.hidden = !this.hidden; const menu = document.getElementById("profileMenu"); if (menu) menu.hidden = this.hidden; }, signOut() { auth.signOut(); window.location.href = "/login"; }, toAbout() { console.log("Button clicked"); const about = document.getElementById("about-us"); if (about) about.scrollIntoView({ behavior: "smooth" }); }, }, computed: { user() { return this.$store.state.user; } } }; </script>
方案B:纯Composition API(<script setup>)写法
用useStore钩子获取store实例,统一使用响应式变量:
<script setup> /* eslint-disable */ import { auth } from '@/firebase/firebaseInit'; import { RouterLink } from 'vue-router'; import { useStore } from 'vuex'; import { ref } from 'vue'; const store = useStore(); const hidden = ref(true); const toggle = () => { hidden.value = !hidden.value; const menu = document.getElementById("profileMenu"); if (menu) menu.hidden = hidden.value; }; const signOut = () => { auth.signOut(); window.location.href = "/login"; }; const toAbout = () => { console.log("Button clicked"); const about = document.getElementById("about-us"); if (about) about.scrollIntoView({ behavior: "smooth" }); }; </script> <template> <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #f7dfc2"> <div class="container"> <router-link class="nav-link" :to="{name: 'Landing'}"><img src="@/assets/navschedoo.png" height="40"></router-link> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" style="position: absolute; top: 10px; right: 100px;"> <span class="navbar-toggler-icon"></span> </button> <span class="navbar-text"> <div v-if="store.state.user" class="profile pButton" @click="toggle" style="position: absolute; top: 7px; right: 30px;"> {{store.state.profileInitials}} <div class="profile-menu" id="profileMenu" :hidden="hidden"> <div class="info"> <p class="initials">{{store.state.profileInitials}}</p> <div class="right"> <p>{{store.state.profileFirstName}} {{store.state.profileLastName}}</p> <p>{{store.state.profileUsername}}</p> <p>{{store.state.profileEmail}}</p> </div> </div> <div class="options"> <div class="option"> <router-link class="option" :to="{name: 'Settings'}" style="color: black; text-decoration: none;" > <userIcon class="icon"/> <p style="padding-top: 10px">Profile</p> </router-link> </div> <div @click="signOut" class="option"> <router-link class="option" to="#" style="color: black; text-decoration: none" > <p>Sign Out</p> </router-link> </div> </div> </div> </div> </span> <div v-if="store.state.user" class="collapse navbar-collapse" id="navbarSupportedContent" style="height: 40px"> <ul class="navbar-nav mx-auto"> <li class="nav-item"> <router-link class="nav-link" :to="{name: 'Calendar'}" style="color: black; text-decoration: none;" >My Calendar</router-link> </li> <li class="nav-item"> <a class="nav-link active" href="#">Insights</a> </li> <li class="nav-item"> <a class="nav-link active" href="#">Focus</a> </li> </ul> </div> <div v-if="!store.state.user" class="collapse navbar-collapse" id="navbarSupportedContent" style="height: 40px"> <ul class="navbar-nav mx-auto"> <li class="nav-item"> <a class="nav-link active" @click="toAbout">About Us</a> </li> </ul> <span class="navbar-text" style="right: 5%"> <router-link class="nav-link" :to="{name: 'Login'}" style="color: black; text-decoration: none;" >Login</router-link> </span> </div> </div> </nav> </template>
步骤3:修正路由守卫中的store访问逻辑
避免直接同步导入store,改用异步导入确保store已初始化:
import { createRouter, createWebHistory } from "vue-router"; import LandingView from '../views/LandingView.vue'; import Login from '../views/Login.vue'; import Register from '../views/Register.vue'; import ForgetPassword from '../views/ForgetPassword.vue'; import Settings from '../views/SettingsView.vue'; import CalendarView from '../views/CalendarView.vue'; import CrEvent from '../components/CrEvent.vue'; import Home from '../views/Home.vue'; const routes = [ { path: "/about", name: "about", component: () => import("../views/AboutView.vue"), }, { path: "/landing", name: "Landing", component: LandingView, }, { path: "/login", name: "Login", component: Login, }, { path: "/register", name: "Register", component: Register, }, { path: "/forget-password", name: "ForgetPassword", component: ForgetPassword, }, { path: "/calendar", name: "Calendar", component: CalendarView, meta: { requiresAuth: true } }, { path: "/create-event", name: "CrEvent", component: CrEvent, }, { path: "/settings", name: "Settings", component: Settings, }, { path: "/home", name: "Home", component: Home, } ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }); router.beforeEach(async (to, from, next) => { if (to.meta.requiresAuth) { const store = await import('@/store/index.js').then(m => m.default); if (store.state.profileId) { next(); } else { next('/login'); } } else { next(); } }); export default router;
内容的提问来源于stack exchange,提问作者chulin
相关产品推荐
相关产品推荐

