Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to cache get request on SSR? #99

Closed
awronski opened this issue Feb 7, 2018 · 36 comments
Closed

How to cache get request on SSR? #99

awronski opened this issue Feb 7, 2018 · 36 comments

Comments

@awronski
Copy link

awronski commented Feb 7, 2018

Hi!

I want to speed up server side rendering.

What is the best way to cache GET request on the Server Side?

I want axios to:

  • cache only server sides requests
  • cache should be shared between all request
  • no cache on client side

Any hints would be helpful, thanks!

This question is available on Nuxt.js community (#c87)
@pi0
Copy link
Member

pi0 commented Feb 7, 2018

Hey. Actually, there was a long discussion #8 about caching at axios level. This is a won't fix for several reasons. I highly recommend utilizing a caching layer (Like nginx) in the top of your API server. Cache is so hard to manage for production. Especially for dynamic content like API.

@pi0 pi0 added the wontfix label Feb 7, 2018
@awronski
Copy link
Author

awronski commented Feb 7, 2018

@pi0 Thank you for your fast reply.

I have nginx proxy but I wanted to speed it up even more. No request is faster than request to proxy.
I wanted to cache only GET request where the request url is the cache key.

But I understand the complexity of this and your response.

Just for curiosity, is it possible to do this just with interceptors?

@pi0
Copy link
Member

pi0 commented Feb 7, 2018

Yes. axios-extensions suggested by @homerjam can be used. I've not tested this solution but may work: Please confirm if you succeed. We ca add a wiki page for axios level caching:

yarn add axios-extensions

npm i axios-extensions

plugins/axios:

import { cacheAdapterEnhancer, throttleAdapterEnhancer } from 'axios-extensions'

export default function ({ app }) {
    const defaults = app.$axios.defaults

    // https://github.com/kuitos/axios-extensions
    defaults.adapter = throttleAdapterEnhancer(cacheAdapterEnhancer(defaults.adapter, true))
}

@pi0
Copy link
Member

pi0 commented Mar 5, 2018

Closing due inactivity.

@pi0 pi0 closed this as completed Mar 5, 2018
@husayt
Copy link

husayt commented Mar 29, 2018

@pi0
I tried following your advice to set caching for selected requests, but no matter what I do it is not caching the request:

 import { cacheAdapterEnhancer } from "axios-extensions"
 import LRUCache from "lru-cache"  
 const ONE_HOUR = 1000 * 60 * 60

 export default function({ app }) {
      const defaultCache = new LRUCache({ maxAge: ONE_HOUR })
      const defaults = app.$axios.defaults
      defaults.adapter = cacheAdapterEnhancer( defaults.adapter, false, "useCache", defaultCache )
 }

What I also noticed that above function is being call on every call, that means cache keeps being reset each time. Where is the best place to initialise axios

I use nuxt-axios with nuxt-proxy and axios-extensions and do call like this:

axios.get("rest/mycategories", { useCache: true })

@vipulbhavsar94
Copy link

@husayt Were you or anyone else able to solve the caching?

@husayt
Copy link

husayt commented Jul 3, 2018

Here is what I have working:

import { cacheAdapterEnhancer } from "axios-extensions"
import LRUCache from "lru-cache"
const ONE_HOUR = 1000 * 60 * 60

const defaultCache = new LRUCache({ maxAge: ONE_HOUR })

export default function({ $axios }) {

  const defaults = $axios.defaults
  // https://github.com/kuitos/axios-extensions
  defaults.adapter = cacheAdapterEnhancer(
    defaults.adapter,
    false,
    "useCache",
    defaultCache
  )
}

and this is the call

 getAuthors() {
    return axios.get("rest/authors", { useCache: true })
  },

@joernroeder
Copy link

@husayt i've tried your solution but i'm still seeing requests to my api made.

@husayt
Copy link

husayt commented Sep 11, 2018

@joernroeder try this syntax

defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
  enabledByDefault: false,
  cacheFlag: "useCache",
  defaultCache
})

@felixdenoix
Copy link

@husayt implemented your solution and it seems to be working well !
(Although sometimes it seems that the server is still making the request)
Does someone knows if it would be possible to know if the reponse is comming from the cache or the server ?

@Shardik04
Copy link

Shardik04 commented Apr 4, 2019

@husayt How Is it possible to know if the response is coming from the cache or the server? How we can verify?
will this work on development also or only in production?
This caching is for first server-side request only please correct/confirm??

await this.$axios
   .get(`/api/predict_win`, {
       params: {
           fields: 'name,question,id'
        }
     }) 
}, { useCache: true })
   .then(res => {
      return res;
}).catch(err => {
    return err;
})

@felixdenoix
Copy link

