I have several VueX actions (that run on the server only) and are dispatched from nuxtServerInit. They make HTTP requests to external services, which is slowing down the TTFB.
I would like to implement a cache plugin that can store and retrieve values from Redis. The aim is to avoid making the HTTP requests in actions on every request.
I started out by adding a line to the nuxt.js config file.
{ src: '~/plugins/cache', ssr: true, mode: 'server' },
I then created the following in resources/plugins/cache.js
import redis from 'redis';
export default ({ app }, inject) => {
console.log('Creating redis client');
inject('cache', redis.createClient({
//options removed for brevity
}));
}
I run the app and can see 'Creating redis client' is printed to the console on every page refresh. Is it possible to create a plugin that is instantiated when the server is started and the same instance is used for every request? Or if that is not possible, what is the best way to implement the cache?
As you want to share a data/instance, plugin is not the right place to do that because plugins are created (called) every time new Vue instance is created, which on server means on every request...
So you need something instantiated only once per server...and that's Nuxt module
modules/cacheModule.js
export default function (_moduleOptions) {
// any data you want to share between all requests
const data = {
message: `Hello from cache - ${new Date().toLocalTimeString()}`
};
this.nuxt.hook("vue-renderer:ssr:prepareContext", (ssrContext) => {
ssrContext.$cache = data;
});
}
And use it in server plugin or nuxtServerInit...
store/index.js
export const state = () => ({
cache: {}
});
export const mutations = {
setcache(state, payload) {
state.cache = payload;
}
};
export const actions = {
nuxtServerInit({ commit }, context) {
commit("setcache", context.ssrContext.$cache);
}
};
Demo
Same technique can be used for applying cacheAdapterEnhancer from axios-extensions package on server/client (or both) Axios instance so you can keep your original code (fetching in nuxtServerInit) - more details here
Related
I'm trying to integrate Sequelize to my Nuxt 3 project. However, I couldn't figure out how to make it load only once instead of reloading it every time the page was refreshed / navigating to another routes.
I couldn't find any information on the docs. Is it even possible?
~/plugins/sequelize.server.ts
import { Sequelize } from "sequelize"
export default defineNuxtPlugin(async (nuxtApp) => {
const config = useRuntimeConfig()
const sequelize = new Sequelize(config.dbName, config.dbUser, config.dbPass,{
host: config.dbHost,
port: parseInt(config.dbPort),
dialect: 'mysql',
})
try {
await sequelize.authenticate()
// this log was executed every time I navigate to a new route
// or refreshing the browser.
console.log('Connection has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
return {
provide: {
db: sequelize
}
}
})
OP solved his issue by removing a composable that was initialized on a component's mounted lifecycle hook.
Just a remaining piece of code.
I have a very basic nuxt.js application using JSON in a local db.json file, for some reason the generated static site links leading to network error, but I can access them from the url or page refresh.
nuxt config
generate: {
routes () {
return axios.get('http://localhost:3000/projects')
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
},
main root index page
data() {
return {
projects: []
}
},
async asyncData({$axios}){
let projects = await $axios.$get('http://localhost:3000/projects')
return {projects}
}
single project page
data() {
return {
id: this.$route.params.id
}
},
async asyncData({params, $axios}){
let project = await $axios.$get(`http://localhost:3000/projects/${params.id}`)
return {project}
}
P.S. I have edited the post with the code for the main and single project page
Issues with server-side requests of your application are caused by conflicts of ports on which app and json-server are running.
By default, both nuxt.js and json-server run on localhost:3000 and requests inside asyncData of the app sometimes do not reach correct endpoint to fetch projects.
Please, check fixed branch of your project's fork.
To ensure issue is easily debuggable, it is important to separate ports of API mock server and app itself for dev, generate and start commands.
Note updated lines in nuxt.config.js:
const baseURL = process.env.API_BASE_URL || 'http://localhost:3000'
export default {
server: {
port: 3001,
host: '0.0.0.0'
},
modules: [
['#nuxtjs/axios', {
baseURL
}]
],
generate: {
async routes () {
return axios.get(`${baseURL}/projects`)
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
}
}
This ensures that API configuration is set from a single source and, ideally, comes from environmental variable API_BASE_URL.
Also, app's default port has been changed to 3001, to avoid conflict with json-server.
asyncData hooks have been updated accordingly to pass only necessary path for a request. Also, try..catch blocks are pretty much required for asyncData and fetch hooks, to handle error correctly and access error specifics.
My data doesn't appear on screen after reload page, but I have it on my store. I noticed after reload page mounted hook doesn't work. What it can be? I'm using nuxt
This can be because of many reasons. How does the data end up in the store? Is it static (already hardcoded into the store data) or is it fetched from an other source like an API?
Without your code, there is not much I can do to help out though - please share it and i'll take a look :)
In general, get familliar with the concepts below so you'll understand how Vue and Nuxt work together and handle data:
https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram (this is the Vue lifecycle)
https://nuxtjs.org/guide#schema (this is how Nuxt works on top of that)
https://dev.to/lilianaziolek/understanding-nuxt-vue-hooks-and-lifecycle-part-1-48lc (an article explaining a lot about these concepts)
What you can do is create a plugin that calls an action in your store which can load the data from another source such as ajax or localStorage and load the data into your state. You will also need something to save your state at some point.
An example module; if you are not using a module move this to Vuex.Store({...}):
const store1 = {
state: { value1: '' },
mutations: {
setValue(state, value) {
state.value1 = value
}
},
actions: {
async load({ commit }) {
let data = await fetch('/getMyValue')
let json = await data.json()
commit('setValue', json.value)
},
// Call `store.dispatch('store1/save')` to save
async save({ state }) {
await fetch('/setMyValue', {
method: 'post',
data: JSON.stringify(state) // Data should contain: { value: 'my value' }
})
}
}
}
You would then create the root of store like the following which uses the above module. This has a basic plugin. Once the store has finished loading each function in the plugins array is called. In our case we will use it to load/init the data in the store or modules.
export const store = new Vuex.Store({
// Remove if you are not using modules
modules: {
store1, store2
},
plugins: [
store => {
// Use `store.dispatch('load')` if you are not using modules
store.dispatch('store1/load')
store.dispatch('store2/load')
}
]
})
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
}
I want to pass some extra data from the ssr server that's present after the middleware has run, and use that on client side middleware. A bit similar to what nuxt already does with vuex.
Documentation at the render:context hook:
Every time a route is server-rendered and before render:route hook. Called before serializing Nuxt context into window.__NUXT__, useful to add some data that you can fetch on client-side.
Now my custom plugin defines some hooks as stated in the documentation, but not all seem to be called properly:
module.exports = function() {
this.nuxt.hook('render:route', (url, result, context) => {
console.log('This one is called on every server side rendering')
}
this.nuxt.hook('renderer', renderer => {
console.log('This is never called')
}
this.nuxt.hook('render:context', context => {
console.log('This is only called once, when it starts loading the module')
}
}
What am I doing wrong and how can I pass custom ssr data to the client side renderer?
Ok, just found the solution to the core problem of passing custom data from the (ssr) server to the client:
Create a plugin: plugins/my-plugin.js
export default ({ beforeNuxtRender, nuxtState }) => {
if (process.server) {
beforeNuxtRender(({ nuxtState }) => {
nuxtState.myCustomData = true
})
} else {
console.log('My cystom data on the client side:', nuxtState.myCustomData)
}
}
Then register the plugin in your nuxt.config.js:
module.exports = {
plugins: ['~/plugins/my-plugin']
}
Docs here.