How to hide code from client side in vue/nuxt, using Server Side Rendering? - vue.js

I am trying to do some processing on the server side, which I do not want to be viewable on the client side.
I have successfully tried using either fetch or asyncData to populate the state, but I do not want the process followed to be available on the browser.
For example:
<template>
// ...
</template>
<script>
import ...
export default {
layout: 'layout1',
name: 'Name',
components: { ... },
data: () => ({ ... }),
computed: { ... },
async asyncData({ store }) {
const news = await axios.get(
'https://newsurl.xml'
).then(feed =>
// parse the feed and do some very secret stuff with it
// including sha256 with a salt encryption
)
store.commit('news/ASSIGN_NEWS', news)
}
}
</script>
I want the code in asyncData (or in fetch) not to be visible on the client side.
Any suggestion will be appreciated.

There are several questions like this one already, one of which I've answered here and here.
The TLDR being that if you want to make something on server only, you probably don't even want it part of a .vue file from the beginning. Using a backend proxy could also be useful (as stated above), especially if you could benefit from caching or reduce bandwidth overall.

You can use onServerPrefetch hook.
onServerPrefetch(async () => {
const news = await axios.get(
'https://newsurl.xml'
).then(feed =>
// parse the feed and do some very secret stuff with it
// including sha256 with a salt encryption
)
this.$store.commit('news/ASSIGN_NEWS', news)
})

Related

How to use a private API key with Nuxt (on the client)?

Problem Solved
If you're struggling with the same issue, look at the accepted answer which is one way to achieve it by using serverMiddleware
I'm using an API which required a private key. I've stored the key inside a .env file, and called it in the nuxt configuration file, like this :
privateRuntimeConfig: {
secretKey: process.env.MY_SECRET_KEY
},
My API call is done inside the asyncData() hook on my index page. It works fine when i load this page, or reload it, but everytime i use the navigation to come back to this page, i end up with an error (I use a buffer to convert my API key to base64)
First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.
After some research and debugging, i found out that my private key wasn't available at the time, and the "secret" value used in my api call was "undefined".
The thing I don't get is why is this working on initial load / reload but not on page navigation ? And is there a way to fix it without using a backend ? (SSR for SEO and the ability to use private keys without exposing them are the main reasons why i used Nuxt for my project)
Here is my code :
async asyncData({ $content, store, $config }) {
const secret = Buffer.from($config.secretKey).toString('base64')
const request = await fetch('https://app.snipcart.com/api/products', {
headers: {
'Authorization': `Basic ${secret}`,
'Accept': 'application/json'
}
})
const result = await request.json()
store.commit('products/addProducts', result)
const stocks = store.getters['products/getProducts']
return { stocks }
},
Update
Looking at the #nuxtjs/snipcart module's key key and since it's a buildModules, you can totally put it there since it will be available only during the build (on Node.js only)!
For more info, Snipcart do have a lot of blog posts, this one based on Nuxt may help clearing things up: https://www.storyblok.com/tp/how-to-build-a-shop-with-nuxt-storyblok-and-snipcart
You do have your key initially because you're reaching the server when you enter the page or hard refresh it.
If you navigate after the hydration, it will be a client side navigation so you will not be able to have access to the private key. At the end, if your key is really private (nowadays, some API provide keys that can be exposed), you'll need to work around it in some ways.
Looking at Snipcart: https://docs.snipcart.com/v3/api-reference/authentication, it clearly states that the key should be available in
Appear in your compiled front-end assets (HTML, JavaScript)
Meanwhile, if you need to make another call to your backend (trying to access something else than products), you'll need to make a second call.
With Nuxt2, you cannot reach for the backend each time as of right now since you will stay in an SPA context (Nuxt is a server then client Vue app basically). But you could write down the token into a cookie or even better, use a backend as a proxy to hide this specific key (or even a serverless function).
Some more info can be found on my other answer here: https://stackoverflow.com/a/69575243/8816585
Thanks #kissu for your (very) quick answer :)
So, based on what you said and your other answer on the subject, i've made a server Middleware in Nuxt in my server folder.
server/snipcart.js
const bodyParser = require('body-parser')
const axios = require('axios')
const app = require('express')()
app.use(bodyParser.json())
app.all('/getProducts', (request, response) => {
const url = 'https://app.snipcart.com/api/products'
const secret = Buffer.from(process.env.SNIPCART_SECRET).toString('base64')
const config = {
headers: {
'Authorization': `Basic ${secret}`,
'Accept': 'application/json'
}
}
axios
.get(url, config)
.then(res => {
const products = {}
res.data.items.forEach(
item => {
const productId = item.userDefinedId.replace(/-/g, '')
const stocks = {}
item.variants.forEach(
variant => {
const size = variant.variation[0].option
const stock = variant.stock
stocks[size] = stock
}
)
products[productId] = stocks
}
)
response.json(products)
})
.catch( err => response.json(err) )
})
module.exports = app
Correct me if i'm wrong, but I think that's basically the same as using a server as a proxy right ? Based on Nuxt lifecycle hooks, the serverMiddleware one is only run on the server, so my API key shouldn't be exposed to the client ? (I still need to do some refactoring to clean the code, but at least it's working) (https://nuxtjs.org/docs/concepts/nuxt-lifecycle/#server & https://nuxtjs.org/docs/configuration-glossary/configuration-servermiddleware/)
nuxt.config.js
serverMiddleware: [
{ path: "/server", handler: "~/server/snipcart.js" }
]
index.vue (where my snipcart API call was previously made, i guess now I should move this call directly from the product card component where the data is needed) :
async asyncData({ $content, store, $axios }) {
await $axios
.get('/server/getProducts')
.then(res => store.commit('products/addProducts', res.data))
.catch(err => console.log(err))
const stocks = store.getters['products/getProducts']
return {stocks, masterplanProducts }
},
PS : Snipcart does provide a public API key, but the use is very limited. In order to access the remaining stock for each product, i have to use the private key (which allows for some other operations, like removing products / accessing orders and such)
UPDATE :
It's not working when the website is fists accessed from any other page than the one one where the API call is, since the store won't have any data from the API call)
Okay, now I feel dumb. I found a way to make it work. I guess taking the time to explain my problem helped me understand how to solve it.
For those who encounter a similar issue, i fixed it by wrapping my API call with a If statement.
if ($config.secretKey) {
const secret = Buffer.from($config.secretKey).toString('base64')
const request = await fetch('https://app.snipcart.com/api/products', {
headers: {
'Authorization': `Basic ${secret}`,
'Accept': 'application/json'
}
})
const result = await request.json()
store.commit('products/addProducts', result)
}
const stocks = store.getters['products/getProducts']
This way, i can just skip the API call and access values from my vuex store.

