Vue 2.0 SSR error page - vue.js

I'm trying to implement ErrorPage vue component for my SSR application. I'm using boilerplate with
// server.js
...
const context = { url: req.url };
const stream = renderer.renderToStream(context);
// If an error occurs while rendering
stream.on('error', (error) => {
var app = new Vue({
template: '<div>Error page</div>'
});
errRenderer.renderToString(app, function (error, html) {
return res
.status(500)
.send(html);
});
});
I'm aware that this is not super pretty, but is there another way of handling that? Ideally I would love to just load an external Vue component and send it to the browser.

Related

Nuxt ServerMiddleware Returning HTML Doc

I am building out a webpage which needs to make a call to the Google Geocoder api.
In order to hide the api key from public view, I am trying to set up server middleware to act as a REST api endpoint.
I have checked through all of the documentation and copied all of it, but the response is always the same. I receive the entirety of the html body back from the axios request rather than anything else I send back via express.
In my component I have the following code:
computed: {
normalizedAddress() {
return `${this.member.address.street} ${this.member.address.city}, ${this.member.address.state} ${this.member.address.zip}`.replace(
/\s/g,
'+'
)
}
},
methods: {
async getLocation() {
try {
const res = await axios.get(
`/api/geocode/${this.normalizedAddress}`
)
console.log(res)
} catch (err) {
console.log(err)
}
}
},
In nuxt.config.js I have this setup
serverMiddleware: ['~/api/geocode.js'],
In the root of my project I have an api folder with geocode.js stored there.
geocode.js is below
import express from 'express';
import axios from "axios";
let GEO_API = "MY_API_KEY"
const app = express();
app.use(express.json());
app.get("/", async (req, res) => {
const uri = `https://maps.googleapis.com/maps/api/geocode/json?address=${req.params.address}&key=${GEO_API}`
try {
const code = await axios.get(uri);
if (code.status !== "OK") {
return res.status(500).send(code.status)
}
return res.status(200).send(code);
} catch (err) {
return res.status(500).send(err);
}
});
export default {
path: "/api/geocode/:address",
handler: app
}
Again. The response always has the entire html document from the website sent back (some 100 pages of code).
Even when I set the response to fixed text, that is not sent.
The only detail I can think of that might be interrupting it is that I have my own custom routing setup using the #nuxtjs/router build module in use.

Awaiting code execution in quasar.dev boot file before vue.js instantiation

The quasar.dev framework introduces the concept of boot files: https://quasar.dev/quasar-cli/boot-files
Using the vue-oidc-client, I am required to wait for the oidc.startup() call before the Vue instance is created (see https://github.com/soukoku/vue-oidc-client/wiki#app-start)
Since I can't simply put an await outside of an async function, I am wondering how I can achieve this.
My code as reference:
// Importing stuff and creating 'oidc' with createOidcAuth(...)
const startupSucessful = oidc.startup()
if(!startupSucessful) throw Error("Unable to bootstrap OIDC client");
export default boot(async ({ app, router, Vue }) => {
oidc.useRouter(router);
});
Since your export block is async, just put the code you want to wait for there:
export default async ({ app, router, Vue }) => {
const startupSucessful = await oidc.startup()
if(!startupSucessful)
throw Error("Unable to bootstrap OIDC client");
else
oidc.useRouter(router);
});

Vue test-utils how to test a router.push()

In my component , I have a method which will execute a router.push()
import router from "#/router";
// ...
export default {
// ...
methods: {
closeAlert: function() {
if (this.msgTypeContactForm == "success") {
router.push("/home");
} else {
return;
}
},
// ....
}
}
I want to test it...
I wrote the following specs..
it("should ... go to home page", async () => {
// given
const $route = {
name: "home"
},
options = {
...
mocks: {
$route
}
};
wrapper = mount(ContactForm, options);
const closeBtn = wrapper.find(".v-alert__dismissible");
closeBtn.trigger("click");
await wrapper.vm.$nextTick();
expect(alert.attributes().style).toBe("display: none;")
// router path '/home' to be called ?
});
1 - I get an error
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property asa read-only value
2 - How I should write the expect() to be sure that this /home route has been called
thanks for feedback
You are doing something that happens to work, but I believe is wrong, and also is causing you problems to test the router. You're importing the router in your component:
import router from "#/router";
Then calling its push right away:
router.push("/home");
I don't know how exactly you're installing the router, but usually you do something like:
new Vue({
router,
store,
i18n,
}).$mount('#app');
To install Vue plugins. I bet you're already doing this (in fact, is this mechanism that expose $route to your component). In the example, a vuex store and a reference to vue-i18n are also being installed.
This will expose a $router member in all your components. Instead of importing the router and calling its push directly, you could call it from this as $router:
this.$router.push("/home");
Now, thise makes testing easier, because you can pass a fake router to your component, when testing, via the mocks property, just as you're doing with $route already:
const push = jest.fn();
const $router = {
push: jest.fn(),
}
...
mocks: {
$route,
$router,
}
And then, in your test, you assert against push having been called:
expect(push).toHaveBeenCalledWith('/the-desired-path');
Assuming that you have setup the pre-requisities correctly and similar to this
Just use
it("should ... go to home page", async () => {
const $route = {
name: "home"
}
...
// router path '/home' to be called ?
expect(wrapper.vm.$route.name).toBe($route.name)
});