felixdenoix commented Apr 9, 2019

Hi @hardikshah91,

DISCLAIMER: The informations I'm going to provide you are the one I've understood on my own and might eventually be incorrect. ⚠️:)

There is going to be two caches, one server side and one client side. The one server side is going to be used and the data are going to be stored in for server-side requests, such as the nuxtServerInit Hook. Then the bundle is going to be send to the client with the cache already hydrated by the data from the server. [This caching is for first server-side request only please correct/confirm?? ==> YES]

The requests that are going to be executed client side are going to be able to take advantage of the cache data from the server if the same request was previously made. If the request is made by the client the cache is going to be client side and thus only profitable to a specific client.

To ensure the cache was working, since the cache is supposed to be much faster you're supposed to be seeing a noticeable difference, you could use console.time() to check the duration of a specific request. You could also log the LRU cache keys before and after your requests to check the contents of it !

Hope I've answered some of your questions !

@tiagomatosweb
Copy link

Hello,
I'm facing the same difficulty.
I can see that my calls are being cached but I am not able to turn off on the endpoint I dont want to cache.

My axios plugin

 // axios via @nuxt/axios module
    const defaults = $axios.defaults;
    defaults.adapter = cacheAdapterEnhancer(
        defaults.adapter
    );

In my method

getSomething() {
        const url = '/something';
        return this.$axios.$get(url, { cache: false });
    },

And I can't switch off the cache.

What I am missing?

@wizardpisces
Copy link

@joernroeder try this syntax

defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
  enabledByDefault: false,
  cacheFlag: "useCache",
  defaultCache
})

browser works fine but server-side seems to run the entire plugin file so it still reset cache, does anyone know why?

@wizardpisces
Copy link

@joernroeder try this syntax

defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
  enabledByDefault: false,
  cacheFlag: "useCache",
  defaultCache
})

browser works fine but server-side seems to run the entire plugin file so it still reset cache, does anyone know why?

fixed by passing on defaultCache from koa server to nuxt plugin

@mlg-dan
Copy link

mlg-dan commented Nov 4, 2019

@joernroeder try this syntax

defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
  enabledByDefault: false,
  cacheFlag: "useCache",
  defaultCache
})

browser works fine but server-side seems to run the entire plugin file so it still reset cache, does anyone know why?

fixed by passing on defaultCache from koa server to nuxt plugin

@wizardpisces could you share how you did that?

@felixdenoix
Copy link

@mlg-dan the anwer is just a scroll away to the top of the page
#99 (comment)

@mlg-dan
Copy link

mlg-dan commented Nov 4, 2019

@felixdenoix I am seeing very inconsistent usage of the cache on the server (call in nuxtServerInit). sometimes its takes a few requests before it starts returning from cache, other times I can never get cache to return. it looked like @wizardpisces was seeing the same thing and fixed it by moving defaultCache out of the plugin, but im not sure how he did that.

@felixdenoix
Copy link

@mlg-dan the cache is only serving data in case of server-side requests: see this response #99 (comment)
In @wizardpisces comment, he is actually passing the defaultCache object from the koa server to the nuxt plugin
Hope this answer clarifies the situation for you

@andreynazarov3
Copy link

andreynazarov3 commented Nov 19, 2019

Hi guys, so the only way I found to use global cache in nuxt is to add express middlware adding cache object to request like this, so you can use it in plugin ...

// server/index.js
const axiosCache = require('./cache');

app.use(function(req, res, next) {
    req.axiosCache = axiosCache;
    next();
  });
