I have started having an issue with apollo client in vue2 when using inside a watch.
I have followed the setup guide for apollo client when using the composition api:
https://v4.apollo.vuejs.org/guide-composable/setup.html#_2-connect-apollo-client-to-vue
so my main.ts looks like this:
import contentfulClient from "./plugins/vue-apollo-contentful";
import apiClient from "./plugins/vue-apollo-api";
Vue.config.productionTip = false;
Vue.use(VueApollo);
new Vue({
router,
store,
vuetify,
setup() {
provide(ApolloClients, {
default: apiClient,
apiClient,
contentfulClient,
});
},
render: (h) => h(App),
}).$mount("#app");
The clients have their own files and are setup the same:
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
const uri = `https://graphql.contentful.com/content/v1/spaces/${process.env.VUE_APP_CONTENTFUL_SPACE_ID}/environments/${process.env.VUE_APP_CONTENTFUL_ENV}?access_token=${process.env.VUE_APP_CONTENTFUL_ACCESS_TOKEN}`;
const link = createHttpLink({
uri,
});
const cache = new InMemoryCache();
const contentfulClient = new ApolloClient({
link,
cache,
});
export default contentfulClient;
I have this component:
import { defineComponent, onMounted, ref, watch } from "#vue/composition-api";
import { useGetCategory } from "#/logic/get-category";
export default defineComponent({
name: "Categories",
setup(_, context) {
const slug = ref(context.root.$route.params.slug);
const result = ref({});
const getCategory = (slug) => {
console.log(slug);
const { category, loading, error } = useGetCategory(slug);
result.value = { category, loading, error };
};
watch(() => context.root.$route.params.slug, getCategory);
onMounted(() => getCategory(slug.value));
return { result };
},
});
When this component loads, it "gets the category" by executing this:
import { useQuery, useResult } from "#vue/apollo-composable";
import * as getCategoryBySlug from "#/graphql/api/query.category.gql";
export function useGetCategory(slug: string) {
const { result, loading, error } = useQuery(getCategoryBySlug, { slug });
const category = useResult(result, null, (data) => data.getCategoryBySlug);
return { category, loading, error };
}
When the page loads, it gets the category fine, but if I change the route parameter (slug) I expect it to get the new category and display it. But instead I get this error:
So I figured that the setup is wrong in main.ts, so I added the non-composition-api aswell, found here:
https://apollo.vuejs.org/guide/installation.html#_1-apollo-client
Now my main.ts looks like this:
import contentfulClient from "./plugins/vue-apollo-contentful";
import apiClient from "./plugins/vue-apollo-api";
Vue.config.productionTip = false;
const apolloProvider = new VueApollo({
defaultClient: apiClient,
});
Vue.use(apolloProvider);
new Vue({
router,
store,
vuetify,
apolloProvider,
setup() {
provide(ApolloClients, {
default: apiClient,
apiClient,
contentfulClient,
});
},
render: (h) => h(App),
}).$mount("#app");
But this does not work. It compiles, but I still get the same error.
Does anyone know what I need to do to get this to work?
What works for me is
setup() {
provide(DefaultApolloClient, apolloClient);
},
I do have the following code in my main.js file:
import Vue from 'vue';
import App from './components/App';
import router from './router';
const app = new Vue({
data: { loading: false },
router,
render: h => h(App),
}).$mount('#app');
Now i try to set the loading var from my router (./router/index.js):
router.beforeEach((to, from, next) => {
this.$root.loading = true;
next();
})
router.afterEach((to, from) => {
this.$root.loading = false;
next();
})
but it doesn't work. I always get
Cannot read property '$root' of undefined
Any ideas what I'm doing wrong?
You should use this in next callback because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
router.beforeEach((to, from, next) => {
//vm refers to this
next((vm)=>{vm.$root.loading = true;});
})
router.afterEach((to, from, next) => {
next((vm)=>{vm.$root.loading = false;});
})
I have created a Vue3 application using the Vue CLI to create my application with Vuex and Router. The application runs well.
Note: I followed this useful doc for the Vuex with Vue3 https://blog.logrocket.com/using-vuex-4-with-vue-3/
Requirement Now I would like to change my Vue3 application to have Server Side Rendering support(i.e. SSR).
I watched this awesome video on creating an SSR application using Vue3 : https://www.youtube.com/watch?v=XJfaAkvLXyU and I can create and run a simple application like in the video. However I am stuck when trying to apply it to my main Vue3 app.
My current sticking point is how to specify the router and vuex on the server code.
My Code
The client entry file (src/main.js) has the following
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
createApp(App).use(store).use(router).mount('#app');
The server entry file (src/main.server.js) currently has the following
import App from './App.vue';
export default App;
And in the express server file (src/server.js) it currently has
const path = require('path');
const express = require('express');
const { createSSRApp } = require('vue');
const { renderToString } = require('#vue/server-renderer');
...
...
server.get('*', async (req, res) => {
const app = createSSRApp(App);
const appContent = await renderToString(app);
I need to change this code so that the app on the server side is using the router and vuex like it is on the client.
Issues
In the express server file i can not import the router and vuex like in the client entry file as it fails due to importing outside a module, therefore in the express server I can not do the following
const app = createSSRApp(App).use(store).use(router);
I have tried changing the server entry file (src/main.server.js) to the following, but this does not work either.
import App from './App.vue';
import router from './router';
import store from './store';
const { createSSRApp } = require('vue');
export default createSSRApp(App).use(store).use(router);
Does anyone know how to do SSR in Vue 3 when your app is using Vuex and Router.
How i did this in Vue 2 is below and what i am trying to change over to Vue 3
My Vue2 version of this application had the following code
src/app.js creates the Vue component with the router and store specified
Client entry file (src/client/main.js) gets the app from app.js, prepopulates the Vuex store with the data serialized out in the html, mounts the app when the router is ready
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
import App from './pages/App.vue';
import createStore from './vuex/store';
import createRouter from './pages/router';
export default function createApp() {
const store = createStore();
const router = createRouter();
sync(store, router);
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return { app, router, store };
}
Server Entry file (src/server/main.js), gets the app from app.js, get the matched routes which will call the "serverPrefetch" on each component to get its data populated in the Vuex store, then returns the resolve promise
import createApp from '../app';
export default (context) => new Promise((resolve, reject) => {
const { app, router, store } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject(new Error('404'));
}
context.rendered = () => {
context.state = store.state;
};
return resolve(app);
}, reject);
});
Express server (/server.js) uses the bundle renderer to render the app to a string to put in the html
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const dotenv = require('dotenv');
dotenv.config();
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-server-bundle.json'),
{
template: fs.readFileSync('./index.html', 'utf-8'),
},
);
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = {
url: req.url,
clientBundle: `client-bundle.js`,
};
bundleRenderer.renderToString(context, (err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
res.end(html);
}
});
});
const port = process.env.PORT || 3000
server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
I have managed to find the solution to this thanks to the following resources:
Server Side Rendering with Vue.js 3 video: https://www.youtube.com/watch?v=XJfaAkvLXyU&feature=youtu.be and git repos: https://github.com/moduslabs/vue3-example-ssr
SSR + Vuex + Router app : https://github.com/shenron/vue3-example-ssr
migrating from Vue 2 to Vue 3
https://v3-migration.vuejs.org/breaking-changes/introduction.html
migrating from VueRouter 3 to VueRouter 4
https://next.router.vuejs.org/guide/migration/
migrating from Vuex 3 to Vuex 4
https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html
client entry file (src/main.js)
import buildApp from './app';
const { app, router, store } = buildApp();
const storeInitialState = window.INITIAL_DATA;
if (storeInitialState) {
store.replaceState(storeInitialState);
}
router.isReady()
.then(() => {
app.mount('#app', true);
});
server entry file (src/main-server.js)
import buildApp from './app';
export default (url) => new Promise((resolve, reject) => {
const { router, app, store } = buildApp();
// set server-side router's location
router.push(url);
router.isReady()
.then(() => {
const matchedComponents = router.currentRoute.value.matched;
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject(new Error('404'));
}
// the Promise should resolve to the app instance so it can be rendered
return resolve({ app, router, store });
}).catch(() => reject);
});
src/app.js
import { createSSRApp, createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
const isSSR = typeof window === 'undefined';
export default function buildApp() {
const app = (isSSR ? createSSRApp(App) : createApp(App));
app.use(router);
app.use(store);
return { app, router, store };
}
server.js
const serialize = require('serialize-javascript');
const path = require('path');
const express = require('express');
const fs = require('fs');
const { renderToString } = require('#vue/server-renderer');
const manifest = require('./dist/server/ssr-manifest.json');
// Create the express app.
const server = express();
// we do not know the name of app.js as when its built it has a hash name
// the manifest file contains the mapping of "app.js" to the hash file which was created
// therefore get the value from the manifest file thats located in the "dist" directory
// and use it to get the Vue App
const appPath = path.join(__dirname, './dist', 'server', manifest['app.js']);
const createApp = require(appPath).default;
const clientDistPath = './dist/client';
server.use('/img', express.static(path.join(__dirname, clientDistPath, 'img')));
server.use('/js', express.static(path.join(__dirname, clientDistPath, 'js')));
server.use('/css', express.static(path.join(__dirname, clientDistPath, 'css')));
server.use('/favicon.ico', express.static(path.join(__dirname, clientDistPath, 'favicon.ico')));
// handle all routes in our application
server.get('*', async (req, res) => {
const { app, store } = await createApp(req);
let appContent = await renderToString(app);
const renderState = `
<script>
window.INITIAL_DATA = ${serialize(store.state)}
</script>`;
fs.readFile(path.join(__dirname, clientDistPath, 'index.html'), (err, html) => {
if (err) {
throw err;
}
appContent = `<div id="app">${appContent}</div>`;
html = html.toString().replace('<div id="app"></div>', `${renderState}${appContent}`);
res.setHeader('Content-Type', 'text/html');
res.send(html);
});
});
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`You can navigate to http://localhost:${port}`);
});
vue.config.js
used to specify the webpack build things
const ManifestPlugin = require('webpack-manifest-plugin');
const nodeExternals = require('webpack-node-externals');
module.exports = {
devServer: {
overlay: {
warnings: false,
errors: false,
},
},
chainWebpack: (webpackConfig) => {
webpackConfig.module.rule('vue').uses.delete('cache-loader');
webpackConfig.module.rule('js').uses.delete('cache-loader');
webpackConfig.module.rule('ts').uses.delete('cache-loader');
webpackConfig.module.rule('tsx').uses.delete('cache-loader');
if (!process.env.SSR) {
// This is required for repl.it to play nicely with the Dev Server
webpackConfig.devServer.disableHostCheck(true);
webpackConfig.entry('app').clear().add('./src/main.js');
return;
}
webpackConfig.entry('app').clear().add('./src/main-server.js');
webpackConfig.target('node');
webpackConfig.output.libraryTarget('commonjs2');
webpackConfig.plugin('manifest').use(new ManifestPlugin({ fileName: 'ssr-manifest.json' }));
webpackConfig.externals(nodeExternals({ allowlist: /\.(css|vue)$/ }));
webpackConfig.optimization.splitChunks(false).minimize(false);
webpackConfig.plugins.delete('hmr');
webpackConfig.plugins.delete('preload');
webpackConfig.plugins.delete('prefetch');
webpackConfig.plugins.delete('progress');
webpackConfig.plugins.delete('friendly-errors');
// console.log(webpackConfig.toConfig())
},
};
src/router/index.js
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const isServer = typeof window === 'undefined';
const history = isServer ? createMemoryHistory() : createWebHistory();
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
const router = createRouter({
history,
routes,
});
export default router;
src/store/index.js
import Vuex from 'vuex';
import fetchAllBeers from '../data/data';
export default Vuex.createStore({
state() {
return {
homePageData: [],
};
},
actions: {
fetchHomePageData({ commit }) {
return fetchAllBeers()
.then((data) => {
commit('setHomePageData', data.beers);
});
},
},
mutations: {
setHomePageData(state, data) {
state.homePageData = data;
},
},
});
Github sample code
I found I needed to go through the building the code step by step doing just SSR, just Router, just Vuex and then put it all together.
My test apps are in github
https://github.com/se22as/vue-3-with-router-basic-sample
"master" branch : just a vue 3 app with a router
"added-ssr" branch : took the "master" branch and added ssr code
"add-just-vuex" branch : took the "master" branch and added vuex code
"added-vuex-to-ssr" branch : app with router, vuex and ssr.
You can also use Vite which has native SSR support and, unlike Webpack, works out-of-the-box without configuration.
And if you use vite-plugin-ssr then it's even easier.
The following highlights the main parts of vite-plugin-ssr's Vuex example
<template>
<h1>To-do List</h1>
<ul>
<li v-for="item in todoList" :key="item.id">{{item.text}}</li>
</ul>
</template>
<script>
export default {
serverPrefetch() {
return this.$store.dispatch('fetchTodoList');
},
computed: {
todoList () {
return this.$store.state.todoList
}
},
}
</script>
import Vuex from 'vuex'
export { createStore }
function createStore() {
const store = Vuex.createStore({
state() {
return {
todoList: []
}
},
actions: {
fetchTodoList({ commit }) {
const todoList = [
{
id: 0,
text: 'Buy milk'
},
{
id: 1,
text: 'Buy chocolate'
}
]
return commit('setTodoList', todoList)
}
},
mutations: {
setTodoList(state, todoList) {
state.todoList = todoList
}
}
})
return store
}
import { createSSRApp, h } from 'vue'
import { createStore } from './store'
export { createApp }
function createApp({ Page }) {
const app = createSSRApp({
render: () => h(Page)
})
const store = createStore()
app.use(store)
return { app, store }
}
import { renderToString } from '#vue/server-renderer'
import { html } from 'vite-plugin-ssr'
import { createApp } from './app'
export { render }
export { addContextProps }
export { setPageProps }
async function render({ contextProps }) {
const { appHtml } = contextProps
return html`<!DOCTYPE html>
<html>
<body>
<div id="app">${html.dangerouslySetHtml(appHtml)}</div>
</body>
</html>`
}
async function addContextProps({ Page }) {
const { app, store } = createApp({ Page })
const appHtml = await renderToString(app)
const INITIAL_STATE = store.state
return {
INITIAL_STATE,
appHtml
}
}
function setPageProps({ contextProps }) {
const { INITIAL_STATE } = contextProps
return { INITIAL_STATE }
}
import { getPage } from 'vite-plugin-ssr/client'
import { createApp } from './app'
hydrate()
async function hydrate() {
const { Page, pageProps } = await getPage()
const { app, store } = createApp({ Page })
store.replaceState(pageProps.INITIAL_STATE)
app.mount('#app')
}
Simplest example: Updated with the latest document on the Vue website.
https://github.com/ThinhVu/vue--just-ssr
You can find more examples in this repository which included more about impl SSR in the real world.
The repository not only includes naive implement in VueJs but also contains an example using Vite only, Vite + Vite-SSR-plugin, Nuxt, QuasarJS
(I'm working on it, more examples will be added later).
https://github.com/ThinhVu/vue-ssr-labs
hello guys i follow everything provide no google but none of these working i m new to vuex 3.0.1 and vue 2.5 below of my code
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from 'axios'
import store from './store'
Vue.prototype.$http = axios;
var VueResource = require('vue-resource');
Vue.use(VueResource);
router.beforeEach((to, from, next) => {
console.log(this.$store.state.authUser)// this is not working
if (to.matched.some(record => record.meta.requiresAuth) &&
(!this.$store.state.authUser ||
this.$store.state.authUser === 'null')){
const authUser = localStorage.getItem('authUser')
console.log('here log');
next({ path: '/', })
}else{
console.log('bhar else redirect');
next()
}
});
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: {
App
}
})
everything work porperly if i remove this this.$store.state.authUser it work here is my store.js file
import Vue from 'vue'
import Vuex from 'vuex'
import userStore from './user/userStore.js'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
modules:{
userStore
},
strict:debug
})
here is my userStore
const state = {
authUser:null
}
const mutations = {
SET_AUTH_USER(state,userObj){
state.authUser = userObj
}
}
const actions = {
setUserObject :({commit},userObj) =>{
commit('SET_AUTH_USER',userObj)
}
}
export default {
state, mutations, actions
}
here is my login succcess from where i trie to dispatch value in store note i have value in store
this.$store.dispatch('setUserObject',response.data.id)
i does everything properly but don't know why it throw error Cannot read property 'state' of undefined
You are accessing your store in the router's beforeEach guard using this.$store. But this is not your vue instance and has no $store property.
Since you are importing the store using import store from './store', you can use that store import to access your store as follows:
router.beforeEach((to, from, next) => {
console.log(store.state.userStore.authUser)// this will log put authUser from userStore module
if (to.matched.some(record => record.meta.requiresAuth) &&
(!store.state.userStore.authUser ||
store.state.userStore.authUser === 'null')){
const authUser = localStorage.getItem('authUser')
console.log('here log');
next({ path: '/', })
}else{
console.log('bhar else redirect');
next()
}
});
The authUser property belongs to the userStore module, so to access it you use
store.state.userStore.authUser
I am trying to develop an application using Laravel5.5 and Vue.js2. I am getting below error
I think vue.js can't send authentication token to Laravel. I am trying to send authentication token from vue.js is like below
Vue.http.headers.common['Authorization'] = 'Bearer ' + Vue.auth.getToken()
I placed this code in main.js file. I have a package in vue.js named Auth.js. But it is not working.
UPDATE
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import VueResource from 'vue-resource'
import Auth from './packages/auth/Auth.js'
Vue.use(VueResource)
Vue.use(Auth)
Vue.http.headers.common['Authorization'] = 'Bearer ' + Vue.auth.getToken()
Vue.config.productionTip = false
router.beforeEach((to, from, next) => {
if(to.matched.some(record => record.meta.forVisitors)) {
if(Vue.auth.isAuthenticated()) {
next({
path: '/dashboard'
})
} else {next()}
}
else if(to.matched.some(record => record.meta.Authenti)) {
if(!Vue.auth.isAuthenticated()) {
next({
path: '/'
})
} else {next()}
} else {next()}
})
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})