Nuxt: Vuex commit or dispatch message outside vuejs component

I have an application in nuxt that I want to connect to a websocket, I have seen examples where the callback to receive messages is placed inside a component, but I do not think ideal, I would like to place the callback inside my store, currently my code is something like this
//I'm using phoenix websocket
var ROOT_SOCKET = `wss://${URL}/socket`;
var socket = new Socket(ROOT_SOCKET);
socket.connect()
var chan = socket.channel(`connect:${guid}`);
chan.join();
console.log("esperando mensj");
chan.on("translate", payload => {
console.log(JSON.stringify(payload));
<store>.commit("loadTranslation",payload) //<- how can I access to my store?
})
chan.onError(err => console.log(`ERROR connecting!!! ${err}`));
const createStore = () => {
return new Vuex.Store({
state: {},
mutations:{
loadTranslation(state,payload){...}
},
....
})}
how can I access to my store inside my own store file and make a commit??? is it possible?...
I know there is a vuex plugin but I can't really understand well the documentation and I'll prefer build this without that plugin
https://vuex.vuejs.org/guide/plugins.html
thank you guys...hope you can help me...
You can do it in nuxt plugin https://nuxtjs.org/guide/plugins/
export default {
plugins: ['~/plugins/chat.js']
}
// chat.js
export default ({ store }) => {
your code that use store here
}

Accessing app inside beforeRouteEnter

I'd like to show some loading animation in the app root while a component prepares to be rendered by vue router.
Already found this question, proposing the use of navigation guards, and another question, where the accepted answer shows how to use the beforeEach guard to set a variable in app, showing a loading animation.
The problem is that this doesn't work when deep-linking to some route (initial url includes a route path, such as 'someurl#/foo'). The beforeEach guard simply doesn't get called then.
So i switched to the loaded component's beforeRouteEnter guard, which would also allow me to show the loading animation for some components only:
app:
var app = new Vue({
el: '#app',
data: { loading: false }
router: router
});
component:
var Foo = {
template: '<div>bar</div>',
beforeRouteEnter: function(to, from, next) {
app.loading = true; // 'app' unavailable when deep-linking
// do some loading here before calling next()...
next();
}
}
But then i found that when deep-linking to the component, app isn't available in beforeRouteEnter, as it gets called very early in the initialisation process.
I don't want to set loading to true inside the app data declaration, as i might decide at some point to deep-link to another route, whose component doesn't need a loading animation.
I believe, your solution is correct. However, I would suggest using next() function instead. As written in vue-router docs.
https://router.vuejs.org/en/advanced/navigation-guards.html
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
vm.$root.loading = true;
})
}
Found a workaround using Vue.nextTick:
beforeRouteEnter: function(to, from, next) {
Vue.nextTick(function(){
// now app is available
app.loading = true;
// some loading to happen here...
seTimeout(function(){
app.loading = false;
next();
}, 1000);
})
}
Feels a little hacky, so would be thankful for other suggestions.
Find a demo of this solution here:
https://s.codepen.io/schellmax/debug/aYvXqx/GnrnbVPBXezr#/foo
What about using beforeRouteLeave to trigger the loading then have the component toggle it off in mounted.
For the initial load of the app you could have
app:
var app = new Vue({
el: '#app',
data() => ({ loading: true }),
mounted() { this.loading: false },
router: router
});
then for your components
component:
var Foo = {
template: '<div>bar</div>',
mounted() {
app.loading = false;
},
beforeRouteLeave(to, from , next) {
switch(to){
case COMPONENT_TO_SHOW_LOADING_ON:
case OTHER_COMPONENT:
app.loading = true;
default:
}
}
}