// plugins/axios.js
export default function(context) {
  const { $axios, app } = context;
  if (process.server) {
    const { req } = context;
    $axios.defaults.adapter = req.axiosCache.adapter;
  } else {
    $axios.defaults.adapter = require('@/server/cache').adapter;
  }
...

anyone has better ideas?

@DanielCambray
Copy link

Hi,

I'm sorry I don't get it. Each time I call the server, the cache is reinitialized. Has anyone a good example to give ?

Thanks.

@andreynazarov3
Copy link

Hi,

I'm sorry I don't get it. Each time I call the server, the cache is reinitialized. Has anyone a good example to give ?

Thanks.

create cache and pass it with in context req property

// server/cache.js
const { setupCache } = require('axios-cache-adapter');
module.exports = setupCache({
  readHeaders: true,
  debug: false
});
// server/index.js
const axiosCache = require('./cache');
// add cache object to req
  app.use(function(req, res, next) {
    req.axiosCache = axiosCache;
    next();
  });

add to axios instance

// plugins/axios.js

export default function(context) {
  const { $axios, app } = context;
  if (process.server) {
    const { req } = context;
    $axios.defaults.adapter = req.axiosCache.adapter;
  } else {
    $axios.defaults.adapter = require('@/server/cache').adapter;
  }

@DanielCambray
Copy link

I have no 'server' directory and if I add one and create index.js in it, it doesn't seem to be taken into account.

@andreynazarov3
Copy link

andreynazarov3 commented Dec 19, 2019

I have no 'server' directory and if I add one and create index.js in it, it doesn't seem to be taken into account.

you need to use custom server for that
https://nuxtjs.org/api/nuxt#nuxt-constructor

you can create new nuxt app, and select custom server feature to get all boilerplate code

@wizardpisces
Copy link

@mlg-dan the cache is only serving data in case of server-side requests: see this response #99 (comment)
In @wizardpisces comment, he is actually passing the defaultCache object from the koa server to the nuxt plugin
Hope this answer clarifies the situation for you

exactly!

@DanielCambray
Copy link

Ok thanks. I undertand better.

@husayt
Copy link

husayt commented Feb 17, 2020

Here is working solution with latest Nuxt 2.11, using locally defined module.

First add a local module to nuxt.config.js

modules: [
   "@/modules/axCache",
...
]

Then

//  modules/axCache.js
import LRU from "lru-cache"

export default function(_moduleOptions) {
  const ONE_HOUR = 1000 * 60 * 60
  const axCache = new LRU({ maxAge: ONE_HOUR })

  this.nuxt.hook("vue-renderer:ssr:prepareContext", ssrContext => {
    ssrContext.$axCache = axCache
  })
}

and

// plugins/axios.js
import { cacheAdapterEnhancer } from "axios-extensions"
import LRU from "lru-cache"
const ONE_HOUR = 1000 * 60 * 60

export default function({ $axios, ssrContext }) {
  const defaultCache = process.server
    ? ssrContext.$axCache
    : new LRU({ maxAge: ONE_HOUR })
  
  const defaults = $axios.defaults
  // https://github.com/kuitos/axios-extensions
  defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
    enabledByDefault: false,
    cacheFlag: "useCache",
    defaultCache
  })
}

Note, this works for both server/client sides and can be configured to work only on one side.

@vskavgaci
Copy link

Here is working solution with latest Nuxt 2.11, using locally defined module.

First add a local module to nuxt.config.js

modules: [
   "@/modules/axCache",
...
]

Then

//  modules/axCache.js
import LRU from "lru-cache"

export default function(_moduleOptions) {
  const ONE_HOUR = 1000 * 60 * 60
  const axCache = new LRU({ maxAge: ONE_HOUR })

  this.nuxt.hook("vue-renderer:ssr:prepareContext", ssrContext => {
    ssrContext.$axCache = axCache
  })
}

and

// plugins/axios.js
import { cacheAdapterEnhancer } from "axios-extensions"
import LRU from "lru-cache"
const ONE_HOUR = 1000 * 60 * 60

export default function({ $axios, ssrContext }) {
  const defaultCache = process.server
    ? ssrContext.$axCache
    : new LRU({ maxAge: ONE_HOUR })
  
  const defaults = $axios.defaults
  // https://github.com/kuitos/axios-extensions
  defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
    enabledByDefault: false,
    cacheFlag: "useCache",
    defaultCache
  })
}

Note, this works for both server/client sides and can be configured to work only on one side.

hi @husayt i tried that, client side is working but not working on server side. when i refresh page still calling api.

@LeoSeyers
Copy link

@husayt I tried both solutions on static generated mode but my build still calls my API like crazy

@pi0 I can't use nginx on my client infrastructure but I can probably cache my /generate Wordpress endpoint using Transient API or WP REST Cache plugin but it will still look like my servers is DDOS'ed right?

@cliqer
Copy link

cliqer commented Mar 25, 2020

@pi what is the best up to date solution?
Is there any final cache approach that works with any case?

@bblanchon
Copy link
Contributor

Here is how I made it work with axios-cache-adapter.

modules/axios-cache.js

import { setupCache } from 'axios-cache-adapter'

export default function () {
  const cache = setupCache({
    // See https://github.com/RasCarlito/axios-cache-adapter/#options
  })

  this.nuxt.hook('vue-renderer:ssr:prepareContext', (ssrContext) => {
    ssrContext.$axiosCache = cache
  })
}

plugins/axios-cache.server.js

export default function ({ $axios, ssrContext }) {
  $axios.defaults.adapter = ssrContext.$axiosCache.adapter
}

nuxt.config.js

{
  plugins: [
    '~/plugins/axios-cache.server.js',
  ],
  modules: [
    '~/modules/axios-cache.js',
  ],
}

Explanation

We cannot use a plugin alone because it's called for each SSR request; that's why we need the module, which is only called once at startup.