How do I properly import multiple components dynamically and use them in Nuxt?

I need to implement dynamic pages in a Nuxt + CMS bundle.
I send the URL to the server and if such a page exists I receive the data.
The data contains a list of components that I need to use, the number of components can be different.
I need to dynamically import these components and use them on the page.
I don't fully understand how I can properly import these components and use them.
I know that I can use the global registration of components, but in this case I am interested in dynamic imports.
Here is a demo that describes the approximate logic of my application.
https://codesandbox.io/s/dank-water-zvwmu?file=%2Fpages%2F_.vue
Here is a github issue that may be useful for you: https://github.com/nuxt/components/issues/227#issuecomment-902013353
I've used something like this before
<nuxt-dynamic :name="icon"></nuxt-dynamic>
to load dynamic SVG depending of the icon prop thanks to dynamic.
Since now, it is baked-in you should be able to do
<component :is="componentId" />
but it looks like it is costly in terms of performance.
This is of course based on Nuxt components and auto-importing them.
Also, if you want to import those from anywhere you wish, you can follow my answer here.
I used this solution. I get all the necessary data in the asyncData hook and then import the components in the created () hook
https://codesandbox.io/s/codesandbox-nuxt-uidc7?file=/pages/index.vue
asyncData({ route, redirect }) {
const dataFromServer = [
{
path: "/about",
componentName: 'myComponent'
},
];
const componentData = dataFromServer.find(
(data) => data.path === route.path
);
return { componentData };
},
data() {
return {
selectedRouteData: null,
componentData: {},
importedComponents: []
};
},
created() {
this.importComponent();
},
methods: {
async importComponent() {
const comp = await import(`~/folder/${this.componentData.componentName}.vue`);
this.importedComponents.push(comp.default);
}

Forward basiauth to axios

I have a site which makes an Axios request. Both the backend and vuejs frontend are on the same domain, and have the same basic auth covering them.
The issue is that whilst the pages load, as soon as an Axios request is made, it asks me again for the basic auth, which doesn't even work if I fill in the details.
Now I imagine I need to pass through the basic auth details somehow, but none of the things I have tried work (and example being below).
If anyone has any tips on passing through the auth token from the parent page to the axios request, that would be great.
const requestOne = axios.get(requestUrl)
const requestTwo = axios.get(requestUrl)
axios
.all([requestOne, requestTwo])
.then(
axios.spread((...responses) => {
<some code here>
})
)
I just answered a similar question with the 3 ways to pass around data in Vue.
You might find it helpful: How to pass v-for index to other components
However, in my opinion, the best approach would be to create a Vue plugin with your Axios client and an init method.
Consider this following (untested) example:
axiosClient.js
import Vue from 'vue';
let instance;
export const getInstance = () => instance;
export const useAxios = () => {
if (instance) return instance;
instance = new Vue({
data() {
return {
client: null,
}
}
});
methods: {
init(authToken) {
this.client = axios.create({
headers: {'Authorization': authToken }
});
}
}
}
export const axiosPlugin = {
install(Vue) {
Vue.prototype.$axios = useAxios();
},
};
Vue.use(axiosPlugin);
Once installed, you can access this in your components using $axios.init(...) and $axios.client.
You can even write API methods directly onto the plugin as well and interact with Vuex through the plugin!
You may need to tweak the plugin a little (and keep in mind this is Vue2 syntax) as I wrote this directly into StackOverflow.
You can also pass any other default values or configuration options through to the axios client by providing options to the plugin and accessing them within init.
You can learn more about plugins here: https://v2.vuejs.org/v2/guide/plugins.html

Nuxtjs Redis cache implementation for API calls inside components

I am using the plugin Nuxt Perfect Cache to server-side cache my QPI requests to an external service.
I am using the cacheFetch method on Component level and this component is loaded on a dynamic page (defined by its slug). When I navigate to the dynamic page, the API call is not cached in Redis, however when I reload the page, the caching happens as expected.
Below is how my code is structured:
_slug.js (for /users)
<template>
<h1>{{ user.name }}</h1>
<Posts :author = user.id>
</template>
<script>
import Posts from '~/components/Posts.vue'
export default {
components: { Posts },
async asyncData({params}) {
const user = await fetch(`/users/${params.slug}`)
.then(res => res.json())
}
}
</script>
And inside Posts.vue I use the perfect cache cacheFetch method to fetch the list of posts, something like:
props: ['author'],
async fetch() {
this.posts = await this.$cacheFetch({ key:`user--#{this.author}--posts`, expire: 60 * 60 },
async () => {
return await fetch(`/users/#{this.author}/posts`).then(res => res.json())
})
},
data() {
return {
posts: []
}
}
When I load the user page directly in the browser, the json response for the posts is saved in Redis as expected. When I navigate from within the application using a NuxtLink, the user page is displayed correctly (including the posts), but no key is set or get from Redis.
How can I ensure the API calls are cached when users interact with the app?
redis is only available in server side not client side when you are navigating in client side you don't have access to redis you can set absolute link to render server side when user is navigating but I don't recommend this.
the best solution is cache data in redis in your api.

how to make a component to get async data and to render server side?

We know that only router component may request asyncData in a ssr environment. i have one main component(not router component), i need some async data to render server side. because it is not router component, so i can't use asyncData for server side rendering.
so i have used created hook for calling async api, but component hook is synchronous and don't wait for promise. what to do for getting async data on server side?
App.vue - Main Component
export default {
components: {
footer: Footer,
header: Header,
selector: selector
},
beforeMount() {
// need preload metadata here
},
created () {
// it return preload metadata as response.
return this.$store.dispatch('GET_PRELOAD_META_DATA');
}
}
Action.js
GET_PRELOAD_META_DATA: ({ commit }) => {
return axios.get("api/preload").then(({ data }) => {
commit('SET_PRELOAD_DATA', data);
});
},
As of Vue 2.6, there is a new SSR feature that may accomplish what you’re trying to do. ServerPrefetch is now a hook that can resolve promises to get async data and can interact with a global store.
Check out more in their documentation. https://ssr.vuejs.org/api/#serverprefetch
(I know this an old post, but I stumbled upon it while googling and thought I might be able to help someone else googling too)