Note: all the credit goes to this answer, I only adapted it to axios-cache-adapter.

@onward-web
Copy link

onward-web commented Mar 9, 2021

Here is how I made it work with axios-cache-adapter.

modules/axios-cache.js

import { setupCache } from 'axios-cache-adapter'

export default function () {
  const cache = setupCache({
    // See https://github.com/RasCarlito/axios-cache-adapter/#options
  })

  this.nuxt.hook('vue-renderer:ssr:prepareContext', (ssrContext) => {
    ssrContext.$axiosCache = cache
  })
}

plugins/axios-cache.server.js

export default function ({ $axios, ssrContext }) {
  $axios.defaults.adapter = ssrContext.$axiosCache.adapter
}

nuxt.config.js

{
  plugins: [
    '~/plugins/axios-cache.server.js',
  ],
  modules: [
    '~/modules/axios-cache.js',
  ],
}

Explanation

We cannot use a plugin alone because it's called for each SSR request; that's why we need the module, which is only called once at startup.

Note: all the credit goes to this answer, I only adapted it to axios-cache-adapter.

It is dos't work for me. Each new request leads to calls to the server. The settings have been adjusted, but it doesn't help. Please help me where to dig.

modules/axios-cache.js
`const { setup, RedisDefaultStore } = require('axios-cache-adapter');
const redis = require('redis');

export default function () {
    const client = redis.createClient({
        host: '127.0.0.1',
        port: '6379',
        password: '*****',
        prefix: 'nuxt2:',
    })

    const storeRedis = new RedisDefaultStore(client, {})

    const cache = setupCache({
        maxAge: 15 * 60 * 1000,
        limit: false,
        store: storeRedis, // Pass `RedisDefaultStore` store to `axios-cache-adapter`
        exclude: {
            query: false,
            methods: [],
        },
        clearOnStale: false,
        clearOnError: false,
        debug: true,

    });

    this.nuxt.hook('vue-renderer:ssr:prepareContext', (ssrContext) => {
        ssrContext.$axiosCache = cache
    })
}`

@davodaslanifakor
Copy link

Here is working solution with latest Nuxt 2.11, using locally defined module.

First add a local module to nuxt.config.js

modules: [
   "@/modules/axCache",
...
]

Then

//  modules/axCache.js
import LRU from "lru-cache"

export default function(_moduleOptions) {
  const ONE_HOUR = 1000 * 60 * 60
  const axCache = new LRU({ maxAge: ONE_HOUR })

  this.nuxt.hook("vue-renderer:ssr:prepareContext", ssrContext => {
    ssrContext.$axCache = axCache
  })
}

and

// plugins/axios.js
import { cacheAdapterEnhancer } from "axios-extensions"
import LRU from "lru-cache"
const ONE_HOUR = 1000 * 60 * 60

export default function({ $axios, ssrContext }) {
  const defaultCache = process.server
    ? ssrContext.$axCache
    : new LRU({ maxAge: ONE_HOUR })
  
  const defaults = $axios.defaults
  // https://github.com/kuitos/axios-extensions
  defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
    enabledByDefault: false,
    cacheFlag: "useCache",
    defaultCache
  })
}

Note, this works for both server/client sides and can be configured to work only on one side.

How can set dynamic maxAge for each request ?

@kovan
Copy link

kovan commented Oct 17, 2021

For anyone interested, I ended up discarding @nuxtjs/axios and using directly Axios myself.

This way I can write the plugin as follows (here it installs HTTP and DNS caches):

// plugins/axios.server.js

import axios from "axios"
import { registerInterceptor } from 'axios-cached-dns-resolve'
import { cacheAdapterEnhancer } from 'axios-extensions';

var myaxios = null

export default function({ $config }, inject) {
    if (!myaxios) {
        myaxios = axios.create({
            baseURL: $config.baseURL,
            adapter: cacheAdapterEnhancer(axios.defaults.adapter)

        })

        registerInterceptor(myaxios)
        console.log("Axios caches installed")    
    }

    inject("axios", myaxios)
    
}

This way the Axios instance is available as $axios in the Nuxt context, as before. Note that the Axios API differs a bit from @axiosjs/nuxt API.

Quite hacky if you ask me, but it works nevertheless.

Probably it would be a good idea to somehow integrate this further in Nuxt or @nuxtjs/axios, because "nuxt generate "usually performs a lot of redundant DNS and HTTP queries. For example Node never caches DNS queries.

@stephenliu1944
Copy link

axios-extensions
Note that POST and other methods besides GET are not affected. so it just work for GET method.

1 similar comment
@stephenliu1944
Copy link

axios-extensions
Note that POST and other methods besides GET are not affected. so it just work for GET method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests